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:
parent
f99764a887
commit
dd03b69609
11 changed files with 1049 additions and 547 deletions
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -2324,6 +2324,19 @@ static struct {
|
|||
{"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},
|
||||
{"stoh", PF_stoh, 0},
|
||||
|
|
|
@ -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
675
engine/common/json.c
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue