#include "cheevos_parser.h" #include "util.h" #include #include #include #include /* C89 wants only int values in enums. */ #define CHEEVOS_JSON_KEY_GAMEID 0xb4960eecU #define CHEEVOS_JSON_KEY_ACHIEVEMENTS 0x69749ae1U #define CHEEVOS_JSON_KEY_ID 0x005973f2U #define CHEEVOS_JSON_KEY_MEMADDR 0x1e76b53fU #define CHEEVOS_JSON_KEY_TITLE 0x0e2a9a07U #define CHEEVOS_JSON_KEY_DESCRIPTION 0xe61a1f69U #define CHEEVOS_JSON_KEY_POINTS 0xca8fce22U #define CHEEVOS_JSON_KEY_AUTHOR 0xa804edb8U #define CHEEVOS_JSON_KEY_MODIFIED 0xdcea4fe6U #define CHEEVOS_JSON_KEY_CREATED 0x3a84721dU #define CHEEVOS_JSON_KEY_BADGENAME 0x887685d9U #define CHEEVOS_JSON_KEY_CONSOLE_ID 0x071656e5U #define CHEEVOS_JSON_KEY_TOKEN 0x0e2dbd26U #define CHEEVOS_JSON_KEY_FLAGS 0x0d2e96b2U #define CHEEVOS_JSON_KEY_LEADERBOARDS 0xf1247d2dU #define CHEEVOS_JSON_KEY_RICHPRESENCE 0xf18dd230U #define CHEEVOS_JSON_KEY_MEM 0x0b8807e4U #define CHEEVOS_JSON_KEY_FORMAT 0xb341208eU #define CHEEVOS_JSON_KEY_SUCCESS 0x110461deU #define CHEEVOS_JSON_KEY_ERROR 0x0d2011cfU typedef struct { const char *value; int is_key; size_t length; unsigned key_hash; } rcheevos_getvalueud_t; /***************************************************************************** Gets a value in a JSON *****************************************************************************/ static uint32_t rcheevos_djb2(const char* str, size_t length) { const unsigned char* aux = (const unsigned char*)str; uint32_t hash = 5381; while (length--) hash = (hash << 5) + hash + *aux++; return hash; } static int 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; } static int rcheevos_getvalue_string(void* userdata, const char* string, size_t length) { rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; if (ud->is_key) { ud->value = string; ud->length = length; ud->is_key = 0; } return 0; } static int rcheevos_getvalue_boolean(void* userdata, int istrue) { rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; if (ud->is_key) { if (istrue) { ud->value = "true"; ud->length = 4; } else { ud->value = "false"; ud->length = 5; } ud->is_key = 0; } return 0; } static int rcheevos_getvalue_null(void* userdata) { rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata; if (ud->is_key ) { ud->value = "null"; ud->length = 4; ud->is_key = 0; } return 0; } 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; *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; } return -1; } /***************************************************************************** Returns the token or the error message *****************************************************************************/ int rcheevos_get_token(const char* json, char* token, size_t length) { rcheevos_get_value(json, CHEEVOS_JSON_KEY_ERROR, token, length); if (!string_is_empty(token)) return -1; return rcheevos_get_value(json, CHEEVOS_JSON_KEY_TOKEN, token, length); } int rcheevos_get_json_error(const char* json, char* token, size_t length) { return rcheevos_get_value(json, CHEEVOS_JSON_KEY_ERROR, token, length); } /***************************************************************************** Count number of achievements in a JSON file *****************************************************************************/ typedef struct { int in_cheevos; int in_lboards; int has_error; uint32_t field_hash; unsigned core_count; unsigned unofficial_count; unsigned lboard_count; } rcheevos_countud_t; static int rcheevos_count_end_array(void* userdata) { rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata; ud->in_cheevos = 0; ud->in_lboards = 0; return 0; } static int rcheevos_count_key(void* userdata, const char* name, size_t length) { rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata; ud->field_hash = rcheevos_djb2(name, length); if (ud->field_hash == CHEEVOS_JSON_KEY_ACHIEVEMENTS) ud->in_cheevos = 1; else if (ud->field_hash == CHEEVOS_JSON_KEY_LEADERBOARDS) ud->in_lboards = 1; else if (ud->field_hash == CHEEVOS_JSON_KEY_ERROR) ud->has_error = 1; return 0; } static int rcheevos_count_number(void* userdata, const char* number, size_t length) { rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata; if (ud->in_cheevos && ud->field_hash == CHEEVOS_JSON_KEY_FLAGS) { long flags = strtol(number, NULL, 10); if (flags == 3) ud->core_count++; /* Core achievements */ else if (flags == 5) ud->unofficial_count++; /* Unofficial achievements */ } else if (ud->in_lboards && ud->field_hash == CHEEVOS_JSON_KEY_ID) ud->lboard_count++; return 0; } static int 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; rcheevos_countud_t ud; ud.in_cheevos = 0; ud.in_lboards = 0; ud.has_error = 0; ud.core_count = 0; ud.unofficial_count = 0; ud.lboard_count = 0; res = jsonsax_parse(json, &handlers, (void*)&ud); *core_count = ud.core_count; *unofficial_count = ud.unofficial_count; *lboard_count = ud.lboard_count; *has_error = ud.has_error; return res; } /***************************************************************************** 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; 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; 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) { rcheevos_racheevo_t* cheevo = NULL; unsigned flags = (unsigned)strtol(ud->flags.string, NULL, 10); if (flags == 3) cheevo = ud->patchdata->core + ud->core_count++; else if (flags == 5) cheevo = ud->patchdata->unofficial + ud->unofficial_count++; else return 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) { CHEEVOS_FREE(cheevo->title); CHEEVOS_FREE(cheevo->description); CHEEVOS_FREE(cheevo->badge); CHEEVOS_FREE(cheevo->memaddr); return -1; } return 0; } static int rcheevos_new_lboard(rcheevos_readud_t* ud) { rcheevos_ralboard_t* lboard = ud->patchdata->lboards + ud->lboard_count++; lboard->title = rcheevos_unescape_string(ud->title.string, ud->title.length); lboard->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length); lboard->format = rcheevos_unescape_string(ud->format.string, ud->format.length); lboard->mem = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length); lboard->id = (unsigned)strtol(ud->id.string, NULL, 10); if ( !lboard->title || !lboard->description || !lboard->format || !lboard->mem) { CHEEVOS_FREE(lboard->title); CHEEVOS_FREE(lboard->description); CHEEVOS_FREE(lboard->format); CHEEVOS_FREE(lboard->mem); return -1; } return 0; } static int rcheevos_read_end_object(void* userdata) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; if (ud->in_cheevos) return rcheevos_new_cheevo(ud); if (ud->in_lboards) return rcheevos_new_lboard(ud); return 0; } static int rcheevos_read_end_array(void* userdata) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; ud->in_cheevos = 0; ud->in_lboards = 0; return 0; } static int 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; switch (hash) { case CHEEVOS_JSON_KEY_ACHIEVEMENTS: ud->in_cheevos = 1; break; case CHEEVOS_JSON_KEY_LEADERBOARDS: ud->in_lboards = 1; break; case CHEEVOS_JSON_KEY_CONSOLE_ID: ud->is_console_id = 1; break; case CHEEVOS_JSON_KEY_RICHPRESENCE: ud->is_richpresence = 1; break; case CHEEVOS_JSON_KEY_ID: if (common) ud->field = &ud->id; else ud->is_game_id = 1; break; case CHEEVOS_JSON_KEY_MEMADDR: if (ud->in_cheevos) ud->field = &ud->memaddr; break; case CHEEVOS_JSON_KEY_MEM: if (ud->in_lboards) ud->field = &ud->memaddr; break; case CHEEVOS_JSON_KEY_TITLE: if (common) ud->field = &ud->title; else ud->is_title = 1; break; case CHEEVOS_JSON_KEY_DESCRIPTION: if (common) ud->field = &ud->desc; break; case CHEEVOS_JSON_KEY_POINTS: if (ud->in_cheevos) ud->field = &ud->points; break; case CHEEVOS_JSON_KEY_AUTHOR: if (ud->in_cheevos) ud->field = &ud->author; break; case CHEEVOS_JSON_KEY_MODIFIED: if (ud->in_cheevos) ud->field = &ud->modified; break; case CHEEVOS_JSON_KEY_CREATED: if (ud->in_cheevos) ud->field = &ud->created; break; case CHEEVOS_JSON_KEY_BADGENAME: if (ud->in_cheevos) ud->field = &ud->badge; break; case CHEEVOS_JSON_KEY_FLAGS: if (ud->in_cheevos) ud->field = &ud->flags; break; case CHEEVOS_JSON_KEY_FORMAT: if (ud->in_lboards) ud->field = &ud->format; break; default: break; } return 0; } static int rcheevos_read_string(void* userdata, const char* string, size_t length) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; if (ud->field) { 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; } return 0; } static int rcheevos_read_number(void* userdata, const char* number, size_t length) { rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata; if (ud->field) { 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; } return 0; } 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; 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) return -1; /* Allocate the achievements. */ patchdata->core = (rcheevos_racheevo_t*) calloc(patchdata->core_count, sizeof(rcheevos_racheevo_t)); patchdata->unofficial = (rcheevos_racheevo_t*) calloc(patchdata->unofficial_count, sizeof(rcheevos_racheevo_t)); patchdata->lboards = (rcheevos_ralboard_t*) calloc(patchdata->lboard_count, sizeof(rcheevos_ralboard_t)); if (!patchdata->core || !patchdata->unofficial || !patchdata->lboards) { CHEEVOS_FREE(patchdata->core); CHEEVOS_FREE(patchdata->unofficial); CHEEVOS_FREE(patchdata->lboards); return -1; } patchdata->richpresence_script = NULL; 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; ud.patchdata = patchdata; if (jsonsax_parse(json, &handlers, (void*)&ud) != JSONSAX_OK) { rcheevos_free_patchdata(patchdata); return -1; } return 0; } /***************************************************************************** Frees the patchdata *****************************************************************************/ void rcheevos_free_patchdata(rcheevos_rapatchdata_t* patchdata) { unsigned i = 0, count = 0; const rcheevos_racheevo_t* cheevo = NULL; const rcheevos_ralboard_t* lboard = NULL; cheevo = patchdata->core; for (i = 0, count = patchdata->core_count; i < count; i++, cheevo++) { CHEEVOS_FREE(cheevo->title); CHEEVOS_FREE(cheevo->description); CHEEVOS_FREE(cheevo->badge); CHEEVOS_FREE(cheevo->memaddr); } cheevo = patchdata->unofficial; for (i = 0, count = patchdata->unofficial_count; i < count; i++, cheevo++) { CHEEVOS_FREE(cheevo->title); CHEEVOS_FREE(cheevo->description); CHEEVOS_FREE(cheevo->badge); CHEEVOS_FREE(cheevo->memaddr); } lboard = patchdata->lboards; for (i = 0, count = patchdata->lboard_count; i < count; i++, lboard++) { CHEEVOS_FREE(lboard->title); CHEEVOS_FREE(lboard->description); CHEEVOS_FREE(lboard->format); CHEEVOS_FREE(lboard->mem); } CHEEVOS_FREE(patchdata->core); CHEEVOS_FREE(patchdata->unofficial); CHEEVOS_FREE(patchdata->lboards); CHEEVOS_FREE(patchdata->richpresence_script); CHEEVOS_FREE(patchdata->title); patchdata->game_id = 0; patchdata->console_id = 0; patchdata->core = NULL; patchdata->unofficial = NULL; patchdata->lboards = NULL; patchdata->core_count = 0; patchdata->unofficial_count = 0; patchdata->lboard_count = 0; } /***************************************************************************** Deactivates unlocked cheevos *****************************************************************************/ typedef struct { int is_element; rcheevos_unlock_cb_t unlock_cb; void* userdata; } rcheevos_deactivate_t; static int rcheevos_deactivate_index(void* userdata, unsigned int index) { rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata; ud->is_element = 1; return 0; } static int rcheevos_deactivate_number(void* userdata, const char* number, size_t length) { rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata; unsigned id = 0; if (ud->is_element) { ud->is_element = 0; id = (unsigned)strtol(number, NULL, 10); ud->unlock_cb(id, ud->userdata); } return 0; } 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); } /***************************************************************************** Returns the game ID *****************************************************************************/ unsigned chevos_get_gameid(const char* json) { char gameid[32]; if (rcheevos_get_value(json, CHEEVOS_JSON_KEY_GAMEID, gameid, sizeof(gameid)) != 0) return 0; return (unsigned)strtol(gameid, NULL, 10); }