New faster json parser/writer library rjson

Replace existing libraries jsonsax_full and jsonsax with it
This commit is contained in:
schellingb 2020-10-12 03:02:20 +09:00
parent b5836a6cb3
commit ba1ed2da4b
11 changed files with 2487 additions and 1551 deletions

View File

@ -1963,8 +1963,7 @@ ifeq ($(HAVE_RBMP), 1)
endif endif
OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \ OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \
$(LIBRETRO_COMM_DIR)/formats/json/jsonsax.o \ $(LIBRETRO_COMM_DIR)/formats/json/rjson.o \
$(LIBRETRO_COMM_DIR)/formats/json/jsonsax_full.o \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.o \ $(LIBRETRO_COMM_DIR)/formats/image_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/m3u/m3u_file.o $(LIBRETRO_COMM_DIR)/formats/m3u/m3u_file.o

View File

@ -3,7 +3,7 @@
#include "util.h" #include "util.h"
#include <encodings/utf.h> #include <encodings/utf.h>
#include <formats/jsonsax.h> #include <formats/rjson.h>
#include <string/stdstring.h> #include <string/stdstring.h>
#include <compat/strl.h> #include <compat/strl.h>
@ -33,12 +33,20 @@
typedef struct typedef struct
{ {
const char *value; char* value;
int is_key; int is_key;
size_t length; size_t length;
unsigned key_hash; unsigned key_hash;
} rcheevos_getvalueud_t; } rcheevos_getvalueud_t;
#define CHEEVOS_RJSON_OPTIONS \
/* Inside the field RichPresencePatch newlines are
* encoded as '\r\n'. This will filter the \r out. */ \
RJSON_OPTION_IGNORE_STRING_CARRIAGE_RETURN \
/* This matches the behavior of the previously used
* json parser. It is probably not required */ \
| RJSON_OPTION_ALLOW_TRAILING_DATA
/***************************************************************************** /*****************************************************************************
Gets a value in a JSON Gets a value in a JSON
*****************************************************************************/ *****************************************************************************/
@ -54,102 +62,87 @@ static uint32_t rcheevos_djb2(const char* str, size_t length)
return hash; return hash;
} }
static int rcheevos_getvalue_key(void* userdata, static bool rcheevos_getvalue_key(void* userdata,
const char* name, size_t length) const char* name, size_t length)
{ {
rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
ud->is_key = rcheevos_djb2(name, length) == ud->key_hash; ud->is_key = rcheevos_djb2(name, length) == ud->key_hash;
return 0; return true;
} }
static int rcheevos_getvalue_string(void* userdata, static bool rcheevos_getvalue_string(void* userdata,
const char* string, size_t length) const char* string, size_t length)
{ {
rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
if (ud->is_key) if (ud->is_key && ud->length > length)
{ {
ud->value = string; strlcpy(ud->value, string, ud->length);
ud->length = length; ud->is_key = 2;
ud->is_key = 0; return false;
} }
return 0; return true;
} }
static int rcheevos_getvalue_boolean(void* userdata, int istrue) static bool rcheevos_getvalue_boolean(void* userdata, bool istrue)
{ {
rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
if (ud->is_key) if (ud->is_key)
{ {
if (istrue) if (istrue && ud->length > 4)
{ {
ud->value = "true"; strlcpy(ud->value, "true", ud->length);
ud->length = 4; ud->is_key = 2;
return false;
} }
else if (!istrue && ud->length > 5)
{ {
ud->value = "false"; strlcpy(ud->value, "false", ud->length);
ud->length = 5; ud->is_key = 2;
return false;
} }
ud->is_key = 0;
} }
return 0; return true;
} }
static int rcheevos_getvalue_null(void* userdata) static bool rcheevos_getvalue_null(void* userdata)
{ {
rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
if (ud->is_key ) if (ud->is_key && ud->length > 4)
{ {
ud->value = "null"; strlcpy(ud->value, "null", ud->length);
ud->length = 4; ud->is_key = 2;
ud->is_key = 0; return false;
} }
return 0; return true;
} }
static int rcheevos_get_value(const char* json, unsigned key_hash, static int rcheevos_get_value(const char* json, unsigned key_hash,
char* value, size_t length) char* value, size_t length)
{ {
static const jsonsax_handlers_t handlers =
{
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
rcheevos_getvalue_key,
NULL,
rcheevos_getvalue_string,
rcheevos_getvalue_string, /* number */
rcheevos_getvalue_boolean,
rcheevos_getvalue_null
};
rcheevos_getvalueud_t ud; rcheevos_getvalueud_t ud;
ud.key_hash = key_hash; ud.key_hash = key_hash;
ud.is_key = 0; ud.is_key = 0;
ud.value = NULL; ud.value = value;
ud.length = 0; ud.length = length;
*value = 0; *value = 0;
if ((jsonsax_parse(json, &handlers, (void*)&ud) == JSONSAX_OK) rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
&& ud.value && ud.length < length) rcheevos_getvalue_key,
{ rcheevos_getvalue_string,
strlcpy(value, ud.value, ud.length + 1); rcheevos_getvalue_string, /* number */
return 0; NULL, NULL, NULL, NULL,
} rcheevos_getvalue_boolean,
rcheevos_getvalue_null, NULL);
return -1; return (ud.is_key == 2 ? 0 : -1);
} }
/***************************************************************************** /*****************************************************************************
@ -186,16 +179,16 @@ typedef struct
unsigned lboard_count; unsigned lboard_count;
} rcheevos_countud_t; } rcheevos_countud_t;
static int rcheevos_count_end_array(void* userdata) static bool rcheevos_count_end_array(void* userdata)
{ {
rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata; rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata;
ud->in_cheevos = 0; ud->in_cheevos = 0;
ud->in_lboards = 0; ud->in_lboards = 0;
return 0; return true;
} }
static int rcheevos_count_key(void* userdata, static bool rcheevos_count_key(void* userdata,
const char* name, size_t length) const char* name, size_t length)
{ {
rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata; rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata;
@ -209,10 +202,10 @@ static int rcheevos_count_key(void* userdata,
else if (ud->field_hash == CHEEVOS_JSON_KEY_ERROR) else if (ud->field_hash == CHEEVOS_JSON_KEY_ERROR)
ud->has_error = 1; ud->has_error = 1;
return 0; return true;
} }
static int rcheevos_count_number(void* userdata, static bool rcheevos_count_number(void* userdata,
const char* number, size_t length) const char* number, size_t length)
{ {
rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata; rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata;
@ -229,30 +222,14 @@ static int rcheevos_count_number(void* userdata,
else if (ud->in_lboards && ud->field_hash == CHEEVOS_JSON_KEY_ID) else if (ud->in_lboards && ud->field_hash == CHEEVOS_JSON_KEY_ID)
ud->lboard_count++; ud->lboard_count++;
return 0; return true;
} }
static int rcheevos_count_cheevos(const char* json, static bool rcheevos_count_cheevos(const char* json,
unsigned* core_count, unsigned* unofficial_count, unsigned* core_count, unsigned* unofficial_count,
unsigned* lboard_count, int* has_error) unsigned* lboard_count, int* has_error)
{ {
static const jsonsax_handlers_t handlers = bool res;
{
NULL,
NULL,
NULL,
NULL,
NULL,
rcheevos_count_end_array,
rcheevos_count_key,
NULL,
NULL,
rcheevos_count_number,
NULL,
NULL
};
int res;
rcheevos_countud_t ud; rcheevos_countud_t ud;
ud.in_cheevos = 0; ud.in_cheevos = 0;
ud.in_lboards = 0; ud.in_lboards = 0;
@ -261,7 +238,14 @@ static int rcheevos_count_cheevos(const char* json,
ud.unofficial_count = 0; ud.unofficial_count = 0;
ud.lboard_count = 0; ud.lboard_count = 0;
res = jsonsax_parse(json, &handlers, (void*)&ud);
res = rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
rcheevos_count_key,
NULL,
rcheevos_count_number,
NULL, NULL, NULL,
rcheevos_count_end_array,
NULL, NULL, NULL);
*core_count = ud.core_count; *core_count = ud.core_count;
*unofficial_count = ud.unofficial_count; *unofficial_count = ud.unofficial_count;
@ -275,168 +259,92 @@ static int rcheevos_count_cheevos(const char* json,
Parses the cheevos in the JSON Parses the cheevos in the JSON
*****************************************************************************/ *****************************************************************************/
typedef struct
{
const char* string;
size_t length;
} rcheevos_field_t;
typedef struct typedef struct
{ {
int in_cheevos; int in_cheevos;
int in_lboards; int in_lboards;
int is_game_id; int lboard_had_id;
int is_title;
int is_console_id;
int is_richpresence;
unsigned core_count; unsigned core_count;
unsigned unofficial_count; unsigned unofficial_count;
unsigned lboard_count; unsigned lboard_count;
rcheevos_field_t* field; unsigned cheevo_flags;
rcheevos_field_t id, memaddr, title, desc, points, author; const char* lboard_format;
rcheevos_field_t modified, created, badge, flags, format; const char** field_string;
unsigned* field_unsigned;
rcheevos_racheevo_t cheevo;
rcheevos_ralboard_t lboard;
rcheevos_rapatchdata_t* patchdata; rcheevos_rapatchdata_t* patchdata;
} rcheevos_readud_t; } rcheevos_readud_t;
static char* rcheevos_unescape_string(const char* string, size_t length) static bool rcheevos_new_cheevo(rcheevos_readud_t* ud)
{
const char* end = string + length;
char* buffer = (char*)malloc(length + 1);
char* buffer_it = buffer;
if (!buffer)
return NULL;
while (string < end)
{
if (*string == '\\')
{
char escaped_char = string[1];
switch (escaped_char)
{
case 'r': /* Ignore carriage return */
string += 2;
break;
case 'n': /* Accept newlines */
*buffer_it++ = '\n';
string += 2;
break;
case 'u': /* Accept UTF-16 unicode characters */
{
#define MAX_SEQUENCES 16
uint16_t utf16[MAX_SEQUENCES];
char utf8[MAX_SEQUENCES * 4];
uint8_t i, j;
for (i = 1; i < MAX_SEQUENCES - 1; i++)
if (strncmp((string + 6 * i), "\\u", 2))
break;
/* Get escaped hex values and add them to the string */
for (j = 0; j < i; j++)
{
char temp[5];
string += 2;
memcpy(temp, string, 4);
temp[4] = '\0';
utf16[j] = string_hex_to_unsigned(temp);
string += 4;
}
utf16[j] = 0;
if (utf16_to_char_string(utf16, utf8, sizeof(utf8)))
{
size_t utf8_len = strlen(utf8);
memcpy(buffer_it, utf8, utf8_len);
buffer_it += utf8_len;
}
}
break;
default:
*buffer_it++ = escaped_char;
string += 2;
break;
}
}
else
*buffer_it++ = *string++;
}
*buffer_it = '\0';
return buffer;
}
static int rcheevos_new_cheevo(rcheevos_readud_t* ud)
{ {
rcheevos_racheevo_t* cheevo = NULL; rcheevos_racheevo_t* cheevo = NULL;
unsigned flags = (unsigned)strtol(ud->flags.string, NULL, 10);
if (flags == 3) if (ud->cheevo_flags == 3)
cheevo = ud->patchdata->core + ud->core_count++; cheevo = ud->patchdata->core + ud->core_count++;
else if (flags == 5) else if (ud->cheevo_flags == 5)
cheevo = ud->patchdata->unofficial + ud->unofficial_count++; cheevo = ud->patchdata->unofficial + ud->unofficial_count++;
else ud->cheevo_flags = 0;
return 0;
cheevo->title = rcheevos_unescape_string(ud->title.string, ud->title.length); if (!cheevo
cheevo->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length); || !ud->cheevo.title
cheevo->badge = rcheevos_unescape_string(ud->badge.string, ud->badge.length); || !ud->cheevo.description
cheevo->memaddr = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length); || !ud->cheevo.badge
cheevo->points = (unsigned)strtol(ud->points.string, NULL, 10); || !ud->cheevo.memaddr)
cheevo->id = (unsigned)strtol(ud->id.string, NULL, 10);
if ( !cheevo->title
|| !cheevo->description
|| !cheevo->badge
|| !cheevo->memaddr)
{ {
CHEEVOS_FREE(cheevo->title); CHEEVOS_FREE(ud->cheevo.title);
CHEEVOS_FREE(cheevo->description); CHEEVOS_FREE(ud->cheevo.description);
CHEEVOS_FREE(cheevo->badge); CHEEVOS_FREE(ud->cheevo.badge);
CHEEVOS_FREE(cheevo->memaddr); CHEEVOS_FREE(ud->cheevo.memaddr);
return -1; memset(&ud->cheevo, 0, sizeof(ud->cheevo));
return (cheevo ? false : true);
} }
return 0; *cheevo = ud->cheevo;
memset(&ud->cheevo, 0, sizeof(ud->cheevo));
return true;
} }
static int rcheevos_new_lboard(rcheevos_readud_t* ud) static bool rcheevos_new_lboard(rcheevos_readud_t* ud)
{ {
rcheevos_ralboard_t* lboard = ud->patchdata->lboards + ud->lboard_count++; rcheevos_ralboard_t* lboard = NULL;
char format[32];
lboard->title = rcheevos_unescape_string(ud->title.string, ud->title.length); if (ud->lboard_had_id)
lboard->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length); lboard = ud->patchdata->lboards + ud->lboard_count++;
lboard->mem = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length); ud->lboard_had_id = 0;
lboard->id = (unsigned)strtol(ud->id.string, NULL, 10);
if ( !lboard->title if (!lboard
|| !lboard->description || !ud->lboard.title
|| !lboard->mem) || !ud->lboard.description
|| !ud->lboard.mem)
{ {
CHEEVOS_FREE(lboard->title); CHEEVOS_FREE(ud->lboard.title);
CHEEVOS_FREE(lboard->description); CHEEVOS_FREE(ud->lboard.description);
CHEEVOS_FREE(lboard->mem); CHEEVOS_FREE(ud->lboard.mem);
return -1; memset(&ud->lboard, 0, sizeof(ud->lboard));
CHEEVOS_FREE(ud->lboard_format);
ud->lboard_format = NULL;
return (lboard ? false : true);
} }
if (ud->format.length > 0 && ud->format.length < sizeof(format) - 1) *lboard = ud->lboard;
memset(&ud->lboard, 0, sizeof(ud->lboard));
if (ud->lboard_format)
{ {
memcpy(format, ud->format.string, ud->format.length); lboard->format = rc_parse_format(ud->lboard_format);
format[ud->format.length] = '\0'; CHEEVOS_FREE(ud->lboard_format);
lboard->format = rc_parse_format(format); ud->lboard_format = NULL;
} }
return 0; return true;
} }
static int rcheevos_read_end_object(void* userdata) static bool rcheevos_read_end_object(void* userdata)
{ {
rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
@ -446,26 +354,26 @@ static int rcheevos_read_end_object(void* userdata)
if (ud->in_lboards) if (ud->in_lboards)
return rcheevos_new_lboard(ud); return rcheevos_new_lboard(ud);
return 0; return true;
} }
static int rcheevos_read_end_array(void* userdata) static bool rcheevos_read_end_array(void* userdata)
{ {
rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
ud->in_cheevos = 0; ud->in_cheevos = 0;
ud->in_lboards = 0; ud->in_lboards = 0;
return 0; return true;
} }
static int rcheevos_read_key(void* userdata, static bool rcheevos_read_key(void* userdata,
const char* name, size_t length) const char* name, size_t length)
{ {
rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
int common = ud->in_cheevos || ud->in_lboards;
uint32_t hash = rcheevos_djb2(name, length); uint32_t hash = rcheevos_djb2(name, length);
ud->field = NULL; ud->field_unsigned = NULL;
ud->field_string = NULL;
switch (hash) switch (hash)
{ {
@ -476,145 +384,121 @@ static int rcheevos_read_key(void* userdata,
ud->in_lboards = 1; ud->in_lboards = 1;
break; break;
case CHEEVOS_JSON_KEY_CONSOLE_ID: case CHEEVOS_JSON_KEY_CONSOLE_ID:
ud->is_console_id = 1; ud->field_unsigned = &ud->patchdata->console_id;
break; break;
case CHEEVOS_JSON_KEY_RICHPRESENCE: case CHEEVOS_JSON_KEY_RICHPRESENCE:
ud->is_richpresence = 1; ud->field_string = (const char**)&ud->patchdata->richpresence_script;
break; break;
case CHEEVOS_JSON_KEY_ID: case CHEEVOS_JSON_KEY_ID:
if (common) if (ud->in_cheevos)
ud->field = &ud->id; ud->field_unsigned = &ud->cheevo.id;
else if (ud->in_lboards)
{
ud->field_unsigned = &ud->lboard.id;
ud->lboard_had_id = 1;
}
else else
ud->is_game_id = 1; ud->field_unsigned = &ud->patchdata->game_id;
break; break;
case CHEEVOS_JSON_KEY_MEMADDR: case CHEEVOS_JSON_KEY_MEMADDR:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->memaddr; ud->field_string = &ud->cheevo.memaddr;
break; break;
case CHEEVOS_JSON_KEY_MEM: case CHEEVOS_JSON_KEY_MEM:
if (ud->in_lboards) if (ud->in_lboards)
ud->field = &ud->memaddr; ud->field_string = &ud->lboard.mem;
break; break;
case CHEEVOS_JSON_KEY_TITLE: case CHEEVOS_JSON_KEY_TITLE:
if (common) if (ud->in_cheevos)
ud->field = &ud->title; ud->field_string = &ud->cheevo.title;
else if (ud->in_lboards)
ud->field_string = &ud->lboard.title;
else else
ud->is_title = 1; ud->field_string = (const char**)&ud->patchdata->title;
break; break;
case CHEEVOS_JSON_KEY_DESCRIPTION: case CHEEVOS_JSON_KEY_DESCRIPTION:
if (common) if (ud->in_cheevos)
ud->field = &ud->desc; ud->field_string = &ud->cheevo.description;
else if (ud->in_lboards)
ud->field_string = &ud->lboard.description;
break; break;
case CHEEVOS_JSON_KEY_POINTS: case CHEEVOS_JSON_KEY_POINTS:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->points; ud->field_unsigned = &ud->cheevo.points;
break; break;
/* UNUSED
case CHEEVOS_JSON_KEY_AUTHOR: case CHEEVOS_JSON_KEY_AUTHOR:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->author; ud->field_string = &ud->cheevo.author;
break; break;
case CHEEVOS_JSON_KEY_MODIFIED: case CHEEVOS_JSON_KEY_MODIFIED:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->modified; ud->field_string = &ud->cheevo.modified;
break; break;
case CHEEVOS_JSON_KEY_CREATED: case CHEEVOS_JSON_KEY_CREATED:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->created; ud->field_string = &ud->cheevo.created;
break; break; */
case CHEEVOS_JSON_KEY_BADGENAME: case CHEEVOS_JSON_KEY_BADGENAME:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->badge; ud->field_string = &ud->cheevo.badge;
break; break;
case CHEEVOS_JSON_KEY_FLAGS: case CHEEVOS_JSON_KEY_FLAGS:
if (ud->in_cheevos) if (ud->in_cheevos)
ud->field = &ud->flags; ud->field_unsigned = &ud->cheevo_flags;
break; break;
case CHEEVOS_JSON_KEY_FORMAT: case CHEEVOS_JSON_KEY_FORMAT:
if (ud->in_lboards) if (ud->in_lboards)
ud->field = &ud->format; ud->field_string = &ud->lboard_format;
break; break;
default: default:
break; break;
} }
return 0; return true;
} }
static int rcheevos_read_string(void* userdata, static bool rcheevos_read_string(void* userdata,
const char* string, size_t length) const char* string, size_t length)
{ {
rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
if (ud->field) if (ud->field_string)
{ {
ud->field->string = string; if (*ud->field_string)
ud->field->length = length; CHEEVOS_FREE((*ud->field_string));
} *ud->field_string = strdup(string);
else if (ud->is_title) ud->field_string = NULL;
{
ud->patchdata->title = rcheevos_unescape_string(string, length);
ud->is_title = 0;
}
else if (ud->is_richpresence)
{
ud->patchdata->richpresence_script = rcheevos_unescape_string(string, length);
ud->is_richpresence = 0;
} }
return 0; return true;
} }
static int rcheevos_read_number(void* userdata, static bool rcheevos_read_number(void* userdata,
const char* number, size_t length) const char* number, size_t length)
{ {
rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
if (ud->field) if (ud->field_unsigned)
{ {
ud->field->string = number; *ud->field_unsigned = (unsigned)strtol(number, NULL, 10);
ud->field->length = length; ud->field_unsigned = NULL;
}
else if (ud->is_game_id)
{
ud->patchdata->game_id = (unsigned)strtol(number, NULL, 10);
ud->is_game_id = 0;
}
else if (ud->is_console_id)
{
ud->patchdata->console_id = (unsigned)strtol(number, NULL, 10);
ud->is_console_id = 0;
} }
return 0; return true;
} }
int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata) int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata)
{ {
static const jsonsax_handlers_t handlers =
{
NULL,
NULL,
NULL,
rcheevos_read_end_object,
NULL,
rcheevos_read_end_array,
rcheevos_read_key,
NULL,
rcheevos_read_string,
rcheevos_read_number,
NULL,
NULL
};
rcheevos_readud_t ud; rcheevos_readud_t ud;
int res; bool res;
int has_error; int has_error;
/* Count the number of achievements in the JSON file. */ /* Count the number of achievements in the JSON file. */
res = rcheevos_count_cheevos(json, &patchdata->core_count, res = rcheevos_count_cheevos(json, &patchdata->core_count,
&patchdata->unofficial_count, &patchdata->lboard_count, &has_error); &patchdata->unofficial_count, &patchdata->lboard_count, &has_error);
if (res != JSONSAX_OK || has_error) if (!res || has_error)
return -1; return -1;
/* Allocate the achievements. */ /* Allocate the achievements. */
@ -643,19 +527,27 @@ int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata)
patchdata->title = NULL; patchdata->title = NULL;
/* Load the achievements. */ /* Load the achievements. */
ud.in_cheevos = 0; memset(&ud, 0, sizeof(ud));
ud.in_lboards = 0;
ud.is_game_id = 0;
ud.is_title = 0;
ud.is_console_id = 0;
ud.is_richpresence = 0;
ud.field = NULL;
ud.core_count = 0;
ud.unofficial_count = 0;
ud.lboard_count = 0;
ud.patchdata = patchdata; ud.patchdata = patchdata;
if (jsonsax_parse(json, &handlers, (void*)&ud) != JSONSAX_OK) res = rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
rcheevos_read_key,
rcheevos_read_string,
rcheevos_read_number,
NULL, rcheevos_read_end_object,
NULL, rcheevos_read_end_array,
NULL, NULL, NULL);
CHEEVOS_FREE(ud.cheevo.title);
CHEEVOS_FREE(ud.cheevo.description);
CHEEVOS_FREE(ud.cheevo.badge);
CHEEVOS_FREE(ud.cheevo.memaddr);
CHEEVOS_FREE(ud.lboard.title);
CHEEVOS_FREE(ud.lboard.description);
CHEEVOS_FREE(ud.lboard.mem);
CHEEVOS_FREE(ud.lboard_format);
if (!res)
{ {
rcheevos_free_patchdata(patchdata); rcheevos_free_patchdata(patchdata);
return -1; return -1;
@ -730,15 +622,23 @@ typedef struct
void* userdata; void* userdata;
} rcheevos_deactivate_t; } rcheevos_deactivate_t;
static int rcheevos_deactivate_index(void* userdata, unsigned int index) static bool rcheevos_deactivate_elements_begin(void* userdata)
{ {
rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata; rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata;
ud->is_element = 1; ud->is_element = 1;
return 0; return true;
} }
static int rcheevos_deactivate_number(void* userdata, static bool rcheevos_deactivate_elements_stop(void* userdata)
{
rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata;
ud->is_element = 0;
return true;
}
static bool rcheevos_deactivate_number(void* userdata,
const char* number, size_t length) const char* number, size_t length)
{ {
rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata; rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata;
@ -746,40 +646,30 @@ static int rcheevos_deactivate_number(void* userdata,
if (ud->is_element) if (ud->is_element)
{ {
ud->is_element = 0;
id = (unsigned)strtol(number, NULL, 10); id = (unsigned)strtol(number, NULL, 10);
ud->unlock_cb(id, ud->userdata); ud->unlock_cb(id, ud->userdata);
} }
return 0; return true;
} }
void rcheevos_deactivate_unlocks(const char* json, rcheevos_unlock_cb_t unlock_cb, void* userdata) void rcheevos_deactivate_unlocks(const char* json, rcheevos_unlock_cb_t unlock_cb, void* userdata)
{ {
static const jsonsax_handlers_t handlers =
{
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
rcheevos_deactivate_index,
NULL,
rcheevos_deactivate_number,
NULL,
NULL
};
rcheevos_deactivate_t ud; rcheevos_deactivate_t ud;
ud.is_element = 0; ud.is_element = 0;
ud.unlock_cb = unlock_cb; ud.unlock_cb = unlock_cb;
ud.userdata = userdata; ud.userdata = userdata;
jsonsax_parse(json, &handlers, (void*)&ud); rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
NULL, NULL,
rcheevos_deactivate_number,
rcheevos_deactivate_elements_stop,
rcheevos_deactivate_elements_stop,
rcheevos_deactivate_elements_begin,
rcheevos_deactivate_elements_stop,
NULL, NULL, NULL);
} }
/***************************************************************************** /*****************************************************************************

View File

@ -23,7 +23,7 @@
#include <file/file_path.h> #include <file/file_path.h>
#include <string/stdstring.h> #include <string/stdstring.h>
#include <streams/file_stream.h> #include <streams/file_stream.h>
#include <formats/jsonsax_full.h> #include <formats/rjson.h>
#include "file_path_special.h" #include "file_path_special.h"
#include "verbosity.h" #include "verbosity.h"
@ -37,25 +37,20 @@
typedef struct typedef struct
{ {
JSON_Parser parser;
JSON_Writer writer;
RFILE *file;
unsigned *current_entry_uint_val; unsigned *current_entry_uint_val;
char **current_entry_str_val; char **current_entry_str_val;
unsigned image_index; unsigned image_index;
char *image_path; char *image_path;
} DCifJSONContext; } DCifJSONContext;
static JSON_Parser_HandlerResult DCifJSONObjectMemberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) static bool DCifJSONObjectMemberHandler(void* context, const char *pValue, size_t length)
{ {
DCifJSONContext *pCtx = (DCifJSONContext*)JSON_Parser_GetUserData(parser); DCifJSONContext *pCtx = (DCifJSONContext*)context;
(void)attributes; /* unused */
if (pCtx->current_entry_str_val) if (pCtx->current_entry_str_val)
{ {
/* something went wrong */ /* something went wrong */
RARCH_ERR("[disk index file] JSON parsing failed at line %d.\n", __LINE__); return false;
return JSON_Parser_Abort;
} }
if (length) if (length)
@ -67,13 +62,12 @@ static JSON_Parser_HandlerResult DCifJSONObjectMemberHandler(JSON_Parser parser,
/* ignore unknown members */ /* ignore unknown members */
} }
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult DCifJSONNumberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) static bool DCifJSONNumberHandler(void* context, const char *pValue, size_t length)
{ {
DCifJSONContext *pCtx = (DCifJSONContext*)JSON_Parser_GetUserData(parser); DCifJSONContext *pCtx = (DCifJSONContext*)context;
(void)attributes; /* unused */
if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue)) if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue))
*pCtx->current_entry_uint_val = string_to_unsigned(pValue); *pCtx->current_entry_uint_val = string_to_unsigned(pValue);
@ -81,13 +75,12 @@ static JSON_Parser_HandlerResult DCifJSONNumberHandler(JSON_Parser parser, char
pCtx->current_entry_uint_val = NULL; pCtx->current_entry_uint_val = NULL;
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult DCifJSONStringHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) static bool DCifJSONStringHandler(void* context, const char *pValue, size_t length)
{ {
DCifJSONContext *pCtx = (DCifJSONContext*)JSON_Parser_GetUserData(parser); DCifJSONContext *pCtx = (DCifJSONContext*)context;
(void)attributes; /* unused */
if (pCtx->current_entry_str_val && length && !string_is_empty(pValue)) if (pCtx->current_entry_str_val && length && !string_is_empty(pValue))
{ {
@ -100,35 +93,7 @@ static JSON_Parser_HandlerResult DCifJSONStringHandler(JSON_Parser parser, char
pCtx->current_entry_str_val = NULL; pCtx->current_entry_str_val = NULL;
return JSON_Parser_Continue; return true;
}
static JSON_Writer_HandlerResult DCifJSONOutputHandler(JSON_Writer writer, const char *pBytes, size_t length)
{
DCifJSONContext *context = (DCifJSONContext*)JSON_Writer_GetUserData(writer);
(void)writer; /* unused */
return filestream_write(context->file, pBytes, length) == length ? JSON_Writer_Continue : JSON_Writer_Abort;
}
static void DCifJSONLogError(DCifJSONContext *pCtx)
{
if (pCtx->parser && JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler)
{
JSON_Error error = JSON_Parser_GetError(pCtx->parser);
JSON_Location errorLocation = { 0, 0, 0 };
(void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation);
RARCH_ERR("[disk index file] Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n",
(int)errorLocation.line + 1,
(int)errorLocation.column + 1,
(int)errorLocation.byte,
JSON_ErrorString(error));
}
else if (pCtx->writer && JSON_Writer_GetError(pCtx->writer) != JSON_Error_AbortedByHandler)
{
RARCH_ERR("[disk index file] Error: could not write output - %s.\n", JSON_ErrorString(JSON_Writer_GetError(pCtx->writer)));
}
} }
/******************/ /******************/
@ -156,6 +121,7 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file)
bool success = false; bool success = false;
DCifJSONContext context = {0}; DCifJSONContext context = {0};
RFILE *file = NULL; RFILE *file = NULL;
rjson_t* parser;
/* Sanity check */ /* Sanity check */
if (!disk_index_file) if (!disk_index_file)
@ -183,66 +149,44 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file)
} }
/* Initialise JSON parser */ /* Initialise JSON parser */
context.image_index = 0; parser = rjson_open_rfile(file);
context.image_path = NULL; if (!parser)
context.parser = JSON_Parser_Create(NULL);
context.file = file;
if (!context.parser)
{ {
RARCH_ERR("[disk index file] Failed to create JSON parser.\n"); RARCH_ERR("[disk index file] Failed to create JSON parser.\n");
goto end; goto end;
} }
/* Configure parser */ /* Configure parser */
JSON_Parser_SetAllowBOM(context.parser, JSON_True); rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
JSON_Parser_SetNumberHandler(context.parser, &DCifJSONNumberHandler);
JSON_Parser_SetStringHandler(context.parser, &DCifJSONStringHandler);
JSON_Parser_SetObjectMemberHandler(context.parser, &DCifJSONObjectMemberHandler);
JSON_Parser_SetUserData(context.parser, &context);
/* Read file */ /* Read file */
while (!filestream_eof(file)) if (rjson_parse(parser, &context,
DCifJSONObjectMemberHandler,
DCifJSONStringHandler,
DCifJSONNumberHandler,
NULL, NULL, NULL, NULL, /* unused object/array handlers */
NULL, NULL) /* unused boolean/null handlers */
!= RJSON_DONE)
{ {
/* Disk index files are tiny - use small chunk size */ if (rjson_get_source_context_len(parser))
char chunk[128] = {0};
int64_t length = filestream_read(file, chunk, sizeof(chunk));
/* Error checking... */
if (!length && !filestream_eof(file))
{ {
RARCH_ERR( RARCH_ERR(
"[disk index file] Failed to read disk index file: %s\n", "[disk index file] Error parsing chunk of disk index file: %s\n---snip---\n%.*s\n---snip---\n",
file_path); file_path,
JSON_Parser_Free(context.parser); rjson_get_source_context_len(parser),
goto end; rjson_get_source_context_buf(parser));
} }
/* Parse chunk */
if (!JSON_Parser_Parse(context.parser, chunk, (size_t)length, JSON_False))
{
RARCH_ERR(
"[disk index file] Error parsing chunk of disk index file: %s\n---snip---\n%s\n---snip---\n",
file_path, chunk);
DCifJSONLogError(&context);
JSON_Parser_Free(context.parser);
goto end;
}
}
/* Finalise parsing */
if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True))
{
RARCH_WARN( RARCH_WARN(
"[disk index file] Error parsing disk index file: %s\n", "[disk index file] Error parsing disk index file: %s\n",
file_path); file_path);
DCifJSONLogError(&context); RARCH_ERR("[disk index file] Error: Invalid JSON at line %d, column %d - %s.\n",
JSON_Parser_Free(context.parser); (int)rjson_get_source_line(parser),
goto end; (int)rjson_get_source_column(parser),
(*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
} }
/* Free parser */ /* Free parser */
JSON_Parser_Free(context.parser); rjson_free(parser);
/* Copy values read from JSON file */ /* Copy values read from JSON file */
disk_index_file->image_index = context.image_index; disk_index_file->image_index = context.image_index;
@ -406,15 +350,11 @@ void disk_index_file_set(
/* Saves specified disk index file to disk */ /* Saves specified disk index file to disk */
bool disk_index_file_save(disk_index_file_t *disk_index_file) bool disk_index_file_save(disk_index_file_t *disk_index_file)
{ {
int n;
char value_string[32];
const char *file_path; const char *file_path;
DCifJSONContext context = {0}; rjsonwriter_t* writer;
RFILE *file = NULL; RFILE *file = NULL;
bool success = false; bool success = false;
value_string[0] = '\0';
/* Sanity check */ /* Sanity check */
if (!disk_index_file) if (!disk_index_file)
return false; return false;
@ -450,69 +390,53 @@ bool disk_index_file_save(disk_index_file_t *disk_index_file)
} }
/* Initialise JSON writer */ /* Initialise JSON writer */
context.writer = JSON_Writer_Create(NULL); writer = rjsonwriter_open_rfile(file);
context.file = file;
if (!context.writer) if (!writer)
{ {
RARCH_ERR("[disk index file] Failed to create JSON writer.\n"); RARCH_ERR("[disk index file] Failed to create JSON writer.\n");
goto end; goto end;
} }
/* Configure JSON writer */
JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8);
JSON_Writer_SetOutputHandler(context.writer, &DCifJSONOutputHandler);
JSON_Writer_SetUserData(context.writer, &context);
/* Write output file */ /* Write output file */
JSON_Writer_WriteStartObject(context.writer); rjsonwriter_add_start_object(writer);
JSON_Writer_WriteNewLine(context.writer); rjsonwriter_add_newline(writer);
/* > Version entry */ /* > Version entry */
JSON_Writer_WriteSpace(context.writer, 2); rjsonwriter_add_spaces(writer, 2);
JSON_Writer_WriteString(context.writer, "version", rjsonwriter_add_string(writer, "version");
STRLEN_CONST("version"), JSON_UTF8); rjsonwriter_add_colon(writer);
JSON_Writer_WriteColon(context.writer); rjsonwriter_add_space(writer);
JSON_Writer_WriteSpace(context.writer, 1); rjsonwriter_add_string(writer, "1.0");
JSON_Writer_WriteString(context.writer, "1.0", rjsonwriter_add_comma(writer);
STRLEN_CONST("1.0"), JSON_UTF8); rjsonwriter_add_newline(writer);
JSON_Writer_WriteComma(context.writer);
JSON_Writer_WriteNewLine(context.writer);
/* > image index entry */ /* > image index entry */
n = snprintf( rjsonwriter_add_spaces(writer, 2);
value_string, sizeof(value_string), rjsonwriter_add_string(writer, "image_index");
"%u", disk_index_file->image_index); rjsonwriter_add_colon(writer);
if ((n < 0) || (n >= 32)) rjsonwriter_add_space(writer);
n = 0; /* Silence GCC warnings... */ rjsonwriter_add_unsigned(writer, disk_index_file->image_index);
rjsonwriter_add_comma(writer);
JSON_Writer_WriteSpace(context.writer, 2); rjsonwriter_add_newline(writer);
JSON_Writer_WriteString(context.writer, "image_index",
STRLEN_CONST("image_index"), JSON_UTF8);
JSON_Writer_WriteColon(context.writer);
JSON_Writer_WriteSpace(context.writer, 1);
JSON_Writer_WriteNumber(context.writer, value_string,
strlen(value_string), JSON_UTF8);
JSON_Writer_WriteComma(context.writer);
JSON_Writer_WriteNewLine(context.writer);
/* > image path entry */ /* > image path entry */
JSON_Writer_WriteSpace(context.writer, 2); rjsonwriter_add_spaces(writer, 2);
JSON_Writer_WriteString(context.writer, "image_path", rjsonwriter_add_string(writer, "image_path");
STRLEN_CONST("image_path"), JSON_UTF8); rjsonwriter_add_colon(writer);
JSON_Writer_WriteColon(context.writer); rjsonwriter_add_space(writer);
JSON_Writer_WriteSpace(context.writer, 1); rjsonwriter_add_string(writer, disk_index_file->image_path);
JSON_Writer_WriteString(context.writer, rjsonwriter_add_newline(writer);
disk_index_file->image_path,
strlen(disk_index_file->image_path), JSON_UTF8);
JSON_Writer_WriteNewLine(context.writer);
/* > Finalise */ /* > Finalise */
JSON_Writer_WriteEndObject(context.writer); rjsonwriter_add_end_object(writer);
JSON_Writer_WriteNewLine(context.writer); rjsonwriter_add_newline(writer);
/* Free JSON writer */ /* Free JSON writer */
JSON_Writer_Free(context.writer); if (!rjsonwriter_free(writer))
{
RARCH_ERR("[disk index file] Error writing disk index file: %s\n", file_path);
}
/* Changes have been written - record /* Changes have been written - record
* is no longer considered to be in a * is no longer considered to be in a

View File

@ -57,8 +57,6 @@
#endif #endif
#endif #endif
#define JSON_STATIC 1 /* must come before runtime_file, netplay_room_parse and jsonsax_full */
#if _MSC_VER && !defined(__WINRT__) #if _MSC_VER && !defined(__WINRT__)
#include "../libretro-common/compat/compat_snprintf.c" #include "../libretro-common/compat/compat_snprintf.c"
#endif #endif
@ -193,7 +191,6 @@ ACHIEVEMENTS
#include "../libretro-common/net/net_http.c" #include "../libretro-common/net/net_http.c"
#endif #endif
#include "../libretro-common/formats/json/jsonsax.c"
#include "../libretro-common/formats/cdfs/cdfs.c" #include "../libretro-common/formats/cdfs/cdfs.c"
#include "../network/net_http_special.c" #include "../network/net_http_special.c"
@ -1279,8 +1276,8 @@ THREAD
#include "../audio/audio_thread_wrapper.c" #include "../audio/audio_thread_wrapper.c"
#endif #endif
/* needed for both playlists and netplay lobbies */ /* needed for playlists, netplay lobbies and achievements */
#include "../libretro-common/formats/json/jsonsax_full.c" #include "../libretro-common/formats/json/rjson.c"
/*============================================================ /*============================================================
NETPLAY NETPLAY

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,263 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rjson.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_FORMAT_RJSON_H__
#define __LIBRETRO_SDK_FORMAT_RJSON_H__
#include <retro_common_api.h>
#include <retro_inline.h> /* INLINE */
#include <boolean.h> /* bool */
#include <stddef.h> /* size_t */
RETRO_BEGIN_DECLS
/* List of possible element types returned by rjson_next */
enum rjson_type
{
RJSON_DONE,
RJSON_OBJECT, RJSON_ARRAY, RJSON_OBJECT_END, RJSON_ARRAY_END,
RJSON_STRING, RJSON_NUMBER, RJSON_TRUE, RJSON_FALSE, RJSON_NULL,
RJSON_ERROR
};
/* Options that can be passed to rjson_set_options */
enum rjson_option
{
/* Allow UTF-8 byte order marks */
RJSON_OPTION_ALLOW_UTF8BOM = (1<<0),
/* Allow JavaScript style comments in the stream */
RJSON_OPTION_ALLOW_COMMENTS = (1<<1),
/* Allow unescaped control characters in strings (bytes 0x00 - 0x1F) */
RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS = (1<<2),
/* Ignore invalid Unicode escapes and don't validate UTF-8 codes */
RJSON_OPTION_IGNORE_INVALID_ENCODING = (1<<3),
/* Replace invalid Unicode escapes and UTF-8 codes with a '?' character */
RJSON_OPTION_REPLACE_INVALID_ENCODING = (1<<4),
/* Ignore carriage return (\r escape sequence) in strings */
RJSON_OPTION_IGNORE_STRING_CARRIAGE_RETURN = (1<<5),
/* Allow data after the end of the top JSON object/array/value */
RJSON_OPTION_ALLOW_TRAILING_DATA = (1<<6)
};
/* Custom data input callback
* Should return > 0 and <= len on success, 0 on file end and < 0 on error. */
typedef int (*rjson_io_t)(void* buf, int len, void *user_data);
typedef struct rjson rjson_t;
struct intfstream_internal;
struct RFILE;
/* Create a new parser instance from various sources */
rjson_t *rjson_open_stream(struct intfstream_internal *stream);
rjson_t *rjson_open_rfile(struct RFILE *rfile);
rjson_t *rjson_open_buffer(const void *buffer, size_t size);
rjson_t *rjson_open_string(const char *string);
rjson_t *rjson_open_user(rjson_io_t io, void *user_data, int io_block_size);
/* Free the parser instance created with rjson_open_* */
void rjson_free(rjson_t *json);
/* Set one or more enum rjson_option, will override previously set options.
* Use bitwise OR to concatenate multiple options.
* By default none of the options are set. */
void rjson_set_options(rjson_t *json, char rjson_option_flags);
/* Sets the maximum context depth, recursion inside arrays and objects.
* By default this is set to 50. */
void rjson_set_max_depth(rjson_t *json, unsigned int max_depth);
/* Parse to the next JSON element and return the type of it.
* Will return RJSON_DONE when successfully reaching the end or
* RJSON_ERROR when an error was encountered. */
enum rjson_type rjson_next(rjson_t *json);
/* Get the current string, null-terminated unescaped UTF-8 encoded.
* Can only be used when the current element is RJSON_STRING or RJSON_NUMBER.
* The returned pointer is only valid until the parsing continues. */
const char *rjson_get_string(rjson_t *json, size_t *length);
/* Returns the current number (or string) converted to double or int */
double rjson_get_double(rjson_t *json);
int rjson_get_int(rjson_t *json);
/* Returns a string describing the error once rjson_next/rjson_parse
* has returned an unrecoverable RJSON_ERROR (otherwise returns ""). */
const char *rjson_get_error(rjson_t *json);
/* Can be used to set a custom error description on an invalid JSON structure.
* Maximum length of 79 characters and once set the parsing can't continue. */
void rjson_set_error(rjson_t *json, const char* error);
/* Functions to get the current position in the source stream as well as */
/* a bit of source json arround the current position for additional detail
* when parsing has failed with RJSON_ERROR.
* Intended to be used with printf style formatting like:
* printf("Invalid JSON at line %d, column %d - %s - Source: ...%.*s...\n",
* (int)rjson_get_source_line(json), (int)rjson_get_source_column(json),
* rjson_get_error(json), rjson_get_source_context_len(json),
* rjson_get_source_context_buf(json)); */
size_t rjson_get_source_line(rjson_t *json);
size_t rjson_get_source_column(rjson_t *json);
int rjson_get_source_context_len(rjson_t *json);
const char* rjson_get_source_context_buf(rjson_t *json);
/* Confirm the parsing context stack, for example calling
rjson_check_context(json, 2, RJSON_OBJECT, RJSON_ARRAY)
returns true when inside "{ [ ..." but not for "[ .." or "{ [ { ..." */
bool rjson_check_context(rjson_t *json, unsigned int depth, ...);
/* Returns the current level of nested objects/arrays */
unsigned int rjson_get_context_depth(rjson_t *json);
/* Return the current parsing context, that is, RJSON_OBJECT if we are inside
* an object, RJSON_ARRAY if we are inside an array, and RJSON_DONE or
* RJSON_ERROR if we are not yet/anymore in either. */
enum rjson_type rjson_get_context_type(rjson_t *json);
/* While inside an object or an array, this return the number of parsing
* events that have already been observed at this level with rjson_next.
* In particular, inside an object, an odd number would indicate that the just
* observed RJSON_STRING event is a member name. */
size_t rjson_get_context_count(rjson_t *json);
/* Parse an entire JSON stream with a list of element specific handlers.
* Each of the handlers can be passed a function or NULL to ignore it.
* If a handler returns false, the parsing will abort and the returned
* rjson_type will indicate on which element type parsing was aborted.
* Otherwise the return value will be RJSON_DONE or RJSON_ERROR. */
enum rjson_type rjson_parse(rjson_t *json, void* context,
bool (*object_member_handler)(void *context, const char *str, size_t len),
bool (*string_handler )(void *context, const char *str, size_t len),
bool (*number_handler )(void *context, const char *str, size_t len),
bool (*start_object_handler )(void *context),
bool (*end_object_handler )(void *context),
bool (*start_array_handler )(void *context),
bool (*end_array_handler )(void *context),
bool (*boolean_handler )(void *context, bool value),
bool (*null_handler )(void *context));
/* A simpler interface to parse a JSON in memory. This will avoid any memory
* allocations unless the document contains strings longer than 512 characters.
* In the error handler, error will be "" if any of the other handlers aborted. */
bool rjson_parse_quick(const char *string, void* context, char option_flags,
bool (*object_member_handler)(void *context, const char *str, size_t len),
bool (*string_handler )(void *context, const char *str, size_t len),
bool (*number_handler )(void *context, const char *str, size_t len),
bool (*start_object_handler )(void *context),
bool (*end_object_handler )(void *context),
bool (*start_array_handler )(void *context),
bool (*end_array_handler )(void *context),
bool (*boolean_handler )(void *context, bool value),
bool (*null_handler )(void *context),
void (*error_handler )(void *context, int line, int col, const char* error));
/* ------------------------------------------------------------------------- */
/* Options that can be passed to rjsonwriter_set_options */
enum rjsonwriter_option
{
/* Don't write spaces, tabs or newlines to the output (except in strings) */
RJSONWRITER_OPTION_SKIP_WHITESPACE = (1<<0)
};
/* Custom data output callback
* Should return len on success and < len on a write error. */
typedef int (*rjsonwriter_io_t)(const void* buf, int len, void *user_data);
typedef struct rjsonwriter rjsonwriter_t;
/* Create a new writer instance to various targets */
rjsonwriter_t *rjsonwriter_open_stream(struct intfstream_internal *stream);
rjsonwriter_t *rjsonwriter_open_rfile(struct RFILE *rfile);
rjsonwriter_t *rjsonwriter_open_user(rjsonwriter_io_t io, void *user_data);
/* Free rjsonwriter handle and return result of final rjsonwriter_flush call */
bool rjsonwriter_free(rjsonwriter_t *writer);
/* Set one or more enum rjsonwriter_option, will override previously set options.
* Use bitwise OR to concatenate multiple options.
* By default none of the options are set. */
void rjsonwriter_set_options(rjsonwriter_t *writer, int rjsonwriter_option_flags);
/* Flush any buffered output data to the output stream.
* Returns true if the data was successfully written. Once writing fails once,
* no more data will be written and flush will always returns false */
bool rjsonwriter_flush(rjsonwriter_t *writer);
/* Returns a string describing an error or "" if there was none.
* The only error possible is "output error" after the io function failed.
* If rjsonwriter_rawf were used manually, "out of memory" is also possible. */
const char *rjsonwriter_get_error(rjsonwriter_t *writer);
/* Used by the inline functions below to append raw data */
void rjsonwriter_raw(rjsonwriter_t *writer, const char *buf, int len);
void rjsonwriter_rawf(rjsonwriter_t *writer, const char *fmt, ...);
/* Add a UTF-8 encoded string
* Special and control characters are automatically escaped.
* If NULL is passed an empty string will be written (not JSON null). */
void rjsonwriter_add_string(rjsonwriter_t *writer, const char *value);
/* Add a signed or unsigned integer or a double number */
static INLINE void rjsonwriter_add_int(rjsonwriter_t *writer, int value)
{ rjsonwriter_rawf(writer, "%d", value); }
static INLINE void rjsonwriter_add_unsigned(rjsonwriter_t *writer, unsigned value)
{ rjsonwriter_rawf(writer, "%u", value); }
void rjsonwriter_add_double(rjsonwriter_t *writer, double value);
/* Functions to add JSON token characters */
static INLINE void rjsonwriter_add_start_object(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, "{", 1); }
static INLINE void rjsonwriter_add_end_object(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, "}", 1); }
static INLINE void rjsonwriter_add_start_array(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, "[", 1); }
static INLINE void rjsonwriter_add_end_array(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, "]", 1); }
static INLINE void rjsonwriter_add_colon(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, ":", 1); }
static INLINE void rjsonwriter_add_comma(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, ",", 1); }
/* Functions to add whitespace characters */
/* These do nothing with the option RJSONWRITER_OPTION_SKIP_WHITESPACE */
static INLINE void rjsonwriter_add_newline(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, "\n", 1); }
static INLINE void rjsonwriter_add_space(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, " ", 1); }
void rjsonwriter_add_spaces(rjsonwriter_t *writer, int count);
static INLINE void rjsonwriter_add_tab(rjsonwriter_t *writer)
{ rjsonwriter_raw(writer, "\t", 1); }
void rjsonwriter_add_tabs(rjsonwriter_t *writer, int count);
RETRO_END_DECLS
#endif

