diff --git a/Makefile.common b/Makefile.common index 79c86feb2c..22fa99f811 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1963,8 +1963,7 @@ ifeq ($(HAVE_RBMP), 1) endif OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \ - $(LIBRETRO_COMM_DIR)/formats/json/jsonsax.o \ - $(LIBRETRO_COMM_DIR)/formats/json/jsonsax_full.o \ + $(LIBRETRO_COMM_DIR)/formats/json/rjson.o \ $(LIBRETRO_COMM_DIR)/formats/image_transfer.o \ $(LIBRETRO_COMM_DIR)/formats/m3u/m3u_file.o diff --git a/cheevos/cheevos_parser.c b/cheevos/cheevos_parser.c index 6fcfc50537..fe70e20723 100644 --- a/cheevos/cheevos_parser.c +++ b/cheevos/cheevos_parser.c @@ -3,7 +3,7 @@ #include "util.h" #include -#include +#include #include #include @@ -33,12 +33,20 @@ typedef struct { - const char *value; + char* value; int is_key; size_t length; unsigned key_hash; } 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 *****************************************************************************/ @@ -54,102 +62,87 @@ static uint32_t rcheevos_djb2(const char* str, size_t length) return hash; } -static int rcheevos_getvalue_key(void* userdata, +static bool rcheevos_getvalue_key(void* userdata, const char* name, size_t length) { rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; 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) { rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; - if (ud->is_key) + if (ud->is_key && ud->length > length) { - ud->value = string; - ud->length = length; - ud->is_key = 0; + strlcpy(ud->value, string, ud->length); + ud->is_key = 2; + 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; if (ud->is_key) { - if (istrue) + if (istrue && ud->length > 4) { - ud->value = "true"; - ud->length = 4; + strlcpy(ud->value, "true", ud->length); + ud->is_key = 2; + return false; } - else + if (!istrue && ud->length > 5) { - ud->value = "false"; - ud->length = 5; + strlcpy(ud->value, "false", ud->length); + 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; - if (ud->is_key ) + if (ud->is_key && ud->length > 4) { - ud->value = "null"; - ud->length = 4; - ud->is_key = 0; + strlcpy(ud->value, "null", ud->length); + ud->is_key = 2; + return false; } - return 0; + return true; } static int rcheevos_get_value(const char* json, unsigned key_hash, 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; ud.key_hash = key_hash; ud.is_key = 0; - ud.value = NULL; - ud.length = 0; + ud.value = value; + ud.length = length; *value = 0; - if ((jsonsax_parse(json, &handlers, (void*)&ud) == JSONSAX_OK) - && ud.value && ud.length < length) - { - strlcpy(value, ud.value, ud.length + 1); - return 0; - } + rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS, + rcheevos_getvalue_key, + rcheevos_getvalue_string, + rcheevos_getvalue_string, /* number */ + 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; } 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; ud->in_cheevos = 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) { 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) 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) { 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) 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* lboard_count, int* has_error) { - static const jsonsax_handlers_t handlers = - { - NULL, - NULL, - NULL, - NULL, - NULL, - rcheevos_count_end_array, - rcheevos_count_key, - NULL, - NULL, - rcheevos_count_number, - NULL, - NULL - }; - - int res; + bool res; rcheevos_countud_t ud; ud.in_cheevos = 0; ud.in_lboards = 0; @@ -261,7 +238,14 @@ static int rcheevos_count_cheevos(const char* json, ud.unofficial_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; *unofficial_count = ud.unofficial_count; @@ -275,168 +259,92 @@ static int rcheevos_count_cheevos(const char* json, Parses the cheevos in the JSON *****************************************************************************/ -typedef struct -{ - const char* string; - size_t length; -} rcheevos_field_t; - typedef struct { int in_cheevos; int in_lboards; - int is_game_id; - int is_title; - int is_console_id; - int is_richpresence; + int lboard_had_id; unsigned core_count; unsigned unofficial_count; unsigned lboard_count; - rcheevos_field_t* field; - rcheevos_field_t id, memaddr, title, desc, points, author; - rcheevos_field_t modified, created, badge, flags, format; + unsigned cheevo_flags; + const char* lboard_format; + const char** field_string; + unsigned* field_unsigned; + + rcheevos_racheevo_t cheevo; + rcheevos_ralboard_t lboard; rcheevos_rapatchdata_t* patchdata; } rcheevos_readud_t; -static char* rcheevos_unescape_string(const char* string, size_t length) -{ - 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) +static bool rcheevos_new_cheevo(rcheevos_readud_t* ud) { 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++; - else if (flags == 5) + else if (ud->cheevo_flags == 5) cheevo = ud->patchdata->unofficial + ud->unofficial_count++; - else - return 0; + ud->cheevo_flags = 0; - cheevo->title = rcheevos_unescape_string(ud->title.string, ud->title.length); - cheevo->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length); - cheevo->badge = rcheevos_unescape_string(ud->badge.string, ud->badge.length); - cheevo->memaddr = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length); - cheevo->points = (unsigned)strtol(ud->points.string, NULL, 10); - cheevo->id = (unsigned)strtol(ud->id.string, NULL, 10); - - if ( !cheevo->title - || !cheevo->description - || !cheevo->badge - || !cheevo->memaddr) + if (!cheevo + || !ud->cheevo.title + || !ud->cheevo.description + || !ud->cheevo.badge + || !ud->cheevo.memaddr) { - CHEEVOS_FREE(cheevo->title); - CHEEVOS_FREE(cheevo->description); - CHEEVOS_FREE(cheevo->badge); - CHEEVOS_FREE(cheevo->memaddr); - return -1; + CHEEVOS_FREE(ud->cheevo.title); + CHEEVOS_FREE(ud->cheevo.description); + CHEEVOS_FREE(ud->cheevo.badge); + CHEEVOS_FREE(ud->cheevo.memaddr); + 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++; - char format[32]; + rcheevos_ralboard_t* lboard = NULL; - lboard->title = rcheevos_unescape_string(ud->title.string, ud->title.length); - lboard->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length); - lboard->mem = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length); - lboard->id = (unsigned)strtol(ud->id.string, NULL, 10); + if (ud->lboard_had_id) + lboard = ud->patchdata->lboards + ud->lboard_count++; + ud->lboard_had_id = 0; - if ( !lboard->title - || !lboard->description - || !lboard->mem) + if (!lboard + || !ud->lboard.title + || !ud->lboard.description + || !ud->lboard.mem) { - CHEEVOS_FREE(lboard->title); - CHEEVOS_FREE(lboard->description); - CHEEVOS_FREE(lboard->mem); - return -1; + CHEEVOS_FREE(ud->lboard.title); + CHEEVOS_FREE(ud->lboard.description); + CHEEVOS_FREE(ud->lboard.mem); + 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); - format[ud->format.length] = '\0'; - lboard->format = rc_parse_format(format); + lboard->format = rc_parse_format(ud->lboard_format); + CHEEVOS_FREE(ud->lboard_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; @@ -446,26 +354,26 @@ static int rcheevos_read_end_object(void* userdata) if (ud->in_lboards) 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; ud->in_cheevos = 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) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; - int common = ud->in_cheevos || ud->in_lboards; uint32_t hash = rcheevos_djb2(name, length); - ud->field = NULL; + ud->field_unsigned = NULL; + ud->field_string = NULL; switch (hash) { @@ -476,145 +384,121 @@ static int rcheevos_read_key(void* userdata, ud->in_lboards = 1; break; case CHEEVOS_JSON_KEY_CONSOLE_ID: - ud->is_console_id = 1; + ud->field_unsigned = &ud->patchdata->console_id; break; case CHEEVOS_JSON_KEY_RICHPRESENCE: - ud->is_richpresence = 1; + ud->field_string = (const char**)&ud->patchdata->richpresence_script; break; case CHEEVOS_JSON_KEY_ID: - if (common) - ud->field = &ud->id; + if (ud->in_cheevos) + ud->field_unsigned = &ud->cheevo.id; + else if (ud->in_lboards) + { + ud->field_unsigned = &ud->lboard.id; + ud->lboard_had_id = 1; + } else - ud->is_game_id = 1; + ud->field_unsigned = &ud->patchdata->game_id; break; case CHEEVOS_JSON_KEY_MEMADDR: if (ud->in_cheevos) - ud->field = &ud->memaddr; + ud->field_string = &ud->cheevo.memaddr; break; case CHEEVOS_JSON_KEY_MEM: if (ud->in_lboards) - ud->field = &ud->memaddr; + ud->field_string = &ud->lboard.mem; break; case CHEEVOS_JSON_KEY_TITLE: - if (common) - ud->field = &ud->title; + if (ud->in_cheevos) + ud->field_string = &ud->cheevo.title; + else if (ud->in_lboards) + ud->field_string = &ud->lboard.title; else - ud->is_title = 1; + ud->field_string = (const char**)&ud->patchdata->title; break; case CHEEVOS_JSON_KEY_DESCRIPTION: - if (common) - ud->field = &ud->desc; + if (ud->in_cheevos) + ud->field_string = &ud->cheevo.description; + else if (ud->in_lboards) + ud->field_string = &ud->lboard.description; break; case CHEEVOS_JSON_KEY_POINTS: if (ud->in_cheevos) - ud->field = &ud->points; + ud->field_unsigned = &ud->cheevo.points; break; + /* UNUSED case CHEEVOS_JSON_KEY_AUTHOR: if (ud->in_cheevos) - ud->field = &ud->author; + ud->field_string = &ud->cheevo.author; break; case CHEEVOS_JSON_KEY_MODIFIED: if (ud->in_cheevos) - ud->field = &ud->modified; + ud->field_string = &ud->cheevo.modified; break; case CHEEVOS_JSON_KEY_CREATED: if (ud->in_cheevos) - ud->field = &ud->created; - break; + ud->field_string = &ud->cheevo.created; + break; */ case CHEEVOS_JSON_KEY_BADGENAME: if (ud->in_cheevos) - ud->field = &ud->badge; + ud->field_string = &ud->cheevo.badge; break; case CHEEVOS_JSON_KEY_FLAGS: if (ud->in_cheevos) - ud->field = &ud->flags; + ud->field_unsigned = &ud->cheevo_flags; break; case CHEEVOS_JSON_KEY_FORMAT: if (ud->in_lboards) - ud->field = &ud->format; + ud->field_string = &ud->lboard_format; break; default: 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) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; - if (ud->field) + if (ud->field_string) { - ud->field->string = string; - ud->field->length = length; - } - else if (ud->is_title) - { - 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; + if (*ud->field_string) + CHEEVOS_FREE((*ud->field_string)); + *ud->field_string = strdup(string); + ud->field_string = NULL; } - return 0; + return true; } -static int rcheevos_read_number(void* userdata, +static bool rcheevos_read_number(void* userdata, const char* number, size_t length) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; - if (ud->field) + if (ud->field_unsigned) { - ud->field->string = number; - ud->field->length = length; - } - 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; + *ud->field_unsigned = (unsigned)strtol(number, NULL, 10); + ud->field_unsigned = NULL; } - return 0; + return true; } 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; - int res; + bool res; int has_error; /* Count the number of achievements in the JSON file. */ res = rcheevos_count_cheevos(json, &patchdata->core_count, &patchdata->unofficial_count, &patchdata->lboard_count, &has_error); - if (res != JSONSAX_OK || has_error) + if (!res || has_error) return -1; /* Allocate the achievements. */ @@ -643,19 +527,27 @@ int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata) patchdata->title = NULL; /* Load the achievements. */ - ud.in_cheevos = 0; - 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; + memset(&ud, 0, sizeof(ud)); 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); return -1; @@ -730,15 +622,23 @@ typedef struct void* userdata; } 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; 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) { rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata; @@ -746,40 +646,30 @@ static int rcheevos_deactivate_number(void* userdata, if (ud->is_element) { - ud->is_element = 0; id = (unsigned)strtol(number, NULL, 10); 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) { - 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; ud.is_element = 0; ud.unlock_cb = unlock_cb; 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); } /***************************************************************************** diff --git a/disk_index_file.c b/disk_index_file.c index 99ac739e19..236bbf5ae6 100644 --- a/disk_index_file.c +++ b/disk_index_file.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include "file_path_special.h" #include "verbosity.h" @@ -37,25 +37,20 @@ typedef struct { - JSON_Parser parser; - JSON_Writer writer; - RFILE *file; unsigned *current_entry_uint_val; char **current_entry_str_val; unsigned image_index; char *image_path; } 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); - (void)attributes; /* unused */ + DCifJSONContext *pCtx = (DCifJSONContext*)context; if (pCtx->current_entry_str_val) { /* something went wrong */ - RARCH_ERR("[disk index file] JSON parsing failed at line %d.\n", __LINE__); - return JSON_Parser_Abort; + return false; } if (length) @@ -67,13 +62,12 @@ static JSON_Parser_HandlerResult DCifJSONObjectMemberHandler(JSON_Parser parser, /* 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); - (void)attributes; /* unused */ + DCifJSONContext *pCtx = (DCifJSONContext*)context; if (pCtx->current_entry_uint_val && length && !string_is_empty(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; - 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); - (void)attributes; /* unused */ + DCifJSONContext *pCtx = (DCifJSONContext*)context; 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; - return JSON_Parser_Continue; -} - -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))); - } + return true; } /******************/ @@ -156,6 +121,7 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file) bool success = false; DCifJSONContext context = {0}; RFILE *file = NULL; + rjson_t* parser; /* Sanity check */ if (!disk_index_file) @@ -183,66 +149,44 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file) } /* Initialise JSON parser */ - context.image_index = 0; - context.image_path = NULL; - context.parser = JSON_Parser_Create(NULL); - context.file = file; - - if (!context.parser) + parser = rjson_open_rfile(file); + if (!parser) { RARCH_ERR("[disk index file] Failed to create JSON parser.\n"); goto end; } /* Configure parser */ - JSON_Parser_SetAllowBOM(context.parser, JSON_True); - JSON_Parser_SetNumberHandler(context.parser, &DCifJSONNumberHandler); - JSON_Parser_SetStringHandler(context.parser, &DCifJSONStringHandler); - JSON_Parser_SetObjectMemberHandler(context.parser, &DCifJSONObjectMemberHandler); - JSON_Parser_SetUserData(context.parser, &context); + rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM); /* 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 */ - char chunk[128] = {0}; - int64_t length = filestream_read(file, chunk, sizeof(chunk)); - - /* Error checking... */ - if (!length && !filestream_eof(file)) + if (rjson_get_source_context_len(parser)) { RARCH_ERR( - "[disk index file] Failed to read disk index file: %s\n", - file_path); - JSON_Parser_Free(context.parser); - goto end; + "[disk index file] Error parsing chunk of disk index file: %s\n---snip---\n%.*s\n---snip---\n", + file_path, + 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( - "[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( "[disk index file] Error parsing disk index file: %s\n", file_path); - DCifJSONLogError(&context); - JSON_Parser_Free(context.parser); - goto end; + RARCH_ERR("[disk index file] Error: Invalid JSON at line %d, column %d - %s.\n", + (int)rjson_get_source_line(parser), + (int)rjson_get_source_column(parser), + (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error")); } /* Free parser */ - JSON_Parser_Free(context.parser); + rjson_free(parser); /* Copy values read from JSON file */ disk_index_file->image_index = context.image_index; @@ -406,15 +350,11 @@ void disk_index_file_set( /* Saves specified disk index file to disk */ bool disk_index_file_save(disk_index_file_t *disk_index_file) { - int n; - char value_string[32]; const char *file_path; - DCifJSONContext context = {0}; + rjsonwriter_t* writer; RFILE *file = NULL; bool success = false; - value_string[0] = '\0'; - /* Sanity check */ if (!disk_index_file) return false; @@ -450,69 +390,53 @@ bool disk_index_file_save(disk_index_file_t *disk_index_file) } /* Initialise JSON writer */ - context.writer = JSON_Writer_Create(NULL); - context.file = file; + writer = rjsonwriter_open_rfile(file); - if (!context.writer) + if (!writer) { RARCH_ERR("[disk index file] Failed to create JSON writer.\n"); 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 */ - JSON_Writer_WriteStartObject(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); /* > Version entry */ - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteString(context.writer, "version", - STRLEN_CONST("version"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, "1.0", - STRLEN_CONST("1.0"), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "version"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, "1.0"); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); /* > image index entry */ - n = snprintf( - value_string, sizeof(value_string), - "%u", disk_index_file->image_index); - if ((n < 0) || (n >= 32)) - n = 0; /* Silence GCC warnings... */ - - JSON_Writer_WriteSpace(context.writer, 2); - 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); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "image_index"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, disk_index_file->image_index); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); /* > image path entry */ - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteString(context.writer, "image_path", - STRLEN_CONST("image_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, - disk_index_file->image_path, - strlen(disk_index_file->image_path), JSON_UTF8); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "image_path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, disk_index_file->image_path); + rjsonwriter_add_newline(writer); /* > Finalise */ - JSON_Writer_WriteEndObject(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_end_object(writer); + rjsonwriter_add_newline(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 * is no longer considered to be in a diff --git a/griffin/griffin.c b/griffin/griffin.c index ea1236e77c..6bec2b5e47 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -57,8 +57,6 @@ #endif #endif -#define JSON_STATIC 1 /* must come before runtime_file, netplay_room_parse and jsonsax_full */ - #if _MSC_VER && !defined(__WINRT__) #include "../libretro-common/compat/compat_snprintf.c" #endif @@ -193,7 +191,6 @@ ACHIEVEMENTS #include "../libretro-common/net/net_http.c" #endif -#include "../libretro-common/formats/json/jsonsax.c" #include "../libretro-common/formats/cdfs/cdfs.c" #include "../network/net_http_special.c" @@ -1279,8 +1276,8 @@ THREAD #include "../audio/audio_thread_wrapper.c" #endif -/* needed for both playlists and netplay lobbies */ -#include "../libretro-common/formats/json/jsonsax_full.c" +/* needed for playlists, netplay lobbies and achievements */ +#include "../libretro-common/formats/json/rjson.c" /*============================================================ NETPLAY diff --git a/libretro-common/formats/json/rjson.c b/libretro-common/formats/json/rjson.c new file mode 100644 index 0000000000..9251e0d36a --- /dev/null +++ b/libretro-common/formats/json/rjson.c @@ -0,0 +1,1385 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rjson.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +/* The parser is based on Public Domain JSON Parser for C by Christopher Wellons - https://github.com/skeeto/pdjson */ + +#include /* snprintf, vsnprintf */ +#include /* va_list */ +#include /* memcpy, strlen */ +#include /* int64_t */ +#include /* malloc, realloc, atof, atoi */ + +#include +#include +#include +#include + +struct _rjson_stack { enum rjson_type type; size_t count; }; + +struct rjson +{ + /* Order of the top few struct elements have an impact on performance */ + /* Place most frequently accessed things on top */ + const unsigned char *input_p; + struct _rjson_stack *stack_top; + const unsigned char *input_end; + const unsigned char* source_column_p; + size_t source_line; + + char *string, *string_pass_through; + size_t string_len, string_cap; + + struct _rjson_stack inline_stack[10]; + struct _rjson_stack *stack; + + rjson_io_t io; + void *user_data; + + unsigned int stack_cap, stack_max; + int input_len; + + char option_flags; + char decimal_sep; + char error_text[80]; + char inline_string[512]; + + /* Must be at the end of the struct, can be allocated with custom size */ + unsigned char input_buf[512]; +}; + +enum _rjson_token +{ + _rJSON_TOK_WHITESPACE, _rJSON_TOK_NEWLINE, _rJSON_TOK_OPTIONAL_SKIP, + _rJSON_TOK_OBJECT, _rJSON_TOK_ARRAY, _rJSON_TOK_STRING, _rJSON_TOK_NUMBER, + _rJSON_TOK_TRUE, _rJSON_TOK_FALSE, _rJSON_TOK_NULL, + _rJSON_TOK_OBJECT_END, _rJSON_TOK_ARRAY_END, _rJSON_TOK_COLON, + _rJSON_TOK_COMMA, _rJSON_TOK_ERROR, _rJSON_TOK_EOF, _rJSON_TOKCOUNT +}; + +/* The used char type is int and not short for better performance */ +typedef unsigned int _rjson_char_t; +#define _rJSON_EOF ((_rjson_char_t)256) + +/* Compiler branching hint for expression with high probability + * Explicitly only have likely (and no unlikely) because compilers + * that don't support it expect likely branches to come first. */ +#if defined(__GNUC__) || defined(__clang__) +#define _rJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define _rJSON_LIKELY(x) (x) +#endif + +/* These 3 error functions return RJSON_ERROR for convenience */ +static enum rjson_type _rjson_error(rjson_t *json, const char *fmt, ...) +{ + va_list ap; + if (json->stack_top->type == RJSON_ERROR) return RJSON_ERROR; + json->stack_top->type = RJSON_ERROR; + va_start(ap, fmt); + vsnprintf(json->error_text, sizeof(json->error_text), fmt, ap); + va_end(ap); + return RJSON_ERROR; +} + +static enum rjson_type _rjson_error_char(rjson_t *json, + const char *fmt, _rjson_char_t chr) +{ + char buf[16]; + if (json->stack_top->type == RJSON_ERROR) return RJSON_ERROR; + snprintf(buf, sizeof(buf), + (chr == _rJSON_EOF ? "end of stream" : + (chr >= ' ' && chr <= '~' ? "'%c'" : "byte 0x%02X")), chr); + return _rjson_error(json, fmt, buf); +} + +static enum rjson_type _rjson_error_token(rjson_t *json, + const char *fmt, enum _rjson_token tok) +{ + return _rjson_error_char(json, fmt, + (tok == _rJSON_TOK_EOF ? _rJSON_EOF : json->input_p[-1])); +} + +static bool _rjson_io_input(rjson_t *json) +{ + if (json->input_end == json->input_buf) return false; + json->source_column_p -= (json->input_end - json->input_buf); + json->input_p = json->input_buf; + json->input_end = json->input_buf + + json->io(json->input_buf, json->input_len, json->user_data); + if (json->input_end < json->input_buf) + { + _rjson_error(json, "input stream read error"); + json->input_end = json->input_buf; + } + return (json->input_end != json->input_p); +} + +static bool _rjson_grow_string(rjson_t *json) +{ + char *string; + size_t new_string_cap = json->string_cap * 2; + if (json->string != json->inline_string) + string = (char*)realloc(json->string, new_string_cap); + else if ((string = (char*)malloc(new_string_cap)) != NULL) + memcpy(string, json->inline_string, sizeof(json->inline_string)); + if (string == NULL) + { + _rjson_error(json, "out of memory"); + return false; + } + json->string_cap = new_string_cap; + json->string = string; + return true; +} + +static INLINE bool _rjson_pushchar(rjson_t *json, _rjson_char_t c) +{ + json->string[json->string_len++] = (char)c; + return (json->string_len != json->string_cap || _rjson_grow_string(json)); +} + +static INLINE bool _rjson_pushchars(rjson_t *json, + const unsigned char *from, const unsigned char *to) +{ + size_t len = json->string_len, new_len = len + (to - from); + unsigned char* string; + while (new_len >= json->string_cap) + if (!_rjson_grow_string(json)) + return false; + string = (unsigned char *)json->string; + while (len != new_len) string[len++] = *(from++); + json->string_len = new_len; + return true; +} + +static INLINE _rjson_char_t _rjson_char_get(rjson_t *json) +{ + return (json->input_p != json->input_end || _rjson_io_input(json) + ? *json->input_p++ : _rJSON_EOF); +} + +static unsigned int _rjson_get_unicode_cp(rjson_t *json) +{ + unsigned int cp = 0, shift = 16; + for (;;) + { + _rjson_char_t c = _rjson_char_get(json); + switch (c) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c -= '0'; break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + c -= ('a' - 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + c -= ('A' - 10); break; + case _rJSON_EOF: + _rjson_error(json, "unterminated string literal in Unicode"); + return (unsigned int)-1; + default: + _rjson_error_char(json, "invalid Unicode escape hexadecimal %s", c); + return (unsigned int)-1; + } + shift -= 4; + cp |= ((unsigned int)c << shift); + if (!shift) return cp; + } +} + +static bool _rjson_read_unicode(rjson_t *json) +{ + #define _rJSON_READ_UNICODE_REPLACE_OR_IGNORE \ + if (json->option_flags & (RJSON_OPTION_IGNORE_INVALID_ENCODING \ + | RJSON_OPTION_REPLACE_INVALID_ENCODING)) goto replace_or_ignore; + + unsigned int cp; + + if ((cp = _rjson_get_unicode_cp(json)) == (unsigned int)-1) + return false; + + if (cp >= 0xd800 && cp <= 0xdbff) + { + /* This is the high portion of a surrogate pair; we need to read the + * lower portion to get the codepoint */ + unsigned int l, h = cp; + + _rjson_char_t c = _rjson_char_get(json); + if (c == _rJSON_EOF) + { + _rjson_error(json, "unterminated string literal in Unicode"); + return false; + } + if (c != '\\') + { + _rjson_error_char(json, "invalid continuation %s" + " for surrogate pair, expected '\\'", c); + return false; + } + + c = _rjson_char_get(json); + if (c == _rJSON_EOF) + { + _rjson_error(json, "unterminated string literal in Unicode"); + return false; + } + if (c != 'u') + { + _rjson_error_char(json, "invalid continuation %s" + " for surrogate pair, expected 'u'", c); + return false; + } + if ((l = _rjson_get_unicode_cp(json)) == (unsigned int)-1) + return false; + if (l < 0xdc00 || l > 0xdfff) + { + _rJSON_READ_UNICODE_REPLACE_OR_IGNORE + _rjson_error(json, "surrogate pair continuation \\u%04x out " + "of range (dc00-dfff)", l); + return false; + } + cp = ((h - 0xd800) * 0x400) + ((l - 0xdc00) + 0x10000); + } + else if (cp >= 0xdc00 && cp <= 0xdfff) + { + _rJSON_READ_UNICODE_REPLACE_OR_IGNORE + _rjson_error(json, "dangling surrogate \\u%04x", cp); + return false; + } + + if (cp < 0x80UL) + return _rjson_pushchar(json, cp); + + if (cp < 0x0800UL) + return (_rjson_pushchar(json, (cp >> 6 & 0x1F) | 0xC0) && + _rjson_pushchar(json, (cp >> 0 & 0x3F) | 0x80)); + + if (cp < 0x010000UL) + { + if (cp >= 0xd800 && cp <= 0xdfff) + { + _rJSON_READ_UNICODE_REPLACE_OR_IGNORE + _rjson_error(json, "invalid codepoint %04x", cp); + return false; + } + return (_rjson_pushchar(json, (cp >> 12 & 0x0F) | 0xE0) && + _rjson_pushchar(json, (cp >> 6 & 0x3F) | 0x80) && + _rjson_pushchar(json, (cp >> 0 & 0x3F) | 0x80)); + } + if (cp < 0x110000UL) + return (_rjson_pushchar(json, (cp >> 18 & 0x07) | 0xF0) && + _rjson_pushchar(json, (cp >> 12 & 0x3F) | 0x80) && + _rjson_pushchar(json, (cp >> 6 & 0x3F) | 0x80) && + _rjson_pushchar(json, (cp >> 0 & 0x3F) | 0x80)); + + _rJSON_READ_UNICODE_REPLACE_OR_IGNORE + _rjson_error(json, "unable to encode %04x as UTF-8", cp); + return false; + replace_or_ignore: + return ((json->option_flags & RJSON_OPTION_IGNORE_INVALID_ENCODING) || + _rjson_pushchar(json, '?')); + #undef _rJSON_READ_UNICODE_REPLACE_OR_IGNORE +} + +static bool _rjson_validate_utf8(rjson_t *json) +{ + unsigned char first, c; + unsigned char *p; + unsigned char *from = (unsigned char *) + (json->string_pass_through ? json->string_pass_through : json->string); + unsigned char *to = from + json->string_len; + + if (json->option_flags & RJSON_OPTION_IGNORE_INVALID_ENCODING) + return true; + + for (;;) + { + if (from == to) return true; + first = *from; + if (first <= 0x7F) { from++; continue; } /* ascii */ + p = from; + if (first <= 0xC1) + { + /* continuation or overlong encoding of an ASCII byte */ + goto invalid_utf8; + } + if (first <= 0xDF) + { + if ((from = p + 2) > to) goto invalid_utf8; + continue_length_2: + c = p[1]; + switch (first) + { + case 0xE0: c = (c < 0xA0 || c > 0xBF); break; + case 0xED: c = (c < 0x80 || c > 0x9F); break; + case 0xF0: c = (c < 0x90 || c > 0xBF); break; + case 0xF4: c = (c < 0x80 || c > 0x8F); break; + default: c = (c < 0x80 || c > 0xBF); break; + } + if (c) goto invalid_utf8; + } + else if (first <= 0xEF) + { + if ((from = p + 3) > to) goto invalid_utf8; + from = p + 3; + continue_length_3: + if ((c = p[2]) < 0x80 || c > 0xBF) goto invalid_utf8; + goto continue_length_2; + } + else if (first <= 0xF4) + { + if ((from = p + 4) > to) goto invalid_utf8; + if ((c = p[3]) < 0x80 || c > 0xBF) goto invalid_utf8; + goto continue_length_3; + } + else goto invalid_utf8; /* length 5 or 6 or invalid UTF-8 */ + continue; + invalid_utf8: + if (!(json->option_flags & RJSON_OPTION_REPLACE_INVALID_ENCODING)) + { + _rjson_error(json, "invalid UTF-8 character in string"); + return false; + } + from = p; + *from++ = '?'; + while (from != to && (*from & 0x80)) *from++ = '?'; + } +} + +static enum rjson_type _rjson_read_string(rjson_t *json) +{ + const unsigned char *p = json->input_p, *raw = p; + const unsigned char *end = json->input_end; + unsigned char utf8mask = 0; + json->string_pass_through = NULL; + json->string_len = 0; + for (;;) + { + if (_rJSON_LIKELY(p != end)) + { + unsigned char c = *p; + if (_rJSON_LIKELY(c != '"' && c != '\\' && c >= 0x20)) + { + /* handle most common case first, it's faster */ + utf8mask |= c; + p++; + } + else if (c == '"') + { + json->input_p = p + 1; + if (json->string_len == 0 && p + 1 != end) + { + /* raw string fully inside input buffer, pass through */ + json->string_len = p - raw; + json->string_pass_through = (char*)raw; + } + else if (raw != p && !_rjson_pushchars(json, raw, p)) + { + /* out of memory */ + return RJSON_ERROR; + } + if ((utf8mask & 0x80) && !_rjson_validate_utf8(json)) + { + /* contains invalid UTF-8 byte sequences */ + return RJSON_ERROR; + } + return RJSON_STRING; + } + else if (c == '\\') + { + _rjson_char_t esc; + if (raw != p) + { + /* can't pass through string with escapes, use string buffer */ + if (!_rjson_pushchars(json, raw, p)) return RJSON_ERROR; + } + json->input_p = p + 1; + esc = _rjson_char_get(json); + switch (esc) + { + case 'u': + if (!_rjson_read_unicode(json)) + return RJSON_ERROR; + break; + + case 'b': esc = '\b'; goto escape_pushchar; + case 'f': esc = '\f'; goto escape_pushchar; + case 'n': esc = '\n'; goto escape_pushchar; + case 'r': + if (!(json->option_flags & RJSON_OPTION_IGNORE_STRING_CARRIAGE_RETURN)) + { + esc = '\r'; + goto escape_pushchar; + } + break; + case 't': esc = '\t'; goto escape_pushchar; + + case '/': case '"': case '\\': + escape_pushchar: + if (!_rjson_pushchar(json, esc)) + return RJSON_ERROR; + break; + + case _rJSON_EOF: + return _rjson_error(json, "unterminated string literal in escape"); + + default: + return _rjson_error_char(json, "invalid escaped %s", esc); + } + raw = p = json->input_p; + end = json->input_end; + } + else if (!(json->option_flags & RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS)) + return _rjson_error_char(json, "unescaped control character %s in string", c); + else + p++; + } + else + { + if (raw != p) + { + /* not fully inside input buffer, copy to string buffer */ + if (!_rjson_pushchars(json, raw, p)) return RJSON_ERROR; + } + if (!_rjson_io_input(json)) + return _rjson_error(json, "unterminated string literal"); + raw = p = json->input_p; + end = json->input_end; + } + } +} + +static enum rjson_type _rjson_read_number(rjson_t *json) +{ + const unsigned char *p = json->input_p - 1; + const unsigned char *end = json->input_end; + const unsigned char *start = p; + + json->string_len = 0; + json->string_pass_through = NULL; + for (;;) + { + if (_rJSON_LIKELY(p != end)) + { + switch (*p++) + { + case '+': case '-': case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'E': case 'e': + continue; + } + p--; + json->input_p = p; + if (!_rjson_pushchars(json, start, p)) + return RJSON_ERROR; /* out of memory */ + break; + } + else + { + /* number sequences are always copied to the string buffer */ + if (!_rjson_pushchars(json, start, p)) return RJSON_ERROR; + if (!_rjson_io_input(json)) + { + /* EOF here is not an error for a number */ + json->input_p = json->input_end; + break; + } + start = p = json->input_p; + end = json->input_end; + } + } + + p = (const unsigned char *)json->string; + end = (p + json->string_len); + + /* validate json number */ + if (*p == '-' && ++p == end) goto invalid_number; + if (*p == '0') + { + if (++p == end) return RJSON_NUMBER; + } + else + { + if (*p < '1' || *p > '9') goto invalid_number; + do { if (++p == end) return RJSON_NUMBER; } + while (*p >= '0' && *p <= '9'); + } + if (*p == '.') + { + if (++p == end) goto invalid_number; + if (*p < '0' || *p > '9') goto invalid_number; + do { if (++p == end) return RJSON_NUMBER; } + while (*p >= '0' && *p <= '9'); + } + if (((*p)|0x20) == 'e') + { + if (++p == end) goto invalid_number; + if ((*p == '-' || *p == '+') && ++p == end) goto invalid_number; + if (*p < '0' || *p > '9') goto invalid_number; + do { if (++p == end) return RJSON_NUMBER; } + while (*p >= '0' && *p <= '9'); + } + invalid_number: + return _rjson_error_char(json, "unexpected %s in number", + (p == json->input_end ? _rJSON_EOF : p[p == end ? -1 : 0])); +} + +static enum rjson_type _rjson_push_stack(rjson_t *json, enum _rjson_token t) +{ + if (json->stack_top + 1 == json->stack + json->stack_cap) + { + /* reached allocated stack size, either reallocate or abort */ + unsigned int new_stack_cap; + struct _rjson_stack *new_stack; + size_t stack_alloc; + if (json->stack_cap == json->stack_max) + return _rjson_error(json, "maximum depth of nesting reached"); + + new_stack_cap = json->stack_cap + 4; + if (new_stack_cap > json->stack_max) + new_stack_cap = json->stack_max; + stack_alloc = new_stack_cap * sizeof(struct _rjson_stack); + if (json->stack != json->inline_stack) + new_stack = (struct _rjson_stack *)realloc(json->stack, stack_alloc); + else if ((new_stack = (struct _rjson_stack*)malloc(stack_alloc)) != NULL) + memcpy(new_stack, json->inline_stack, sizeof(json->inline_stack)); + if (new_stack == NULL) + return _rjson_error(json, "out of memory"); + + json->stack = new_stack; + json->stack_top = new_stack + json->stack_cap - 1; + json->stack_cap = new_stack_cap; + } + json->stack_top++; + json->stack_top->count = 0; + return (json->stack_top->type = + (t == _rJSON_TOK_ARRAY ? RJSON_ARRAY : RJSON_OBJECT)); +} + +static enum rjson_type _rjson_read_name(rjson_t *json, const char *pattern, enum rjson_type type) +{ + _rjson_char_t c; + const char *p; + for (p = pattern; *p; p++) + if ((_rjson_char_t)*p != (c = _rjson_char_get(json))) + return _rjson_error_char(json, "unexpected %s in value", c); + return type; +} + +static bool _rjson_optional_skip(rjson_t *json, const unsigned char **p, const unsigned char **end) +{ + unsigned char c, skip = (*p)[-1]; + int state = 0; + + if (skip == '/' && !(json->option_flags & RJSON_OPTION_ALLOW_COMMENTS)) + return false; + + if (skip == 0xEF && (!(json->option_flags & RJSON_OPTION_ALLOW_UTF8BOM) || + json->source_line != 1 || json->source_column_p != json->input_p)) + return false; + + for (;;) + { + if (*p == *end) + { + if (!_rjson_io_input(json)) + { + _rjson_error(json, "unfinished %s", + (skip == '/' ? "comment" : "utf8 byte order mark")); + return false; + } + *p = json->input_p, *end = json->input_end; + } + c = *(*p)++; + if (skip == '/') + { + if (state == 0 && c == '/') state = 1; + else if (state == 0 && c == '*') state = 2; + else if (state == 0) return false; + else if (state == 1 && c == '\n') return true; + else if (state == 2 && c == '*') state = 3; + else if (state == 3 && c == '/') return true; + else if (state == 3 && c != '*') state = 2; + } + else if (skip == 0xEF) + { + if (state == 0 && c == 0xBB) state = 1; + else if (state == 1 && c == 0xBF) return true; + else return false; + } + } + return false; +} + +enum rjson_type rjson_next(rjson_t *json) +{ + struct _rjson_stack *stack = json->stack_top; + const unsigned char *p = json->input_p; + const unsigned char *end = json->input_end; + unsigned char tok; + unsigned char passed_token = false; + + /* JSON token look-up-table */ + static const unsigned char token_lut[256] = + { + #define i _rJSON_TOK_ERROR + /* 0 | 0x00 | */ i,i,i,i,i,i,i,i,i, + /* 9 | 0x09 |\t */ _rJSON_TOK_WHITESPACE, + /* 10 | 0x0A |\n */ _rJSON_TOK_NEWLINE, i,i, + /* 13 | 0x0D |\r */ _rJSON_TOK_WHITESPACE, i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i, + /* 32 | 0x20 | */ _rJSON_TOK_WHITESPACE, i, + /* 34 | 0x22 | " */ _rJSON_TOK_STRING, i,i,i,i,i,i,i,i,i, + /* 44 | 0x2C | , */ _rJSON_TOK_COMMA, + /* 45 | 0x2D | - */ _rJSON_TOK_NUMBER, i, + /* 47 | 0x2F | / */ _rJSON_TOK_OPTIONAL_SKIP, + /* 48 | 0x30 | 0 */ _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, + /* 53 | 0x35 | 5 */ _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, + /* 58 | 0x3A | : */ _rJSON_TOK_COLON, i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i, + /* 91 | 0x5B | [ */ _rJSON_TOK_ARRAY, i, + /* 93 | 0x5D | ] */ _rJSON_TOK_ARRAY_END, i,i,i,i,i,i,i,i, + /* 102 | 0x66 | f */ _rJSON_TOK_FALSE, i,i,i,i,i,i,i, + /* 110 | 0x6E | n */ _rJSON_TOK_NULL, i,i,i,i,i, + /* 116 | 0x74 | t */ _rJSON_TOK_TRUE, i,i,i,i,i,i, + /* 123 | 0x7B | { */ _rJSON_TOK_OBJECT, i, + /* 125 | 0x7D | } */ _rJSON_TOK_OBJECT_END, + /* 126 | 0x7E | ~ */ i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i, + /* 164 | 0xA4 | */ i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i, + /* 202 | 0xCA | */ i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i, + /* 239 | 0xEF | */ _rJSON_TOK_OPTIONAL_SKIP, i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i + #undef i + }; + + if (_rJSON_LIKELY(stack->type != RJSON_ERROR)) + { + for (;;) + { + if (_rJSON_LIKELY(p != end)) + { + tok = token_lut[*p++]; + if (_rJSON_LIKELY(tok > _rJSON_TOK_OPTIONAL_SKIP)) + { + /* actual JSON token, process below */ + } + else if (_rJSON_LIKELY(tok == _rJSON_TOK_WHITESPACE)) + { + continue; + } + else if (tok == _rJSON_TOK_NEWLINE) + { + json->source_line++; + json->source_column_p = p; + continue; + } + else if (tok == _rJSON_TOK_OPTIONAL_SKIP) + { + if (_rjson_optional_skip(json, &p, &end)) + continue; + } + } + else if (_rJSON_LIKELY(_rjson_io_input(json))) + { + p = json->input_p, end = json->input_end; + continue; + } + else + { + p = json->input_end; + tok = _rJSON_TOK_EOF; + } + + if (stack->type == RJSON_OBJECT) + { + if (stack->count & 1) + { + /* Expecting colon followed by value. */ + if (passed_token) + goto read_value; + if (_rJSON_LIKELY(tok == _rJSON_TOK_COLON)) + { + passed_token = true; + continue; + } + json->input_p = p; + return _rjson_error_token(json, + "expected ':' not %s after member name", (enum _rjson_token)tok); + } + if (passed_token) + { + if (_rJSON_LIKELY(tok == _rJSON_TOK_STRING)) + goto read_value; + json->input_p = p; + return _rjson_error(json, "expected member name after ','"); + } + if (tok == _rJSON_TOK_OBJECT_END) + { + json->input_p = p; + json->stack_top--; + return RJSON_OBJECT_END; + } + if (stack->count == 0) + { + /* No member name/value pairs yet. */ + if (_rJSON_LIKELY(tok == _rJSON_TOK_STRING)) + goto read_value; + json->input_p = p; + return _rjson_error(json, "expected member name or '}'"); + } + /* Expecting comma followed by member name. */ + if (_rJSON_LIKELY(tok == _rJSON_TOK_COMMA)) + { + passed_token = true; + continue; + } + json->input_p = p; + return _rjson_error_token(json, + "expected ',' or '}' not %s after member value", (enum _rjson_token)tok); + } + else if (stack->type == RJSON_ARRAY) + { + if (passed_token) + goto read_value; + if (tok == _rJSON_TOK_ARRAY_END) + { + json->input_p = p; + json->stack_top--; + return RJSON_ARRAY_END; + } + if (stack->count == 0) + goto read_value; + if (_rJSON_LIKELY(tok == _rJSON_TOK_COMMA)) + { + passed_token = true; + continue; + } + json->input_p = p; + return _rjson_error_token(json, + "expected ',' or ']' not %s in array", (enum _rjson_token)tok); + } + else + { + if (_rJSON_LIKELY(!stack->count && tok != _rJSON_TOK_EOF)) + goto read_value; + json->input_p = p; + if (!stack->count) + return _rjson_error(json, "reached end without any data"); + if (tok == _rJSON_TOK_EOF) + return RJSON_DONE; + if (!(json->option_flags & RJSON_OPTION_ALLOW_TRAILING_DATA)) + return _rjson_error_token(json, + "expected end of stream instead of %s", (enum _rjson_token)tok); + json->input_p--; + return RJSON_DONE; + } + + /* read value for current token */ + read_value: + json->input_p = p; + stack->count++; + /* This is optimal when there are many strings, otherwise a switch statement + * or a function pointer table is better (depending on compiler/cpu) */ + if (tok == _rJSON_TOK_STRING) + return _rjson_read_string(json); + else if (tok == _rJSON_TOK_NUMBER) + return _rjson_read_number(json); + else if (tok == _rJSON_TOK_OBJECT) + return _rjson_push_stack(json, _rJSON_TOK_OBJECT); + else if (tok == _rJSON_TOK_ARRAY) + return _rjson_push_stack(json, _rJSON_TOK_ARRAY); + else if (tok == _rJSON_TOK_TRUE) + return _rjson_read_name(json, "rue", RJSON_TRUE); + else if (tok == _rJSON_TOK_FALSE) + return _rjson_read_name(json, "alse", RJSON_FALSE); + else if (tok == _rJSON_TOK_NULL) + return _rjson_read_name(json, "ull", RJSON_NULL); + else return _rjson_error_token(json, + "unexpected %s in value", (enum _rjson_token)tok); + } + } + return RJSON_ERROR; +} + +void _rjson_setup(rjson_t *json, rjson_io_t io, void *user_data, int input_len) +{ + json->io = io; + json->user_data = user_data; + json->input_len = input_len; + json->input_p = json->input_end = json->input_buf + input_len; + + json->stack = json->inline_stack; + json->stack_top = json->stack; + json->stack_top->type = RJSON_DONE; + json->stack_top->count = 0; + json->stack_cap = (unsigned int)(sizeof(json->inline_stack) / sizeof(json->inline_stack[0])); + json->stack_max = (unsigned int)50; + + json->string = json->inline_string; + json->string_pass_through = NULL; + json->string_len = 0; + json->string_cap = sizeof(json->inline_string); + + json->source_line = 1; + json->source_column_p = json->input_p; + json->option_flags = 0; + json->decimal_sep = 0; +} + +rjson_t *rjson_open_user(rjson_io_t io, void *user_data, int io_block_size) +{ + rjson_t* json = (rjson_t*)malloc( + sizeof(rjson_t) - sizeof(((rjson_t*)0)->input_buf) + io_block_size); + if (json) _rjson_setup(json, io, user_data, io_block_size); + return json; +} + +static int _rjson_buffer_io(void* buf, int len, void *user) +{ + const char **ud = (const char **)user; + if (ud[1] - ud[0] < len) len = (int)(ud[1] - ud[0]); + memcpy(buf, ud[0], len); + ud[0] += len; + return len; +} + +rjson_t *rjson_open_buffer(const void *buffer, size_t size) +{ + rjson_t *json = (rjson_t *)malloc(sizeof(rjson_t) + sizeof(const char *)*2); + const char **ud = (const char **)(json + 1); + if (!json) return NULL; + ud[0] = (const char *)buffer; + ud[1] = ud[0] + size; + _rjson_setup(json, _rjson_buffer_io, (void*)ud, sizeof(json->input_buf)); + return json; +} + +rjson_t *rjson_open_string(const char *string) +{ + return rjson_open_buffer(string, strlen(string)); +} + +static int _rjson_stream_io(void* buf, int len, void *user) +{ + return (int)intfstream_read((intfstream_t*)user, buf, (uint64_t)len); +} + +rjson_t *rjson_open_stream(struct intfstream_internal *stream) +{ + /* Allocate an input buffer based on the file size */ + int64_t size = intfstream_get_size(stream); + int io_size = + (size > 1024*1024 ? 4096 : + (size > 256*1024 ? 2048 : 1024)); + return rjson_open_user(_rjson_stream_io, stream, io_size); +} + +static int _rjson_rfile_io(void* buf, int len, void *user) +{ + return (int)filestream_read((RFILE*)user, buf, (int64_t)len); +} + +rjson_t *rjson_open_rfile(RFILE *rfile) +{ + /* Allocate an input buffer based on the file size */ + int64_t size = filestream_get_size(rfile); + int io_size = + (size > 1024*1024 ? 4096 : + (size > 256*1024 ? 2048 : 1024)); + return rjson_open_user(_rjson_rfile_io, rfile, io_size); +} + +void rjson_set_options(rjson_t *json, char rjson_option_flags) +{ + json->option_flags = rjson_option_flags; +} + +void rjson_set_max_depth(rjson_t *json, unsigned int max_depth) +{ + json->stack_max = max_depth; +} + +const char *rjson_get_string(rjson_t *json, size_t *length) +{ + char* str = (json->string_pass_through ? json->string_pass_through : json->string); + if (length != NULL) + *length = json->string_len; + str[json->string_len] = '\0'; + return str; +} + +double rjson_get_double(rjson_t *json) +{ + char* str = (json->string_pass_through ? json->string_pass_through : json->string); + str[json->string_len] = '\0'; + if (json->decimal_sep != '.') + { + /* handle locale that uses a non-standard decimal separator */ + char *p; + if (json->decimal_sep == 0) + { + char test[4]; + snprintf(test, sizeof(test), "%.1f", 0.0f); + json->decimal_sep = test[1]; + } + if (json->decimal_sep != '.' && (p = strchr(str, '.')) != NULL) + { + double res; + *p = json->decimal_sep; + res = atof(str); + *p = '.'; + return res; + } + } + return atof(str); +} + +int rjson_get_int(rjson_t *json) +{ + char* str = (json->string_pass_through ? json->string_pass_through : json->string); + str[json->string_len] = '\0'; + return atoi(str); +} + +const char *rjson_get_error(rjson_t *json) +{ + return (json->stack_top->type == RJSON_ERROR ? json->error_text : ""); +} + +void rjson_set_error(rjson_t *json, const char* error) +{ + _rjson_error(json, "%s", error); +} + +size_t rjson_get_source_line(rjson_t *json) +{ + return json->source_line; +} + +size_t rjson_get_source_column(rjson_t *json) +{ + return (json->input_p == json->source_column_p ? 1 : + json->input_p - json->source_column_p); +} + +int rjson_get_source_context_len(rjson_t *json) +{ + const unsigned char *from = json->input_buf, *to = json->input_end, *p = json->input_p; + return ((p + 256 < to ? p + 256 : to) - (p > from + 256 ? p - 256 : from)); +} + +const char* rjson_get_source_context_buf(rjson_t *json) +{ + /* inside the input buffer, some " may have been replaced with \0. */ + const unsigned char *p = json->input_p, *from = json->input_buf; + unsigned char *i = json->input_buf; + for (; i != json->input_end; i++) if (*i == '\0') *i = '"'; + return (const char*)(p > from + 256 ? p - 256 : from); +} + +bool rjson_check_context(rjson_t *json, unsigned int depth, ...) +{ + va_list ap; + const struct _rjson_stack *stack = json->stack, *stack_top = json->stack_top; + if ((unsigned int)(stack_top - stack) != depth) + return false; + va_start(ap, depth); + while (++stack <= stack_top) + { + if (va_arg(ap, int) == (int)stack->type) continue; + va_end(ap); + return false; + } + va_end(ap); + return true; +} + +unsigned int rjson_get_context_depth(rjson_t *json) +{ + return json->stack_top - json->stack; +} + +size_t rjson_get_context_count(rjson_t *json) +{ + return json->stack_top->count; +} + +enum rjson_type rjson_get_context_type(rjson_t *json) +{ + return json->stack_top->type; +} + +void rjson_free(rjson_t *json) +{ + if (json->stack != json->inline_stack) + free(json->stack); + if (json->string != json->inline_string) + free(json->string); + free(json); +} + +static bool _rjson_nop_default(void *context) { return true; } +static bool _rjson_nop_string(void *context, const char *value, size_t length) { return true; } +static bool _rjson_nop_bool(void *context, bool value) { return true; } + +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)) +{ + bool in_object = false; + size_t len; + const char* string; + if (!object_member_handler) object_member_handler = _rjson_nop_string; + if (!string_handler ) string_handler = _rjson_nop_string; + if (!number_handler ) number_handler = _rjson_nop_string; + if (!start_object_handler ) start_object_handler = _rjson_nop_default; + if (!end_object_handler ) end_object_handler = _rjson_nop_default; + if (!start_array_handler ) start_array_handler = _rjson_nop_default; + if (!end_array_handler ) end_array_handler = _rjson_nop_default; + if (!boolean_handler ) boolean_handler = _rjson_nop_bool; + if (!null_handler ) null_handler = _rjson_nop_default; + for (;;) + { + switch (rjson_next(json)) + { + case RJSON_STRING: + string = rjson_get_string(json, &len); + if (_rJSON_LIKELY( + (in_object && (json->stack_top->count & 1) ? + object_member_handler : string_handler) + (context, string, len))) + continue; + return RJSON_STRING; + case RJSON_NUMBER: + string = rjson_get_string(json, &len); + if (_rJSON_LIKELY(number_handler(context, string, len))) + continue; + return RJSON_NUMBER; + case RJSON_OBJECT: + in_object = true; + if (_rJSON_LIKELY(start_object_handler(context))) + continue; + return RJSON_OBJECT; + case RJSON_ARRAY: + in_object = false; + if (_rJSON_LIKELY(start_array_handler(context))) + continue; + return RJSON_ARRAY; + case RJSON_OBJECT_END: + if (_rJSON_LIKELY(end_object_handler(context))) + { + in_object = (json->stack_top->type == RJSON_OBJECT); + continue; + } + return RJSON_OBJECT_END; + case RJSON_ARRAY_END: + if (_rJSON_LIKELY(end_array_handler(context))) + { + in_object = (json->stack_top->type == RJSON_OBJECT); + continue; + } + return RJSON_ARRAY_END; + case RJSON_TRUE: + if (_rJSON_LIKELY(boolean_handler(context, true))) + continue; + return RJSON_TRUE; + case RJSON_FALSE: + if (_rJSON_LIKELY(boolean_handler(context, false))) + continue; + return RJSON_FALSE; + case RJSON_NULL: + if (_rJSON_LIKELY(null_handler(context))) + continue; + return RJSON_NULL; + case RJSON_ERROR: + return RJSON_ERROR; + case RJSON_DONE: + return RJSON_DONE; + } + } +} + +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)) +{ + const char *user_data[2]; + rjson_t json; + user_data[0] = string; + user_data[1] = string + strlen(string); + _rjson_setup(&json, _rjson_buffer_io, (void*)user_data, sizeof(json.input_buf)); + rjson_set_options(&json, option_flags); + if (rjson_parse(&json, context, + object_member_handler, string_handler, number_handler, + start_object_handler, end_object_handler, + start_array_handler, end_array_handler, + boolean_handler, null_handler) == RJSON_DONE) + return true; + if (error_handler) + error_handler(context, + (int)rjson_get_source_line(&json), + (int)rjson_get_source_column(&json), + rjson_get_error(&json)); + return false; +} + +struct rjsonwriter +{ + char* buf; + int buf_num, buf_cap; + + rjsonwriter_io_t io; + void *user_data; + + const char* error_text; + char option_flags; + char decimal_sep; + + char inline_buf[1024]; +}; + +rjsonwriter_t *rjsonwriter_open_user(rjsonwriter_io_t io, void *user_data) +{ + rjsonwriter_t* writer = (rjsonwriter_t*)malloc(sizeof(rjsonwriter_t)); + if (!writer) return NULL; + + writer->buf = writer->inline_buf; + writer->buf_num = 0; + writer->buf_cap = sizeof(writer->inline_buf); + + writer->option_flags = 0; + writer->decimal_sep = 0; + writer->error_text = NULL; + + writer->io = io; + writer->user_data = user_data; + + return writer; +} + +static int _rjsonwriter_stream_io(const void* buf, int len, void *user) +{ + return (int)intfstream_write((intfstream_t*)user, buf, (uint64_t)len); +} + +rjsonwriter_t *rjsonwriter_open_stream(struct intfstream_internal *stream) +{ + return rjsonwriter_open_user(_rjsonwriter_stream_io, stream); +} + +static int _rjsonwriter_rfile_io(const void* buf, int len, void *user) +{ + return (int)filestream_write((RFILE*)user, buf, (int64_t)len); +} + +rjsonwriter_t *rjsonwriter_open_rfile(RFILE *rfile) +{ + return rjsonwriter_open_user(_rjsonwriter_rfile_io, rfile); +} + +bool rjsonwriter_free(rjsonwriter_t *writer) +{ + bool res = rjsonwriter_flush(writer); + if (writer->buf != writer->inline_buf) + free(writer->buf); + free(writer); + return res; +} + +void rjsonwriter_set_options(rjsonwriter_t *writer, int rjsonwriter_option_flags) +{ + writer->option_flags = rjsonwriter_option_flags; +} + +bool rjsonwriter_flush(rjsonwriter_t *writer) +{ + if (writer->buf_num && !writer->error_text && writer->io(writer->buf, + writer->buf_num, writer->user_data) != writer->buf_num) + writer->error_text = "output error"; + writer->buf_num = 0; + return !writer->error_text; +} + +const char *rjsonwriter_get_error(rjsonwriter_t *writer) +{ + return (writer->error_text ? writer->error_text : ""); +} + +void rjsonwriter_raw(rjsonwriter_t *writer, const char *buf, int len) +{ + if (writer->buf_num + len > writer->buf_cap) + rjsonwriter_flush(writer); + if (len == 1) + { + if (buf[0] > ' ' || + !(writer->option_flags & RJSONWRITER_OPTION_SKIP_WHITESPACE)) + writer->buf[writer->buf_num++] = buf[0]; + return; + } + if (writer->buf_num > 0 || len < writer->buf_cap) + { + unsigned int add = (unsigned int)(writer->buf_cap - writer->buf_num); + if (add > (unsigned int)len) add = (unsigned int)len; + memcpy(writer->buf + writer->buf_num, buf, add); + writer->buf_num += add; + if ((unsigned int)len == add) return; + rjsonwriter_flush(writer); + len -= add; + buf += add; + } + if (len < writer->buf_cap) + { + memcpy(writer->buf, buf, len); + writer->buf_num += len; + } + else writer->io(buf, len, writer->user_data); +} + +void rjsonwriter_rawf(rjsonwriter_t *writer, const char *fmt, ...) +{ + int available, need; + va_list ap, ap2; + if (writer->buf_num >= writer->buf_cap - 16) + rjsonwriter_flush(writer); + available = (writer->buf_cap - writer->buf_num); + va_start(ap, fmt); + need = vsnprintf(writer->buf + writer->buf_num, available, fmt, ap); + va_end(ap); + if (need <= 0) return; + if (need < available) + { + writer->buf_num += need; + return; + } + rjsonwriter_flush(writer); + if (need >= writer->buf_cap) + { + char* newbuf = (char*)malloc(need + 1); + if (!newbuf) + { + if (!writer->error_text) writer->error_text = "out of memory"; + return; + } + if (writer->buf != writer->inline_buf) + free(writer->buf); + writer->buf = newbuf; + writer->buf_cap = need + 1; + } + va_start(ap2, fmt); + vsnprintf(writer->buf, writer->buf_cap, fmt, ap2); + va_end(ap2); + writer->buf_num = need; +} + +void rjsonwriter_add_string(rjsonwriter_t *writer, const char *value) +{ + const char *p = (const char*)value, *raw = p; + unsigned char c; + rjsonwriter_raw(writer, "\"", 1); + if (!p) goto string_end; + while ((c = (unsigned char)*p++) != '\0') + { + /* forward slash is special, it should be escaped if the previous character + * was a < (intended to avoid having html tags in JSON files) */ + if (c < 0x20 || c == '\"' || c == '\\' || + (c == '/' && p > value + 1 && p[-2] == '<')) + { + char esc_buf[8], esc_len = 2; + const char* esc; + if (raw != p - 1) + rjsonwriter_raw(writer, raw, (int)(p - 1 - raw)); + switch (c) + { + case '\b': esc = "\\b"; break; + case '\t': esc = "\\t"; break; + case '\n': esc = "\\n"; break; + case '\f': esc = "\\f"; break; + case '\r': esc = "\\r"; break; + case '\"': esc = "\\\""; break; + case '\\': esc = "\\\\"; break; + case '/': esc = "\\/"; break; + default: + snprintf(esc_buf, sizeof(esc_buf), "\\u%04x", c); + esc = esc_buf; + esc_len = 6; + } + rjsonwriter_raw(writer, esc, esc_len); + raw = p; + } + } + if (raw != p - 1) + rjsonwriter_raw(writer, raw, (int)(p - 1 - raw)); + string_end: + rjsonwriter_raw(writer, "\"", 1); +} + +void rjsonwriter_add_double(rjsonwriter_t *writer, double value) +{ + int old_buf_num = writer->buf_num; + rjsonwriter_rawf(writer, "%G", value); + if (writer->decimal_sep != '.') + { + /* handle locale that uses a non-standard decimal separator */ + char *p, *str; + if (writer->decimal_sep == 0) + { + char test[4]; + snprintf(test, sizeof(test), "%.1f", 0.0f); + if ((writer->decimal_sep = test[1]) == '.') return; + } + str = writer->buf + (old_buf_num > writer->buf_num ? 0 : old_buf_num); + if ((p = strchr(str, writer->decimal_sep)) != NULL) *p = '.'; + } +} + +void rjsonwriter_add_spaces(rjsonwriter_t *writer, int count) +{ + int add; + if (count <= 0 || (writer->option_flags & RJSONWRITER_OPTION_SKIP_WHITESPACE)) + return; + for (; (add = (count > 8 ? 8 : count)) != 0; count -= add) + rjsonwriter_raw(writer, " ", add); +} + +void rjsonwriter_add_tabs(rjsonwriter_t *writer, int count) +{ + int add; + if (count <= 0 || (writer->option_flags & RJSONWRITER_OPTION_SKIP_WHITESPACE)) + return; + for (; (add = (count > 8 ? 8 : count)) != 0; count -= add) + rjsonwriter_raw(writer, "\t\t\t\t\t\t\t\t", add); +} + +#undef _rJSON_EOF +#undef _rJSON_LIKELY diff --git a/libretro-common/include/formats/rjson.h b/libretro-common/include/formats/rjson.h new file mode 100644 index 0000000000..35d5a8d600 --- /dev/null +++ b/libretro-common/include/formats/rjson.h @@ -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 +#include /* INLINE */ +#include /* bool */ +#include /* 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 diff --git a/network/netplay/netplay_room_parse.c b/network/netplay/netplay_room_parse.c index 40e526051f..dd710da4f1 100644 --- a/network/netplay/netplay_room_parse.c +++ b/network/netplay/netplay_room_parse.c @@ -21,11 +21,11 @@ #include #include #include -#include +#include #include "netplay_discovery.h" #include "../../verbosity.h" -enum parse_state +enum netplay_parse_state { STATE_START = 0, STATE_ARRAY_START, @@ -41,113 +41,71 @@ struct netplay_rooms struct netplay_room *cur; }; -typedef struct tag_Context +struct netplay_json_context { - JSON_Parser parser; - char *cur_field; - void *cur_member; - enum parse_state state; -} Context; + bool *cur_member_bool; + int *cur_member_int; + int *cur_member_inthex; + char *cur_member_string; + size_t cur_member_size; + enum netplay_parse_state state; +}; /* TODO/FIXME - static global variable */ 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) - 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; + struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx; if (pCtx->state == STATE_FIELDS_OBJECT_START) - if (pCtx->cur_field) - *((bool*)pCtx->cur_member) = value; + if (pCtx->cur_member_bool) + *pCtx->cur_member_bool = value; - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSON_CALL StringHandler( - JSON_Parser parser, char* pValue, size_t length, - JSON_StringAttributes attributes) +static bool netplay_json_string(void* ctx, const char* pValue, size_t length) { - Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); - (void)parser; - (void)attributes; + struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx; if (pCtx->state == STATE_FIELDS_OBJECT_START) { if (pValue && length) { - if (pCtx->cur_field) + if (pCtx->cur_member_inthex) { /* CRC comes in as a string but it is stored * as an unsigned casted to int. */ - if (string_is_equal(pCtx->cur_field, "game_crc")) - *((int*)pCtx->cur_member) = (int)strtoul(pValue, NULL, 16); - else - strlcpy((char*)pCtx->cur_member, pValue, PATH_MAX_LENGTH); + *pCtx->cur_member_inthex = (int)strtoul(pValue, NULL, 16); + } + if (pCtx->cur_member_string) + { + strlcpy(pCtx->cur_member_string, pValue, pCtx->cur_member_size); } } } - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSON_CALL NumberHandler( - JSON_Parser parser, char* pValue, size_t length, JSON_NumberAttributes attributes) +static bool netplay_json_number(void* ctx, const char* pValue, size_t length) { - Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); - (void)parser; - (void)attributes; + struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx; if (pCtx->state == STATE_FIELDS_OBJECT_START) { if (pValue && length) - if (pCtx->cur_field) - *((int*)pCtx->cur_member) = (int)strtol(pValue, NULL, 10); + if (pCtx->cur_member_int) + *pCtx->cur_member_int = (int)strtol(pValue, NULL, 10); } - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSON_CALL SpecialNumberHandler( - JSON_Parser parser, JSON_SpecialNumber value) +static bool netplay_json_start_object(void* ctx) { - Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); - (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; + struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx; 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) 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) pCtx->state = STATE_ARRAY_START; - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSON_CALL ObjectMemberHandler(JSON_Parser parser, - char* pValue, size_t length, JSON_StringAttributes attributes) +static bool netplay_json_object_member(void* ctx, const char* pValue, size_t length) { - Context* pCtx = (Context*)JSON_Parser_GetUserData(parser); - (void)parser; - (void)attributes; + struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx; if (!pValue || !length) - return JSON_Parser_Continue; + return true; if (pCtx->state == STATE_OBJECT_START && !string_is_empty(pValue) && 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->cur_field) - free(pCtx->cur_field); - pCtx->cur_field = NULL; + pCtx->cur_member_bool = NULL; + pCtx->cur_member_int = NULL; + pCtx->cur_member_inthex = NULL; + pCtx->cur_member_string = NULL; if (!string_is_empty(pValue)) { if (string_is_equal(pValue, "username")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->nickname; + pCtx->cur_member_string = netplay_rooms_data->cur->nickname; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->nickname); } else if (string_is_equal(pValue, "game_name")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->gamename; + pCtx->cur_member_string = netplay_rooms_data->cur->gamename; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->gamename); } else if (string_is_equal(pValue, "core_name")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->corename; + pCtx->cur_member_string = netplay_rooms_data->cur->corename; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->corename); } else if (string_is_equal(pValue, "ip")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->address; + pCtx->cur_member_string = netplay_rooms_data->cur->address; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->address); } else if (string_is_equal(pValue, "port")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->port; + pCtx->cur_member_int = &netplay_rooms_data->cur->port; } else if (string_is_equal(pValue, "game_crc")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->gamecrc; + pCtx->cur_member_inthex = &netplay_rooms_data->cur->gamecrc; } else if (string_is_equal(pValue, "core_version")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->coreversion; + pCtx->cur_member_string = netplay_rooms_data->cur->coreversion; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->coreversion); } else if (string_is_equal(pValue, "has_password")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->has_password; + pCtx->cur_member_bool = &netplay_rooms_data->cur->has_password; } else if (string_is_equal(pValue, "has_spectate_password")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->has_spectate_password; + pCtx->cur_member_bool = &netplay_rooms_data->cur->has_spectate_password; } else if (string_is_equal(pValue, "fixed")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->fixed; + pCtx->cur_member_bool = &netplay_rooms_data->cur->fixed; } else if (string_is_equal(pValue, "mitm_ip")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->mitm_address; + pCtx->cur_member_string = 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")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->mitm_port; + pCtx->cur_member_int = &netplay_rooms_data->cur->mitm_port; } else if (string_is_equal(pValue, "host_method")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->host_method; + pCtx->cur_member_int = &netplay_rooms_data->cur->host_method; } else if (string_is_equal(pValue, "retroarch_version")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->retroarch_version; + pCtx->cur_member_string = netplay_rooms_data->cur->retroarch_version; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->retroarch_version); } else if (string_is_equal(pValue, "country")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->country; + pCtx->cur_member_string = netplay_rooms_data->cur->country; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->country); } else if (string_is_equal(pValue, "frontend")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->frontend; + pCtx->cur_member_string = netplay_rooms_data->cur->frontend; + pCtx->cur_member_size = sizeof(netplay_rooms_data->cur->frontend); } else if (string_is_equal(pValue, "subsystem_name")) { - pCtx->cur_field = strdup(pValue); - pCtx->cur_member = &netplay_rooms_data->cur->subsystem_name; + pCtx->cur_member_string = 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); - (void)parser; + struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx; if (pCtx->state == STATE_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); - (void)parser; - (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; + RARCH_ERR("[netplay] Error: Invalid JSON at line %d, column %d - %s.\n", + line, col, error); } void netplay_rooms_free(void) @@ -392,7 +282,7 @@ void netplay_rooms_free(void) int netplay_rooms_parse(const char *buf) { - Context ctx; + struct netplay_json_context ctx; memset(&ctx, 0, sizeof(ctx)); @@ -404,17 +294,17 @@ int netplay_rooms_parse(const char *buf) netplay_rooms_data = (struct netplay_rooms*) calloc(1, sizeof(*netplay_rooms_data)); - ctx.parser = JSON_Parser_Create(NULL); - - if (!ctx.parser) - { - RARCH_ERR("could not allocate memory for JSON parser.\n"); - return 1; - } - - parse_context_setup(&ctx); - json_parse(&ctx, buf); - parse_context_free(&ctx); + rjson_parse_quick(buf, &ctx, 0, + netplay_json_object_member, + netplay_json_string, + netplay_json_number, + netplay_json_start_object, + netplay_json_end_object, + netplay_json_start_array, + NULL /* end_array_handler */, + netplay_json_boolean, + NULL /* null handler */, + netplay_rooms_error); return 0; } diff --git a/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj index 1003e827e6..d77b11907e 100644 --- a/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj @@ -147,7 +147,7 @@ 92B9EB8024E0518700E6CFB2 /* net_http_parse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = net_http_parse.h; sourceTree = ""; }; 92B9EB8124E0518700E6CFB2 /* retro_timers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = retro_timers.h; sourceTree = ""; }; 92B9EB8324E0518700E6CFB2 /* m3u_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m3u_file.h; sourceTree = ""; }; - 92B9EB8424E0518700E6CFB2 /* jsonsax.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jsonsax.h; sourceTree = ""; }; + 92B9EB8424E0518700E6CFB2 /* rjson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjson.h; sourceTree = ""; }; 92B9EB8524E0518700E6CFB2 /* rxml.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rxml.h; sourceTree = ""; }; 92B9EB8624E0518700E6CFB2 /* cdfs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cdfs.h; sourceTree = ""; }; 92B9EB8724E0518700E6CFB2 /* rpng.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rpng.h; sourceTree = ""; }; @@ -156,7 +156,6 @@ 92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = logiqx_dat.h; sourceTree = ""; }; 92B9EB8B24E0518700E6CFB2 /* rbmp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rbmp.h; sourceTree = ""; }; 92B9EB8C24E0518700E6CFB2 /* rwav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rwav.h; sourceTree = ""; }; - 92B9EB8D24E0518700E6CFB2 /* jsonsax_full.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jsonsax_full.h; sourceTree = ""; }; 92B9EB8E24E0518700E6CFB2 /* rjpeg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjpeg.h; sourceTree = ""; }; 92B9EB9024E0518700E6CFB2 /* rglgen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rglgen.h; sourceTree = ""; }; 92B9EB9124E0518700E6CFB2 /* glsym_es2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glsym_es2.h; sourceTree = ""; }; @@ -580,7 +579,7 @@ isa = PBXGroup; children = ( 92B9EB8324E0518700E6CFB2 /* m3u_file.h */, - 92B9EB8424E0518700E6CFB2 /* jsonsax.h */, + 92B9EB8424E0518700E6CFB2 /* rjson.h */, 92B9EB8524E0518700E6CFB2 /* rxml.h */, 92B9EB8624E0518700E6CFB2 /* cdfs.h */, 92B9EB8724E0518700E6CFB2 /* rpng.h */, @@ -589,7 +588,6 @@ 92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */, 92B9EB8B24E0518700E6CFB2 /* rbmp.h */, 92B9EB8C24E0518700E6CFB2 /* rwav.h */, - 92B9EB8D24E0518700E6CFB2 /* jsonsax_full.h */, 92B9EB8E24E0518700E6CFB2 /* rjpeg.h */, ); path = formats; diff --git a/playlist.c b/playlist.c index c9a3add3c0..63a0ccd813 100644 --- a/playlist.c +++ b/playlist.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include "playlist.h" @@ -80,20 +80,12 @@ struct content_playlist typedef struct { struct playlist_entry *current_entry; - char *current_meta_string; - char *current_items_string; - char **current_entry_val; - char **current_meta_val; - int *current_entry_int_val; + char **current_string_val; unsigned *current_entry_uint_val; - struct string_list **current_entry_string_list_val; enum playlist_label_display_mode *current_meta_label_display_mode_val; enum playlist_thumbnail_mode *current_meta_thumbnail_mode_val; enum playlist_sort_mode *current_meta_sort_mode_val; - intfstream_t *file; playlist_t *playlist; - JSON_Parser parser; /* ptr alignment */ - JSON_Writer writer; /* ptr alignment */ unsigned array_depth; unsigned object_depth; @@ -1181,39 +1173,11 @@ success: return true; } -static JSON_Writer_HandlerResult JSONOutputHandler(JSON_Writer writer, const char *pBytes, size_t length) -{ - JSONContext *context = (JSONContext*)JSON_Writer_GetUserData(writer); - - (void)writer; /* unused */ - return intfstream_write(context->file, pBytes, length) == length ? JSON_Writer_Continue : JSON_Writer_Abort; -} - -static void JSONLogError(JSONContext *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_WARN("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_WARN("Error: could not write output - %s.\n", JSON_ErrorString(JSON_Writer_GetError(pCtx->writer))); - } -} - void playlist_write_runtime_file(playlist_t *playlist) { size_t i, len; intfstream_t *file = NULL; - JSONContext context = {0}; + rjsonwriter_t* writer; if (!playlist || !playlist->modified) return; @@ -1227,211 +1191,137 @@ void playlist_write_runtime_file(playlist_t *playlist) return; } - context.writer = JSON_Writer_Create(NULL); - context.file = file; - - if (!context.writer) + writer = rjsonwriter_open_stream(file); + if (!writer) { RARCH_ERR("Failed to create JSON writer\n"); goto end; } - JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8); - JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler); - JSON_Writer_SetUserData(context.writer, &context); - - JSON_Writer_WriteStartObject(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteString(context.writer, "version", - STRLEN_CONST("version"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, "1.0", - STRLEN_CONST("1.0"), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteString(context.writer, "items", - STRLEN_CONST("items"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteStartArray(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "version"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, "1.0"); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "items"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_start_array(writer); + rjsonwriter_add_newline(writer); for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { - JSON_Writer_WriteSpace(context.writer, 4); - JSON_Writer_WriteStartObject(context.writer); + rjsonwriter_add_spaces(writer, 4); + rjsonwriter_add_start_object(writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "path", - STRLEN_CONST("path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].path - ? playlist->entries[i].path - : "", - playlist->entries[i].path - ? strlen(playlist->entries[i].path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].path); + rjsonwriter_add_comma(writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "core_path", - STRLEN_CONST("core_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].core_path - ? playlist->entries[i].core_path - : "", - playlist->entries[i].core_path - ? strlen(playlist->entries[i].core_path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "core_path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].core_path); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - { - char tmp[32] = {0}; + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "runtime_hours"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].runtime_hours); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_hours); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "runtime_minutes"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].runtime_minutes); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "runtime_hours", - STRLEN_CONST("runtime_hours"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "runtime_seconds"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].runtime_seconds); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - memset(tmp, 0, sizeof(tmp)); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "last_played_year"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_year); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_minutes); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "last_played_month"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_month); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "runtime_minutes", - STRLEN_CONST("runtime_minutes"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "last_played_day"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_day); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - memset(tmp, 0, sizeof(tmp)); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "last_played_hour"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_hour); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_seconds); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "last_played_minute"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_minute); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "runtime_seconds", - STRLEN_CONST("runtime_seconds"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "last_played_second"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_second); + rjsonwriter_add_newline(writer); - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_year); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_year", - STRLEN_CONST("last_played_year"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_month); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_month", - STRLEN_CONST("last_played_month"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_day); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_day", - STRLEN_CONST("last_played_day"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, - strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_hour); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_hour", - STRLEN_CONST("last_played_hour"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_minute); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_minute", - STRLEN_CONST("last_played_minute"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_second); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_second", - STRLEN_CONST("last_played_second"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, - strlen(tmp), JSON_UTF8); - JSON_Writer_WriteNewLine(context.writer); - } - - JSON_Writer_WriteSpace(context.writer, 4); - JSON_Writer_WriteEndObject(context.writer); + rjsonwriter_add_spaces(writer, 4); + rjsonwriter_add_end_object(writer); if (i < len - 1) - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_comma(writer); - JSON_Writer_WriteNewLine(context.writer); + rjsonwriter_add_newline(writer); } - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteEndArray(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteEndObject(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_Free(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_end_array(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_end_object(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_free(writer); playlist->modified = false; playlist->old_format = false; @@ -1443,18 +1333,6 @@ end: free(file); } -/* No-op versions of JSON whitespace writers, - * used when generating compressed output */ -static JSON_Status JSON_CALL JSON_Writer_WriteNewLine_NULL(JSON_Writer writer) -{ - return JSON_Success; -} - -static JSON_Status JSON_CALL JSON_Writer_WriteSpace_NULL(JSON_Writer writer, size_t numberOfSpaces) -{ - return JSON_Success; -} - void playlist_write_file(playlist_t *playlist) { size_t i, len; @@ -1530,283 +1408,169 @@ void playlist_write_file(playlist_t *playlist) else #endif { - char uint_str[4]; - JSONContext context = {0}; - - /* Assign JSON whitespace functions - * > When compressing playlists, human readability - * is not a factor - can skip all indentation - * and new line characters - * > Create these function pointers locally to - * ensure thread safety */ - JSON_Status (JSON_CALL *json_write_new_line)(JSON_Writer writer) = - compressed ? - JSON_Writer_WriteNewLine_NULL : - JSON_Writer_WriteNewLine; - JSON_Status (JSON_CALL *json_write_space)(JSON_Writer writer, size_t numberOfSpaces) = - compressed ? - JSON_Writer_WriteSpace_NULL : - JSON_Writer_WriteSpace; - - context.writer = JSON_Writer_Create(NULL); - context.file = file; - - if (!context.writer) + rjsonwriter_t* writer = rjsonwriter_open_stream(file); + if (!writer) { RARCH_ERR("Failed to create JSON writer\n"); goto end; } + if (compressed) + { + /* When compressing playlists, human readability + * is not a factor - can skip all indentation + * and new line characters */ + rjsonwriter_set_options(writer, RJSONWRITER_OPTION_SKIP_WHITESPACE); + } - JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8); - JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler); - JSON_Writer_SetUserData(context.writer, &context); + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); - JSON_Writer_WriteStartObject(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "version"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, "1.4"); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "version", - STRLEN_CONST("version"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, "1.4", - STRLEN_CONST("1.4"), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "default_core_path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->default_core_path); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "default_core_path", - STRLEN_CONST("default_core_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->default_core_path - ? playlist->default_core_path - : "", - playlist->default_core_path - ? strlen(playlist->default_core_path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "default_core_name", - STRLEN_CONST("default_core_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->default_core_name - ? playlist->default_core_name - : "", - playlist->default_core_name - ? strlen(playlist->default_core_name) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "default_core_name"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->default_core_name); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); if (!string_is_empty(playlist->base_content_directory)) { - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "base_content_directory", - STRLEN_CONST("base_content_directory"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->base_content_directory, - strlen(playlist->base_content_directory), - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "base_content_directory"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->base_content_directory); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); } - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->label_display_mode); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "label_display_mode"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_int(writer, (int)playlist->label_display_mode); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "label_display_mode", - STRLEN_CONST("label_display_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "right_thumbnail_mode"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_int(writer, (int)playlist->right_thumbnail_mode); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->right_thumbnail_mode); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "left_thumbnail_mode"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_int(writer, (int)playlist->left_thumbnail_mode); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "right_thumbnail_mode", - STRLEN_CONST("right_thumbnail_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "sort_mode"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_int(writer, (int)playlist->sort_mode); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->left_thumbnail_mode); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "left_thumbnail_mode", - STRLEN_CONST("left_thumbnail_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->sort_mode); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "sort_mode", - STRLEN_CONST("sort_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "items", - STRLEN_CONST("items"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteStartArray(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "items"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_start_array(writer); + rjsonwriter_add_newline(writer); for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { - json_write_space(context.writer, 4); - JSON_Writer_WriteStartObject(context.writer); + rjsonwriter_add_spaces(writer, 4); + rjsonwriter_add_start_object(writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "path", - STRLEN_CONST("path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].path - ? playlist->entries[i].path - : "", - playlist->entries[i].path - ? strlen(playlist->entries[i].path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].path); + rjsonwriter_add_comma(writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "label", - STRLEN_CONST("label"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].label - ? playlist->entries[i].label - : "", - playlist->entries[i].label - ? strlen(playlist->entries[i].label) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "label"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].label); + rjsonwriter_add_comma(writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "core_path", - STRLEN_CONST("core_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].core_path - ? playlist->entries[i].core_path - : "", - playlist->entries[i].core_path - ? strlen(playlist->entries[i].core_path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "core_path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].core_path); + rjsonwriter_add_comma(writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "core_name", - STRLEN_CONST("core_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].core_name - ? playlist->entries[i].core_name - : "", - playlist->entries[i].core_name - ? strlen(playlist->entries[i].core_name) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "core_name"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].core_name); + rjsonwriter_add_comma(writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "crc32", - STRLEN_CONST("crc32"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, playlist->entries[i].crc32 ? playlist->entries[i].crc32 : "", - playlist->entries[i].crc32 - ? strlen(playlist->entries[i].crc32) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "crc32"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].crc32); + rjsonwriter_add_comma(writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "db_name", - STRLEN_CONST("db_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, playlist->entries[i].db_name ? playlist->entries[i].db_name : "", - playlist->entries[i].db_name - ? strlen(playlist->entries[i].db_name) - : 0, - JSON_UTF8); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "db_name"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].db_name); if (!string_is_empty(playlist->entries[i].subsystem_ident)) { - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "subsystem_ident", - STRLEN_CONST("subsystem_ident"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, playlist->entries[i].subsystem_ident ? playlist->entries[i].subsystem_ident : "", - playlist->entries[i].subsystem_ident - ? strlen(playlist->entries[i].subsystem_ident) - : 0, - JSON_UTF8); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "subsystem_ident"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].subsystem_ident); } if (!string_is_empty(playlist->entries[i].subsystem_name)) { - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "subsystem_name", - STRLEN_CONST("subsystem_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].subsystem_name - ? playlist->entries[i].subsystem_name - : "", - playlist->entries[i].subsystem_name - ? strlen(playlist->entries[i].subsystem_name) - : 0, JSON_UTF8); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "subsystem_name"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].subsystem_name); } if ( playlist->entries[i].subsystem_roms && @@ -1814,58 +1578,57 @@ void playlist_write_file(playlist_t *playlist) { unsigned j; - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "subsystem_roms", - STRLEN_CONST("subsystem_roms"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteStartArray(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "subsystem_roms"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_start_array(writer); + rjsonwriter_add_newline(writer); for (j = 0; j < playlist->entries[i].subsystem_roms->size; j++) { const struct string_list *roms = playlist->entries[i].subsystem_roms; - json_write_space(context.writer, 8); - JSON_Writer_WriteString(context.writer, + rjsonwriter_add_spaces(writer, 8); + rjsonwriter_add_string(writer, !string_is_empty(roms->elems[j].data) ? roms->elems[j].data - : "", - !string_is_empty(roms->elems[j].data) - ? strlen(roms->elems[j].data) - : 0, - JSON_UTF8); + : ""); if (j < playlist->entries[i].subsystem_roms->size - 1) { - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); } } - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteEndArray(context.writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_end_array(writer); } - json_write_new_line(context.writer); + rjsonwriter_add_newline(writer); - json_write_space(context.writer, 4); - JSON_Writer_WriteEndObject(context.writer); + rjsonwriter_add_spaces(writer, 4); + rjsonwriter_add_end_object(writer); if (i < len - 1) - JSON_Writer_WriteComma(context.writer); + rjsonwriter_add_comma(writer); - json_write_new_line(context.writer); + rjsonwriter_add_newline(writer); } - json_write_space(context.writer, 2); - JSON_Writer_WriteEndArray(context.writer); - json_write_new_line(context.writer); - JSON_Writer_WriteEndObject(context.writer); - json_write_new_line(context.writer); - JSON_Writer_Free(context.writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_end_array(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_end_object(writer); + rjsonwriter_add_newline(writer); + + if (!rjsonwriter_free(writer)) + { + RARCH_ERR("Failed to write to playlist file: %s\n", playlist->config.path); + } playlist->old_format = false; } @@ -1970,62 +1733,38 @@ size_t playlist_capacity(playlist_t *playlist) return playlist->config.capacity; } -static JSON_Parser_HandlerResult JSONStartArrayHandler(JSON_Parser parser) +static bool JSONStartArrayHandler(void *context) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + JSONContext *pCtx = (JSONContext *)context; pCtx->array_depth++; - if (pCtx->object_depth == 1) - { - if (string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 1) - pCtx->in_items = true; - } - else if (pCtx->object_depth == 2) - { - if (pCtx->array_depth == 2) - if (string_is_equal(pCtx->current_items_string, "subsystem_roms")) - pCtx->in_subsystem_roms = true; - } - - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSONEndArrayHandler(JSON_Parser parser) +static bool JSONEndArrayHandler(void *context) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + JSONContext *pCtx = (JSONContext *)context; retro_assert(pCtx->array_depth > 0); pCtx->array_depth--; - if (pCtx->object_depth == 1) + if (pCtx->in_items && pCtx->array_depth == 0 && pCtx->object_depth <= 1) { - if (pCtx->in_items && string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 0) - { - free(pCtx->current_meta_string); - pCtx->current_meta_string = NULL; - pCtx->in_items = false; - - if (pCtx->current_items_string) - { - free(pCtx->current_items_string); - pCtx->current_items_string = NULL; - } - } + pCtx->in_items = false; } - else if (pCtx->object_depth == 2) + else if (pCtx->in_subsystem_roms && pCtx->array_depth <= 1 && pCtx->object_depth <= 2) { - if (pCtx->in_subsystem_roms && string_is_equal(pCtx->current_items_string, "subsystem_roms") && pCtx->array_depth == 1) - pCtx->in_subsystem_roms = false; + pCtx->in_subsystem_roms = false; } - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser) +static bool JSONStartObjectHandler(void *context) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + JSONContext *pCtx = (JSONContext *)context; pCtx->object_depth++; @@ -2041,7 +1780,7 @@ static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser) if (!RBUF_TRYFIT(pCtx->playlist->entries, len + 1)) { pCtx->out_of_memory = true; - return JSON_Parser_Abort; + return false; } pCtx->current_entry = &pCtx->playlist->entries[len]; memset(pCtx->current_entry, 0, sizeof(*pCtx->current_entry)); @@ -2063,12 +1802,12 @@ static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser) } } - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser) +static bool JSONEndObjectHandler(void *context) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + JSONContext *pCtx = (JSONContext *)context; if (pCtx->in_items && pCtx->object_depth == 2) { @@ -2081,35 +1820,34 @@ static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser) pCtx->object_depth--; - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSONStringHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) +static bool JSONStringHandler(void *context, const char *pValue, size_t length) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - (void)attributes; /* unused */ + JSONContext *pCtx = (JSONContext *)context; if (pCtx->in_items && pCtx->in_subsystem_roms && pCtx->object_depth == 2 && pCtx->array_depth == 2) { - if (pCtx->current_entry_string_list_val && length && !string_is_empty(pValue)) + if (length && !string_is_empty(pValue)) { union string_list_elem_attr attr = {0}; - if (!*pCtx->current_entry_string_list_val) - *pCtx->current_entry_string_list_val = string_list_new(); + if (!pCtx->current_entry->subsystem_roms) + pCtx->current_entry->subsystem_roms = string_list_new(); - string_list_append(*pCtx->current_entry_string_list_val, pValue, attr); + string_list_append(pCtx->current_entry->subsystem_roms, pValue, attr); } } else if (pCtx->in_items && pCtx->object_depth == 2) { if (pCtx->array_depth == 1) { - if (pCtx->current_entry_val && length && !string_is_empty(pValue)) + if (pCtx->current_string_val && length && !string_is_empty(pValue)) { - if (*pCtx->current_entry_val) - free(*pCtx->current_entry_val); - *pCtx->current_entry_val = strdup(pValue); + if (*pCtx->current_string_val) + free(*pCtx->current_string_val); + *pCtx->current_string_val = strdup(pValue); } } } @@ -2117,42 +1855,30 @@ static JSON_Parser_HandlerResult JSONStringHandler(JSON_Parser parser, char *pVa { if (pCtx->array_depth == 0) { - if (pCtx->current_meta_val && length && !string_is_empty(pValue)) + if (pCtx->current_string_val && length && !string_is_empty(pValue)) { /* handle any top-level playlist metadata here */ -#if 0 - RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue); -#endif - - free(pCtx->current_meta_string); - pCtx->current_meta_string = NULL; - - if (*pCtx->current_meta_val) - free(*pCtx->current_meta_val); - - *pCtx->current_meta_val = strdup(pValue); + if (*pCtx->current_string_val) + free(*pCtx->current_string_val); + *pCtx->current_string_val = strdup(pValue); } } } - pCtx->current_entry_val = NULL; - pCtx->current_meta_val = NULL; + pCtx->current_string_val = NULL; - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) +static bool JSONNumberHandler(void *context, const char *pValue, size_t length) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - (void)attributes; /* unused */ + JSONContext *pCtx = (JSONContext *)context; if (pCtx->in_items && pCtx->object_depth == 2) { - if (pCtx->array_depth == 1) + if (pCtx->array_depth == 1 && length && !string_is_empty(pValue)) { - if (pCtx->current_entry_int_val && length && !string_is_empty(pValue)) - *pCtx->current_entry_int_val = (int)strtoul(pValue, NULL, 10); - else if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue)) + if (pCtx->current_entry_uint_val) *pCtx->current_entry_uint_val = (unsigned)strtoul(pValue, NULL, 10); } } @@ -2160,16 +1886,9 @@ static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pVa { if (pCtx->array_depth == 0) { - if (pCtx->current_meta_string && length && !string_is_empty(pValue)) + if (length && !string_is_empty(pValue)) { /* handle any top-level playlist metadata here */ -#if 0 - RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue); -#endif - - free(pCtx->current_meta_string); - pCtx->current_meta_string = NULL; - if (pCtx->current_meta_label_display_mode_val) *pCtx->current_meta_label_display_mode_val = (enum playlist_label_display_mode)strtoul(pValue, NULL, 10); else if (pCtx->current_meta_thumbnail_mode_val) @@ -2180,134 +1899,128 @@ static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pVa } } - pCtx->current_entry_int_val = NULL; pCtx->current_entry_uint_val = NULL; pCtx->current_meta_label_display_mode_val = NULL; pCtx->current_meta_thumbnail_mode_val = NULL; pCtx->current_meta_sort_mode_val = NULL; - return JSON_Parser_Continue; + return true; } -static JSON_Parser_HandlerResult JSONObjectMemberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) +static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t length) { - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - (void)attributes; /* unused */ + JSONContext *pCtx = (JSONContext *)context; if (pCtx->in_items && pCtx->object_depth == 2) { if (pCtx->array_depth == 1) { - if (pCtx->current_entry_val) + if (pCtx->current_string_val) { /* something went wrong */ - RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__); - return JSON_Parser_Abort; + return false; } - if (length) + if (length && !pCtx->capacity_exceeded) { - if (!string_is_empty(pValue)) + pCtx->current_string_val = NULL; + pCtx->current_entry_uint_val = NULL; + pCtx->in_subsystem_roms = false; + switch (pValue[0]) { - if (!string_is_empty(pCtx->current_items_string)) - free(pCtx->current_items_string); - pCtx->current_items_string = strdup(pValue); - } - - if (!pCtx->capacity_exceeded) - { - if (string_is_equal(pValue, "path")) - pCtx->current_entry_val = &pCtx->current_entry->path; - else if (string_is_equal(pValue, "label")) - pCtx->current_entry_val = &pCtx->current_entry->label; - else if (string_is_equal(pValue, "core_path")) - pCtx->current_entry_val = &pCtx->current_entry->core_path; - else if (string_is_equal(pValue, "core_name")) - pCtx->current_entry_val = &pCtx->current_entry->core_name; - else if (string_is_equal(pValue, "crc32")) - pCtx->current_entry_val = &pCtx->current_entry->crc32; - else if (string_is_equal(pValue, "db_name")) - pCtx->current_entry_val = &pCtx->current_entry->db_name; - else if (string_starts_with_size(pValue, "subsystem_", STRLEN_CONST("subsystem_"))) - { - if (string_is_equal(pValue, "subsystem_ident")) - pCtx->current_entry_val = &pCtx->current_entry->subsystem_ident; - else if (string_is_equal(pValue, "subsystem_name")) - pCtx->current_entry_val = &pCtx->current_entry->subsystem_name; - else if (string_is_equal(pValue, "subsystem_roms")) - pCtx->current_entry_string_list_val = &pCtx->current_entry->subsystem_roms; - } - else if (string_starts_with_size(pValue, "runtime_", STRLEN_CONST("runtime_"))) - { - if (string_is_equal(pValue, "runtime_hours")) - pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours; - else if (string_is_equal(pValue, "runtime_minutes")) - pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes; - else if (string_is_equal(pValue, "runtime_seconds")) - pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds; - } - else if (string_starts_with_size(pValue, "last_played_", STRLEN_CONST("last_played_"))) - { - if (string_is_equal(pValue, "last_played_year")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year; - else if (string_is_equal(pValue, "last_played_month")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month; - else if (string_is_equal(pValue, "last_played_day")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day; - else if (string_is_equal(pValue, "last_played_hour")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour; - else if (string_is_equal(pValue, "last_played_minute")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute; - else if (string_is_equal(pValue, "last_played_second")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second; - } - } - else - { - pCtx->current_entry_val = NULL; - pCtx->current_entry_uint_val = NULL; - pCtx->current_entry_string_list_val = NULL; + case 'c': + if (string_is_equal(pValue, "core_name")) + pCtx->current_string_val = &pCtx->current_entry->core_name; + else if (string_is_equal(pValue, "core_path")) + pCtx->current_string_val = &pCtx->current_entry->core_path; + else if (string_is_equal(pValue, "crc32")) + pCtx->current_string_val = &pCtx->current_entry->crc32; + break; + case 'd': + if (string_is_equal(pValue, "db_name")) + pCtx->current_string_val = &pCtx->current_entry->db_name; + break; + case 'l': + if (string_is_equal(pValue, "label")) + pCtx->current_string_val = &pCtx->current_entry->label; + else if (string_is_equal(pValue, "last_played_day")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day; + else if (string_is_equal(pValue, "last_played_hour")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour; + else if (string_is_equal(pValue, "last_played_minute")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute; + else if (string_is_equal(pValue, "last_played_month")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month; + else if (string_is_equal(pValue, "last_played_second")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second; + else if (string_is_equal(pValue, "last_played_year")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year; + break; + case 'p': + if (string_is_equal(pValue, "path")) + pCtx->current_string_val = &pCtx->current_entry->path; + break; + case 'r': + if (string_is_equal(pValue, "runtime_hours")) + pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours; + else if (string_is_equal(pValue, "runtime_minutes")) + pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes; + else if (string_is_equal(pValue, "runtime_seconds")) + pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds; + break; + case 's': + if (string_is_equal(pValue, "subsystem_ident")) + pCtx->current_string_val = &pCtx->current_entry->subsystem_ident; + else if (string_is_equal(pValue, "subsystem_name")) + pCtx->current_string_val = &pCtx->current_entry->subsystem_name; + else if (string_is_equal(pValue, "subsystem_roms")) + pCtx->in_subsystem_roms = true; + break; } } } } - else if (pCtx->object_depth == 1) + else if (pCtx->object_depth == 1 && pCtx->array_depth == 0 && length) { - if (pCtx->array_depth == 0) + pCtx->current_string_val = NULL; + pCtx->current_meta_label_display_mode_val = NULL; + pCtx->current_meta_thumbnail_mode_val = NULL; + pCtx->current_meta_sort_mode_val = NULL; + pCtx->in_items = false; + switch (pValue[0]) { - if (pCtx->current_meta_val) - { - /* something went wrong */ - RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__); - return JSON_Parser_Abort; - } - - if (length) - { - if (pCtx->current_meta_string) - free(pCtx->current_meta_string); - pCtx->current_meta_string = strdup(pValue); - + case 'b': + if (string_is_equal(pValue, "base_content_directory")) + pCtx->current_string_val = &pCtx->playlist->base_content_directory; + break; + case 'd': if (string_is_equal(pValue, "default_core_path")) - pCtx->current_meta_val = &pCtx->playlist->default_core_path; + pCtx->current_string_val = &pCtx->playlist->default_core_path; else if (string_is_equal(pValue, "default_core_name")) - pCtx->current_meta_val = &pCtx->playlist->default_core_name; - else if (string_is_equal(pValue, "label_display_mode")) + pCtx->current_string_val = &pCtx->playlist->default_core_name; + break; + case 'i': + if (string_is_equal(pValue, "items")) + pCtx->in_items = true; + break; + case 'l': + if (string_is_equal(pValue, "label_display_mode")) pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode; - else if (string_is_equal(pValue, "right_thumbnail_mode")) - pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode; else if (string_is_equal(pValue, "left_thumbnail_mode")) pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode; - else if (string_is_equal(pValue, "sort_mode")) + break; + case 'r': + if (string_is_equal(pValue, "right_thumbnail_mode")) + pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode; + break; + case 's': + if (string_is_equal(pValue, "sort_mode")) pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode; - else if (string_is_equal(pValue, "base_content_directory")) - pCtx->current_meta_val = &pCtx->playlist->base_content_directory; - - } + break; } } - return JSON_Parser_Continue; + return true; } static void get_old_format_metadata_value( @@ -2389,92 +2102,51 @@ static bool playlist_read_file(playlist_t *playlist) if (!playlist->old_format) { + rjson_t* parser; JSONContext context = {0}; - context.parser = JSON_Parser_Create(NULL); - context.file = file; context.playlist = playlist; - if (!context.parser) + parser = rjson_open_stream(file); + if (!parser) { RARCH_ERR("Failed to create JSON parser\n"); goto end; } -#if 0 - JSON_Parser_SetTrackObjectMembers(context.parser, JSON_True); -#endif - JSON_Parser_SetAllowBOM(context.parser, JSON_True); - JSON_Parser_SetAllowComments(context.parser, JSON_True); - JSON_Parser_SetAllowSpecialNumbers(context.parser, JSON_True); - JSON_Parser_SetAllowHexNumbers(context.parser, JSON_True); - JSON_Parser_SetAllowUnescapedControlCharacters(context.parser, JSON_True); - JSON_Parser_SetReplaceInvalidEncodingSequences(context.parser, JSON_True); + rjson_set_options(parser, + RJSON_OPTION_ALLOW_UTF8BOM + | RJSON_OPTION_ALLOW_COMMENTS + | RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS + | RJSON_OPTION_REPLACE_INVALID_ENCODING); -#if 0 - JSON_Parser_SetNullHandler(context.parser, &JSONNullHandler); - JSON_Parser_SetBooleanHandler(context.parser, &JSONBooleanHandler); - JSON_Parser_SetSpecialNumberHandler(context.parser, &JSONSpecialNumberHandler); - JSON_Parser_SetArrayItemHandler(context.parser, &JSONArrayItemHandler); -#endif - - JSON_Parser_SetNumberHandler(context.parser, &JSONNumberHandler); - JSON_Parser_SetStringHandler(context.parser, &JSONStringHandler); - JSON_Parser_SetStartObjectHandler(context.parser, &JSONStartObjectHandler); - JSON_Parser_SetEndObjectHandler(context.parser, &JSONEndObjectHandler); - JSON_Parser_SetObjectMemberHandler(context.parser, &JSONObjectMemberHandler); - JSON_Parser_SetStartArrayHandler(context.parser, &JSONStartArrayHandler); - JSON_Parser_SetEndArrayHandler(context.parser, &JSONEndArrayHandler); - JSON_Parser_SetUserData(context.parser, &context); - - while (!intfstream_eof(file)) + if (rjson_parse(parser, &context, + JSONObjectMemberHandler, + JSONStringHandler, + JSONNumberHandler, + JSONStartObjectHandler, + JSONEndObjectHandler, + JSONStartArrayHandler, + JSONEndArrayHandler, + NULL, NULL) /* unused boolean/null handlers */ + != RJSON_DONE) { - char chunk[4096] = {0}; - int64_t length = intfstream_read(file, chunk, sizeof(chunk)); - - if (!length && !intfstream_eof(file)) + if (context.out_of_memory) { - RARCH_WARN("Could not read JSON input.\n"); - goto json_cleanup; + RARCH_WARN("Ran out of memory while parsing JSON playlist\n"); + res = false; } - - if (!JSON_Parser_Parse(context.parser, chunk, - (size_t)length, JSON_False)) + else { - if (context.out_of_memory) - { - RARCH_WARN("Ran out of memory while parsing JSON playlist\n"); - res = false; - goto json_cleanup; - } - /* Note: Chunk may not be null-terminated. - * It is therefore dangerous to print its contents. - * Setting a size limit here mitigates the issue, but - * in general this is not good practice... - * Addendum: RARCH_WARN() actually limits the printed - * buffer size anyway, so this warning message is most - * likely worthless... */ - RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", 4096, chunk); - JSONLogError(&context); - goto json_cleanup; + RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", + rjson_get_source_context_len(parser), + rjson_get_source_context_buf(parser)); + RARCH_WARN("Error: Invalid JSON at line %d, column %d - %s.\n", + (int)rjson_get_source_line(parser), + (int)rjson_get_source_column(parser), + (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error")); } } - - if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True)) - { - RARCH_WARN("Error parsing JSON.\n"); - JSONLogError(&context); - goto json_cleanup; - } - -json_cleanup: - - JSON_Parser_Free(context.parser); - - if (context.current_meta_string) - free(context.current_meta_string); - - if (context.current_items_string) - free(context.current_items_string); + rjson_free(parser); } else { diff --git a/runtime_file.c b/runtime_file.c index 7fc8e0f99c..491e011db2 100644 --- a/runtime_file.c +++ b/runtime_file.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include