View File

@ -21,11 +21,11 @@
#include <string.h> #include <string.h>
#include <string/stdstring.h> #include <string/stdstring.h>
#include <compat/strl.h> #include <compat/strl.h>
#include <formats/jsonsax_full.h> #include <formats/rjson.h>
#include "netplay_discovery.h" #include "netplay_discovery.h"
#include "../../verbosity.h" #include "../../verbosity.h"
enum parse_state enum netplay_parse_state
{ {
STATE_START = 0, STATE_START = 0,
STATE_ARRAY_START, STATE_ARRAY_START,
@ -41,113 +41,71 @@ struct netplay_rooms
struct netplay_room *cur; struct netplay_room *cur;
}; };
typedef struct tag_Context struct netplay_json_context
{ {
JSON_Parser parser; bool *cur_member_bool;
char *cur_field; int *cur_member_int;
void *cur_member; int *cur_member_inthex;
enum parse_state state; char *cur_member_string;
} Context; size_t cur_member_size;
enum netplay_parse_state state;
};
/* TODO/FIXME - static global variable */ /* TODO/FIXME - static global variable */
static struct netplay_rooms *netplay_rooms_data; static struct netplay_rooms *netplay_rooms_data;
static void parse_context_free(Context* pCtx) static bool netplay_json_boolean(void* ctx, bool value)
{ {
if (pCtx->cur_field) struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
free(pCtx->cur_field);
pCtx->cur_field = NULL;
JSON_Parser_Free(pCtx->parser);
}
static JSON_Parser_HandlerResult JSON_CALL EncodingDetectedHandler(
JSON_Parser parser)
{
(void)parser;
return JSON_Parser_Continue;
}
static JSON_Parser_HandlerResult JSON_CALL NullHandler(
JSON_Parser parser)
{
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
(void)parser;
(void)pCtx;
return JSON_Parser_Continue;
}
static JSON_Parser_HandlerResult JSON_CALL BooleanHandler(
JSON_Parser parser, JSON_Boolean value)
{
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
(void)parser;
if (pCtx->state == STATE_FIELDS_OBJECT_START) if (pCtx->state == STATE_FIELDS_OBJECT_START)
if (pCtx->cur_field) if (pCtx->cur_member_bool)
*((bool*)pCtx->cur_member) = value; *pCtx->cur_member_bool = value;
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL StringHandler( static bool netplay_json_string(void* ctx, const char* pValue, size_t length)
JSON_Parser parser, char* pValue, size_t length,
JSON_StringAttributes attributes)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
(void)parser;
(void)attributes;
if (pCtx->state == STATE_FIELDS_OBJECT_START) if (pCtx->state == STATE_FIELDS_OBJECT_START)
{ {
if (pValue && length) if (pValue && length)
{ {
if (pCtx->cur_field) if (pCtx->cur_member_inthex)
{ {
/* CRC comes in as a string but it is stored /* CRC comes in as a string but it is stored
* as an unsigned casted to int. */ * as an unsigned casted to int. */
if (string_is_equal(pCtx->cur_field, "game_crc")) *pCtx->cur_member_inthex = (int)strtoul(pValue, NULL, 16);
*((int*)pCtx->cur_member) = (int)strtoul(pValue, NULL, 16); }
else if (pCtx->cur_member_string)
strlcpy((char*)pCtx->cur_member, pValue, PATH_MAX_LENGTH); {
strlcpy(pCtx->cur_member_string, pValue, pCtx->cur_member_size);
} }
} }
} }
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL NumberHandler( static bool netplay_json_number(void* ctx, const char* pValue, size_t length)
JSON_Parser parser, char* pValue, size_t length, JSON_NumberAttributes attributes)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
(void)parser;
(void)attributes;
if (pCtx->state == STATE_FIELDS_OBJECT_START) if (pCtx->state == STATE_FIELDS_OBJECT_START)
{ {
if (pValue && length) if (pValue && length)
if (pCtx->cur_field) if (pCtx->cur_member_int)
*((int*)pCtx->cur_member) = (int)strtol(pValue, NULL, 10); *pCtx->cur_member_int = (int)strtol(pValue, NULL, 10);
} }
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL SpecialNumberHandler( static bool netplay_json_start_object(void* ctx)
JSON_Parser parser, JSON_SpecialNumber value)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
(void)parser;
(void)pCtx;
return JSON_Parser_Continue;
}
static JSON_Parser_HandlerResult JSON_CALL StartObjectHandler(JSON_Parser parser)
{
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
(void)parser;
if (pCtx->state == STATE_FIELDS_START) if (pCtx->state == STATE_FIELDS_START)
{ {
@ -167,28 +125,25 @@ static JSON_Parser_HandlerResult JSON_CALL StartObjectHandler(JSON_Parser parser
else if (pCtx->state == STATE_ARRAY_START) else if (pCtx->state == STATE_ARRAY_START)
pCtx->state = STATE_OBJECT_START; pCtx->state = STATE_OBJECT_START;
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL EndObjectHandler(JSON_Parser parser) static bool netplay_json_end_object(void* ctx)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
if (pCtx->state == STATE_FIELDS_OBJECT_START) if (pCtx->state == STATE_FIELDS_OBJECT_START)
pCtx->state = STATE_ARRAY_START; pCtx->state = STATE_ARRAY_START;
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL ObjectMemberHandler(JSON_Parser parser, static bool netplay_json_object_member(void* ctx, const char* pValue, size_t length)
char* pValue, size_t length, JSON_StringAttributes attributes)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
(void)parser;
(void)attributes;
if (!pValue || !length) if (!pValue || !length)
return JSON_Parser_Continue; return true;
if (pCtx->state == STATE_OBJECT_START && !string_is_empty(pValue) if (pCtx->state == STATE_OBJECT_START && !string_is_empty(pValue)
&& string_is_equal(pValue, "fields")) && string_is_equal(pValue, "fields"))
@ -196,176 +151,111 @@ static JSON_Parser_HandlerResult JSON_CALL ObjectMemberHandler(JSON_Parser parse
if (pCtx->state == STATE_FIELDS_OBJECT_START) if (pCtx->state == STATE_FIELDS_OBJECT_START)
{ {
if (pCtx->cur_field) pCtx->cur_member_bool = NULL;
free(pCtx->cur_field); pCtx->cur_member_int = NULL;
pCtx->cur_field = NULL; pCtx->cur_member_inthex = NULL;
pCtx->cur_member_string = NULL;
if (!string_is_empty(pValue)) if (!string_is_empty(pValue))
{ {
if (string_is_equal(pValue, "username")) if (string_is_equal(pValue, "username"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->nickname;
pCtx->cur_member = &netplay_rooms_data->cur->nickname; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->nickname);
} }
else if (string_is_equal(pValue, "game_name")) else if (string_is_equal(pValue, "game_name"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->gamename;
pCtx->cur_member = &netplay_rooms_data->cur->gamename; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->gamename);
} }
else if (string_is_equal(pValue, "core_name")) else if (string_is_equal(pValue, "core_name"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->corename;
pCtx->cur_member = &netplay_rooms_data->cur->corename; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->corename);
} }
else if (string_is_equal(pValue, "ip")) else if (string_is_equal(pValue, "ip"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->address;
pCtx->cur_member = &netplay_rooms_data->cur->address; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->address);
} }
else if (string_is_equal(pValue, "port")) else if (string_is_equal(pValue, "port"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_int = &netplay_rooms_data->cur->port;
pCtx->cur_member = &netplay_rooms_data->cur->port;
} }
else if (string_is_equal(pValue, "game_crc")) else if (string_is_equal(pValue, "game_crc"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_inthex = &netplay_rooms_data->cur->gamecrc;
pCtx->cur_member = &netplay_rooms_data->cur->gamecrc;
} }
else if (string_is_equal(pValue, "core_version")) else if (string_is_equal(pValue, "core_version"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->coreversion;
pCtx->cur_member = &netplay_rooms_data->cur->coreversion; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->coreversion);
} }
else if (string_is_equal(pValue, "has_password")) else if (string_is_equal(pValue, "has_password"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_bool = &netplay_rooms_data->cur->has_password;
pCtx->cur_member = &netplay_rooms_data->cur->has_password;
} }
else if (string_is_equal(pValue, "has_spectate_password")) else if (string_is_equal(pValue, "has_spectate_password"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_bool = &netplay_rooms_data->cur->has_spectate_password;
pCtx->cur_member = &netplay_rooms_data->cur->has_spectate_password;
} }
else if (string_is_equal(pValue, "fixed")) else if (string_is_equal(pValue, "fixed"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_bool = &netplay_rooms_data->cur->fixed;
pCtx->cur_member = &netplay_rooms_data->cur->fixed;
} }
else if (string_is_equal(pValue, "mitm_ip")) else if (string_is_equal(pValue, "mitm_ip"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->mitm_address;
pCtx->cur_member = &netplay_rooms_data->cur->mitm_address; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->mitm_address);
} }
else if (string_is_equal(pValue, "mitm_port")) else if (string_is_equal(pValue, "mitm_port"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_int = &netplay_rooms_data->cur->mitm_port;
pCtx->cur_member = &netplay_rooms_data->cur->mitm_port;
} }
else if (string_is_equal(pValue, "host_method")) else if (string_is_equal(pValue, "host_method"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_int = &netplay_rooms_data->cur->host_method;
pCtx->cur_member = &netplay_rooms_data->cur->host_method;
} }
else if (string_is_equal(pValue, "retroarch_version")) else if (string_is_equal(pValue, "retroarch_version"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->retroarch_version;
pCtx->cur_member = &netplay_rooms_data->cur->retroarch_version; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->retroarch_version);
} }
else if (string_is_equal(pValue, "country")) else if (string_is_equal(pValue, "country"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->country;
pCtx->cur_member = &netplay_rooms_data->cur->country; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->country);
} }
else if (string_is_equal(pValue, "frontend")) else if (string_is_equal(pValue, "frontend"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->frontend;
pCtx->cur_member = &netplay_rooms_data->cur->frontend; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->frontend);
} }
else if (string_is_equal(pValue, "subsystem_name")) else if (string_is_equal(pValue, "subsystem_name"))
{ {
pCtx->cur_field = strdup(pValue); pCtx->cur_member_string = netplay_rooms_data->cur->subsystem_name;
pCtx->cur_member = &netplay_rooms_data->cur->subsystem_name; pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->subsystem_name);
} }
} }
} }
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL StartArrayHandler(JSON_Parser parser) static bool netplay_json_start_array(void* ctx)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
(void)parser;
if (pCtx->state == STATE_START) if (pCtx->state == STATE_START)
pCtx->state = STATE_ARRAY_START; pCtx->state = STATE_ARRAY_START;
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult JSON_CALL EndArrayHandler(JSON_Parser parser) static void netplay_rooms_error(void *context, int line, int col, const char* error)
{ {
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); RARCH_ERR("[netplay] Error: Invalid JSON at line %d, column %d - %s.\n",
(void)parser; line, col, error);
(void)pCtx;
return JSON_Parser_Continue;
}
static JSON_Parser_HandlerResult JSON_CALL ArrayItemHandler(JSON_Parser parser)
{
Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
(void)parser;
(void)pCtx;
return JSON_Parser_Continue;
}
static void parse_context_setup(Context* pCtx)
{
if (JSON_Parser_GetInputEncoding(pCtx->parser) == JSON_UnknownEncoding)
JSON_Parser_SetEncodingDetectedHandler(pCtx->parser,
&EncodingDetectedHandler);
JSON_Parser_SetNullHandler(pCtx->parser, &NullHandler);
JSON_Parser_SetBooleanHandler(pCtx->parser, &BooleanHandler);
JSON_Parser_SetStringHandler(pCtx->parser, &StringHandler);
JSON_Parser_SetNumberHandler(pCtx->parser, &NumberHandler);
JSON_Parser_SetSpecialNumberHandler(pCtx->parser, &SpecialNumberHandler);
JSON_Parser_SetStartObjectHandler(pCtx->parser, &StartObjectHandler);
JSON_Parser_SetEndObjectHandler(pCtx->parser, &EndObjectHandler);
JSON_Parser_SetObjectMemberHandler(pCtx->parser, &ObjectMemberHandler);
JSON_Parser_SetStartArrayHandler(pCtx->parser, &StartArrayHandler);
JSON_Parser_SetEndArrayHandler(pCtx->parser, &EndArrayHandler);
JSON_Parser_SetArrayItemHandler(pCtx->parser, &ArrayItemHandler);
JSON_Parser_SetUserData(pCtx->parser, pCtx);
}
static void parse_context_error(Context* pCtx)
{
if (JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler)
{
JSON_Error error = JSON_Parser_GetError(pCtx->parser);
JSON_Location errorLocation = {0, 0, 0};
(void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation);
RARCH_ERR("invalid JSON at line %d, column %d (input byte %d) - %s.\n",
(int)errorLocation.line + 1,
(int)errorLocation.column + 1,
(int)errorLocation.byte,
JSON_ErrorString(error));
}
}
static int json_parse(Context* pCtx, const char *buf)
{
if (!JSON_Parser_Parse(pCtx->parser, buf, strlen(buf), JSON_True))
{
parse_context_error(pCtx);
return 0;
}
return 1;
} }
void netplay_rooms_free(void) void netplay_rooms_free(void)
@ -392,7 +282,7 @@ void netplay_rooms_free(void)
int netplay_rooms_parse(const char *buf) int netplay_rooms_parse(const char *buf)
{ {
Context ctx; struct netplay_json_context ctx;
memset(&ctx, 0, sizeof(ctx)); memset(&ctx, 0, sizeof(ctx));
@ -404,17 +294,17 @@ int netplay_rooms_parse(const char *buf)
netplay_rooms_data = (struct netplay_rooms*) netplay_rooms_data = (struct netplay_rooms*)
calloc(1, sizeof(*netplay_rooms_data)); calloc(1, sizeof(*netplay_rooms_data));
ctx.parser = JSON_Parser_Create(NULL); rjson_parse_quick(buf, &ctx, 0,
netplay_json_object_member,
if (!ctx.parser) netplay_json_string,
{ netplay_json_number,
RARCH_ERR("could not allocate memory for JSON parser.\n"); netplay_json_start_object,
return 1; netplay_json_end_object,
} netplay_json_start_array,
NULL /* end_array_handler */,
parse_context_setup(&ctx); netplay_json_boolean,
json_parse(&ctx, buf); NULL /* null handler */,
parse_context_free(&ctx); netplay_rooms_error);
return 0; return 0;
} }

View File

@ -147,7 +147,7 @@
92B9EB8024E0518700E6CFB2 /* net_http_parse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = net_http_parse.h; sourceTree = "<group>"; }; 92B9EB8024E0518700E6CFB2 /* net_http_parse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = net_http_parse.h; sourceTree = "<group>"; };
92B9EB8124E0518700E6CFB2 /* retro_timers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = retro_timers.h; sourceTree = "<group>"; }; 92B9EB8124E0518700E6CFB2 /* retro_timers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = retro_timers.h; sourceTree = "<group>"; };
92B9EB8324E0518700E6CFB2 /* m3u_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m3u_file.h; sourceTree = "<group>"; }; 92B9EB8324E0518700E6CFB2 /* m3u_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m3u_file.h; sourceTree = "<group>"; };
92B9EB8424E0518700E6CFB2 /* jsonsax.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jsonsax.h; sourceTree = "<group>"; }; 92B9EB8424E0518700E6CFB2 /* rjson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjson.h; sourceTree = "<group>"; };
92B9EB8524E0518700E6CFB2 /* rxml.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rxml.h; sourceTree = "<group>"; }; 92B9EB8524E0518700E6CFB2 /* rxml.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rxml.h; sourceTree = "<group>"; };
92B9EB8624E0518700E6CFB2 /* cdfs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cdfs.h; sourceTree = "<group>"; }; 92B9EB8624E0518700E6CFB2 /* cdfs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cdfs.h; sourceTree = "<group>"; };
92B9EB8724E0518700E6CFB2 /* rpng.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rpng.h; sourceTree = "<group>"; }; 92B9EB8724E0518700E6CFB2 /* rpng.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rpng.h; sourceTree = "<group>"; };
@ -156,7 +156,6 @@
92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = logiqx_dat.h; sourceTree = "<group>"; }; 92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = logiqx_dat.h; sourceTree = "<group>"; };
92B9EB8B24E0518700E6CFB2 /* rbmp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rbmp.h; sourceTree = "<group>"; }; 92B9EB8B24E0518700E6CFB2 /* rbmp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rbmp.h; sourceTree = "<group>"; };
92B9EB8C24E0518700E6CFB2 /* rwav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rwav.h; sourceTree = "<group>"; }; 92B9EB8C24E0518700E6CFB2 /* rwav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rwav.h; sourceTree = "<group>"; };
92B9EB8D24E0518700E6CFB2 /* jsonsax_full.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jsonsax_full.h; sourceTree = "<group>"; };
92B9EB8E24E0518700E6CFB2 /* rjpeg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjpeg.h; sourceTree = "<group>"; }; 92B9EB8E24E0518700E6CFB2 /* rjpeg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjpeg.h; sourceTree = "<group>"; };
92B9EB9024E0518700E6CFB2 /* rglgen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rglgen.h; sourceTree = "<group>"; }; 92B9EB9024E0518700E6CFB2 /* rglgen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rglgen.h; sourceTree = "<group>"; };
92B9EB9124E0518700E6CFB2 /* glsym_es2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glsym_es2.h; sourceTree = "<group>"; }; 92B9EB9124E0518700E6CFB2 /* glsym_es2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glsym_es2.h; sourceTree = "<group>"; };
@ -580,7 +579,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
92B9EB8324E0518700E6CFB2 /* m3u_file.h */, 92B9EB8324E0518700E6CFB2 /* m3u_file.h */,
92B9EB8424E0518700E6CFB2 /* jsonsax.h */, 92B9EB8424E0518700E6CFB2 /* rjson.h */,
92B9EB8524E0518700E6CFB2 /* rxml.h */, 92B9EB8524E0518700E6CFB2 /* rxml.h */,
92B9EB8624E0518700E6CFB2 /* cdfs.h */, 92B9EB8624E0518700E6CFB2 /* cdfs.h */,
92B9EB8724E0518700E6CFB2 /* rpng.h */, 92B9EB8724E0518700E6CFB2 /* rpng.h */,
@ -589,7 +588,6 @@
92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */, 92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */,
92B9EB8B24E0518700E6CFB2 /* rbmp.h */, 92B9EB8B24E0518700E6CFB2 /* rbmp.h */,
92B9EB8C24E0518700E6CFB2 /* rwav.h */, 92B9EB8C24E0518700E6CFB2 /* rwav.h */,
92B9EB8D24E0518700E6CFB2 /* jsonsax_full.h */,
92B9EB8E24E0518700E6CFB2 /* rjpeg.h */, 92B9EB8E24E0518700E6CFB2 /* rjpeg.h */,
); );
path = formats; path = formats;

1180
playlist.c

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@
#include <file/file_path.h> #include <file/file_path.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include <streams/file_stream.h> #include <streams/file_stream.h>
#include <formats/jsonsax_full.h> #include <formats/rjson.h>
#include <string/stdstring.h> #include <string/stdstring.h>
#include <encodings/utf.h> #include <encodings/utf.h>
#include <time/rtime.h> #include <time/rtime.h>
@ -53,27 +53,19 @@
typedef struct typedef struct
{ {
JSON_Parser parser;
JSON_Writer writer;
RFILE *file;
char **current_entry_val; char **current_entry_val;
char *runtime_string; char *runtime_string;
char *last_played_string; char *last_played_string;
} RtlJSONContext; } RtlJSONContext;
static JSON_Parser_HandlerResult RtlJSONObjectMemberHandler( static bool RtlJSONObjectMemberHandler(void *ctx, const char *s, size_t len)
JSON_Parser parser,
char *s, size_t len,
JSON_StringAttributes attributes)
{ {
RtlJSONContext *p_ctx = (RtlJSONContext*)JSON_Parser_GetUserData(parser); RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
(void)attributes; /* unused */
if (p_ctx->current_entry_val) if (p_ctx->current_entry_val)
{ {
/* something went wrong */ /* something went wrong */
RARCH_ERR("JSON parsing failed at line %d.\n", __LINE__); return false;
return JSON_Parser_Abort;
} }
if (len) if (len)
@ -85,15 +77,12 @@ static JSON_Parser_HandlerResult RtlJSONObjectMemberHandler(
/* ignore unknown members */ /* ignore unknown members */
} }
return JSON_Parser_Continue; return true;
} }
static JSON_Parser_HandlerResult RtlJSONStringHandler( static bool RtlJSONStringHandler(void *ctx, const char *s, size_t len)
JSON_Parser parser,
char *s, size_t len, JSON_StringAttributes attributes)
{ {
RtlJSONContext *p_ctx = (RtlJSONContext*)JSON_Parser_GetUserData(parser); RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
(void)attributes; /* unused */
if (p_ctx->current_entry_val && len && !string_is_empty(s)) if (p_ctx->current_entry_val && len && !string_is_empty(s))
{ {
@ -106,42 +95,7 @@ static JSON_Parser_HandlerResult RtlJSONStringHandler(
p_ctx->current_entry_val = NULL; p_ctx->current_entry_val = NULL;
return JSON_Parser_Continue; return true;
}
static JSON_Writer_HandlerResult RtlJSONOutputHandler(
JSON_Writer writer, const char *pBytes, size_t length)
{
RtlJSONContext *context = (RtlJSONContext*)JSON_Writer_GetUserData(writer);
(void)writer; /* unused */
return filestream_write(context->file, pBytes, length) == length
? JSON_Writer_Continue
: JSON_Writer_Abort;
}
static void RtlJSONLogError(RtlJSONContext *p_ctx)
{
if (p_ctx->parser && JSON_Parser_GetError(p_ctx->parser)
!= JSON_Error_AbortedByHandler)
{
JSON_Error error = JSON_Parser_GetError(p_ctx->parser);
JSON_Location errorLocation = { 0, 0, 0 };
(void)JSON_Parser_GetErrorLocation(p_ctx->parser, &errorLocation);
RARCH_ERR(
"Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n",
(int)errorLocation.line + 1,
(int)errorLocation.column + 1,
(int)errorLocation.byte,
JSON_ErrorString(error));
}
else if (p_ctx->writer && JSON_Writer_GetError(p_ctx->writer)
!= JSON_Error_AbortedByHandler)
{
RARCH_ERR("Error: could not write output - %s.\n",
JSON_ErrorString(JSON_Writer_GetError(p_ctx->writer)));
}
} }
/* Initialisation */ /* Initialisation */
@ -162,6 +116,8 @@ static void runtime_log_read_file(runtime_log_t *runtime_log)
unsigned last_played_second = 0; unsigned last_played_second = 0;
RtlJSONContext context = {0}; RtlJSONContext context = {0};
rjson_t* parser;
/* Attempt to open log file */ /* Attempt to open log file */
RFILE *file = filestream_open(runtime_log->path, RFILE *file = filestream_open(runtime_log->path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
@ -173,61 +129,41 @@ static void runtime_log_read_file(runtime_log_t *runtime_log)
} }
/* Initialise JSON parser */ /* Initialise JSON parser */
context.runtime_string = NULL; parser = rjson_open_rfile(file);
context.last_played_string = NULL; if (!parser)
context.parser = JSON_Parser_Create(NULL);
context.file = file;
if (!context.parser)
{ {
RARCH_ERR("Failed to create JSON parser.\n"); RARCH_ERR("Failed to create JSON parser.\n");
goto end; goto end;
} }
/* Configure parser */ /* Configure parser */
JSON_Parser_SetAllowBOM(context.parser, JSON_True); rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
JSON_Parser_SetStringHandler(context.parser, &RtlJSONStringHandler);
JSON_Parser_SetObjectMemberHandler(context.parser,
&RtlJSONObjectMemberHandler);
JSON_Parser_SetUserData(context.parser, &context);
/* Read file */ /* Read file */
while (!filestream_eof(file)) if (rjson_parse(parser, &context,
RtlJSONObjectMemberHandler,
RtlJSONStringHandler,
NULL, /* unused number handler */
NULL, NULL, NULL, NULL, /* unused object/array handlers */
NULL, NULL) /* unused boolean/null handlers */
!= RJSON_DONE)
{ {
/* Runtime log files are tiny - use small chunk size */ if (rjson_get_source_context_len(parser))
char chunk[128] = {0};
int64_t length = filestream_read(file, chunk, sizeof(chunk));
/* Error checking... */
if (!length && !filestream_eof(file))
{ {
RARCH_ERR("Failed to read runtime log file: %s\n", runtime_log->path); RARCH_ERR("Error parsing chunk of runtime log file: %s\n---snip---\n%.*s\n---snip---\n",
JSON_Parser_Free(context.parser); runtime_log->path,
goto end; rjson_get_source_context_len(parser),
rjson_get_source_context_buf(parser));
} }
/* Parse chunk */
if (!JSON_Parser_Parse(context.parser, chunk,
(size_t)length, JSON_False))
{
RARCH_ERR("Error parsing chunk of runtime log file: %s\n---snip---\n%s\n---snip---\n", runtime_log->path, chunk);
RtlJSONLogError(&context);
JSON_Parser_Free(context.parser);
goto end;
}
}
/* Finalise parsing */
if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True))
{
RARCH_WARN("Error parsing runtime log file: %s\n", runtime_log->path); RARCH_WARN("Error parsing runtime log file: %s\n", runtime_log->path);
RtlJSONLogError(&context); RARCH_ERR("Error: Invalid JSON at line %d, column %d - %s.\n",
JSON_Parser_Free(context.parser); (int)rjson_get_source_line(parser),
goto end; (int)rjson_get_source_column(parser),
(*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
} }
/* Free parser */ /* Free parser */
JSON_Parser_Free(context.parser); rjson_free(parser);
/* Process string values read from JSON file */ /* Process string values read from JSON file */
@ -1217,8 +1153,8 @@ void runtime_log_save(runtime_log_t *runtime_log)
int n; int n;
char value_string[64]; /* 64 characters should be char value_string[64]; /* 64 characters should be
enough for a very long runtime... :) */ enough for a very long runtime... :) */
RtlJSONContext context = {0};
RFILE *file = NULL; RFILE *file = NULL;
rjsonwriter_t* writer;
if (!runtime_log) if (!runtime_log)
return; return;
@ -1236,34 +1172,25 @@ void runtime_log_save(runtime_log_t *runtime_log)
} }
/* Initialise JSON writer */ /* Initialise JSON writer */
context.writer = JSON_Writer_Create(NULL); writer = rjsonwriter_open_rfile(file);
context.file = file; if (!writer)
if (!context.writer)
{ {
RARCH_ERR("Failed to create JSON writer.\n"); RARCH_ERR("Failed to create JSON writer.\n");
goto end; goto end;
} }
/* Configure JSON writer */
JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8);
JSON_Writer_SetOutputHandler(context.writer, &RtlJSONOutputHandler);
JSON_Writer_SetUserData(context.writer, &context);
/* Write output file */ /* Write output file */
JSON_Writer_WriteStartObject(context.writer); rjsonwriter_add_start_object(writer);
JSON_Writer_WriteNewLine(context.writer); rjsonwriter_add_newline(writer);
/* > Version entry */ /* > Version entry */
JSON_Writer_WriteSpace(context.writer, 2); rjsonwriter_add_spaces(writer, 2);
JSON_Writer_WriteString(context.writer, "version", rjsonwriter_add_string(writer, "version");
STRLEN_CONST("version"), JSON_UTF8); rjsonwriter_add_colon(writer);
JSON_Writer_WriteColon(context.writer); rjsonwriter_add_space(writer);
JSON_Writer_WriteSpace(context.writer, 1); rjsonwriter_add_string(writer, "1.0");
JSON_Writer_WriteString(context.writer, "1.0", rjsonwriter_add_comma(writer);
STRLEN_CONST("1.0"), JSON_UTF8); rjsonwriter_add_newline(writer);
JSON_Writer_WriteComma(context.writer);
JSON_Writer_WriteNewLine(context.writer);
/* > Runtime entry */ /* > Runtime entry */
value_string[0] = '\0'; value_string[0] = '\0';
@ -1274,15 +1201,13 @@ void runtime_log_save(runtime_log_t *runtime_log)
if ((n < 0) || (n >= 64)) if ((n < 0) || (n >= 64))
n = 0; /* Silence GCC warnings... */ n = 0; /* Silence GCC warnings... */
JSON_Writer_WriteSpace(context.writer, 2); rjsonwriter_add_spaces(writer, 2);
JSON_Writer_WriteString(context.writer, "runtime", rjsonwriter_add_string(writer, "runtime");
STRLEN_CONST("runtime"), JSON_UTF8); rjsonwriter_add_colon(writer);
JSON_Writer_WriteColon(context.writer); rjsonwriter_add_space(writer);
JSON_Writer_WriteSpace(context.writer, 1); rjsonwriter_add_string(writer, value_string);
JSON_Writer_WriteString(context.writer, value_string, rjsonwriter_add_comma(writer);
strlen(value_string), JSON_UTF8); rjsonwriter_add_newline(writer);
JSON_Writer_WriteComma(context.writer);
JSON_Writer_WriteNewLine(context.writer);
/* > Last played entry */ /* > Last played entry */
value_string[0] = '\0'; value_string[0] = '\0';
@ -1295,21 +1220,22 @@ void runtime_log_save(runtime_log_t *runtime_log)
if ((n < 0) || (n >= 64)) if ((n < 0) || (n >= 64))
n = 0; /* Silence GCC warnings... */ n = 0; /* Silence GCC warnings... */
JSON_Writer_WriteSpace(context.writer, 2); rjsonwriter_add_spaces(writer, 2);
JSON_Writer_WriteString(context.writer, "last_played", rjsonwriter_add_string(writer, "last_played");
STRLEN_CONST("last_played"), JSON_UTF8); rjsonwriter_add_colon(writer);
JSON_Writer_WriteColon(context.writer); rjsonwriter_add_space(writer);
JSON_Writer_WriteSpace(context.writer, 1); rjsonwriter_add_string(writer, value_string);
JSON_Writer_WriteString(context.writer, value_string, rjsonwriter_add_newline(writer);
strlen(value_string), JSON_UTF8);
JSON_Writer_WriteNewLine(context.writer);
/* > Finalise */ /* > Finalise */
JSON_Writer_WriteEndObject(context.writer); rjsonwriter_add_end_object(writer);
JSON_Writer_WriteNewLine(context.writer); rjsonwriter_add_newline(writer);
/* Free JSON writer */ /* Free JSON writer */
JSON_Writer_Free(context.writer); if (!rjsonwriter_free(writer))
{
RARCH_ERR("Error writing runtime log file: %s\n", runtime_log->path);
}
end: end:
/* Close log file */ /* Close log file */

View File

@ -114,8 +114,7 @@ SOURCES_C := \
$(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \ $(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \ $(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
$(LIBRETRO_COMM_DIR)/formats/json/jsonsax.c \ $(LIBRETRO_COMM_DIR)/formats/json/rjson.c \
$(LIBRETRO_COMM_DIR)/formats/json/jsonsax_full.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/queues/task_queue.c \ $(LIBRETRO_COMM_DIR)/queues/task_queue.c \