Path: blob/master/dep/rcheevos/src/rapi/rc_api_editor.c
4806 views
#include "rc_api_editor.h"1#include "rc_api_common.h"2#include "rc_api_runtime.h"34#include "../rc_compat.h"5#include "../rhash/md5.h"67#include <stdlib.h>8#include <string.h>910/* --- Fetch Code Notes --- */1112int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params) {13return rc_api_init_fetch_code_notes_request_hosted(request, api_params, &g_host);14}1516int rc_api_init_fetch_code_notes_request_hosted(rc_api_request_t* request,17const rc_api_fetch_code_notes_request_t* api_params,18const rc_api_host_t* host) {19rc_api_url_builder_t builder;2021rc_api_url_build_dorequest_url(request, host);2223if (api_params->game_id == 0)24return RC_INVALID_STATE;2526rc_url_builder_init(&builder, &request->buffer, 48);27rc_url_builder_append_str_param(&builder, "r", "codenotes2");28rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);2930request->post_data = rc_url_builder_finalize(&builder);31request->content_type = RC_CONTENT_TYPE_URLENCODED;3233return builder.result;34}3536int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) {37rc_api_server_response_t response_obj;3839memset(&response_obj, 0, sizeof(response_obj));40response_obj.body = server_response;41response_obj.body_length = rc_json_get_object_string_length(server_response);4243return rc_api_process_fetch_code_notes_server_response(response, &response_obj);44}4546int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) {47rc_json_field_t array_field;48rc_json_iterator_t iterator;49rc_api_code_note_t* note;50const char* address_str;51const char* last_author = "";52size_t last_author_len = 0;53size_t len;54int result;5556rc_json_field_t fields[] = {57RC_JSON_NEW_FIELD("Success"),58RC_JSON_NEW_FIELD("Error"),59RC_JSON_NEW_FIELD("CodeNotes")60};6162rc_json_field_t note_fields[] = {63RC_JSON_NEW_FIELD("Address"),64RC_JSON_NEW_FIELD("User"),65RC_JSON_NEW_FIELD("Note")66};6768memset(response, 0, sizeof(*response));69rc_buffer_init(&response->response.buffer);7071result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));72if (result != RC_OK || !response->response.succeeded)73return result;7475if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes"))76return RC_MISSING_VALUE;7778if (response->num_notes) {79response->notes = (rc_api_code_note_t*)rc_buffer_alloc(&response->response.buffer, response->num_notes * sizeof(rc_api_code_note_t));80if (!response->notes)81return RC_OUT_OF_MEMORY;8283memset(&iterator, 0, sizeof(iterator));84iterator.json = array_field.value_start;85iterator.end = array_field.value_end;8687note = response->notes;88while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) {89/* an empty note represents a record that was deleted on the server */90/* a note set to '' also represents a deleted note (remnant of a bug) */91/* NOTE: len will include the quotes */92if (note_fields[2].value_start) {93len = note_fields[2].value_end - note_fields[2].value_start;94if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {95--response->num_notes;96continue;97}98}99100if (!rc_json_get_required_string(&address_str, &response->response, ¬e_fields[0], "Address"))101return RC_MISSING_VALUE;102note->address = (uint32_t)strtoul(address_str, NULL, 16);103if (!rc_json_get_required_string(¬e->note, &response->response, ¬e_fields[2], "Note"))104return RC_MISSING_VALUE;105106len = note_fields[1].value_end - note_fields[1].value_start;107if (len == last_author_len && memcmp(note_fields[1].value_start, last_author, len) == 0) {108note->author = last_author;109}110else {111if (!rc_json_get_required_string(¬e->author, &response->response, ¬e_fields[1], "User"))112return RC_MISSING_VALUE;113114if (note->author == NULL) {115/* ensure we don't pass NULL out to client */116last_author = note->author = "";117last_author_len = 0;118} else {119last_author = note->author;120last_author_len = len;121}122}123124++note;125}126}127128return RC_OK;129}130131void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response) {132rc_buffer_destroy(&response->response.buffer);133}134135/* --- Update Code Note --- */136137int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params) {138return rc_api_init_update_code_note_request_hosted(request, api_params, &g_host);139}140141int rc_api_init_update_code_note_request_hosted(rc_api_request_t* request,142const rc_api_update_code_note_request_t* api_params,143const rc_api_host_t* host) {144rc_api_url_builder_t builder;145146rc_api_url_build_dorequest_url(request, host);147148if (api_params->game_id == 0)149return RC_INVALID_STATE;150151rc_url_builder_init(&builder, &request->buffer, 128);152if (!rc_api_url_build_dorequest(&builder, "submitcodenote", api_params->username, api_params->api_token))153return builder.result;154155rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);156rc_url_builder_append_unum_param(&builder, "m", api_params->address);157158if (api_params->note && *api_params->note)159rc_url_builder_append_str_param(&builder, "n", api_params->note);160161request->post_data = rc_url_builder_finalize(&builder);162request->content_type = RC_CONTENT_TYPE_URLENCODED;163164return builder.result;165}166167int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) {168rc_api_server_response_t response_obj;169170memset(&response_obj, 0, sizeof(response_obj));171response_obj.body = server_response;172response_obj.body_length = rc_json_get_object_string_length(server_response);173174return rc_api_process_update_code_note_server_response(response, &response_obj);175}176177int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) {178int result;179180rc_json_field_t fields[] = {181RC_JSON_NEW_FIELD("Success"),182RC_JSON_NEW_FIELD("Error")183/* unused fields184RC_JSON_NEW_FIELD("GameID"),185RC_JSON_NEW_FIELD("Address"),186RC_JSON_NEW_FIELD("Note")187*/188};189190memset(response, 0, sizeof(*response));191rc_buffer_init(&response->response.buffer);192193result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));194if (result != RC_OK || !response->response.succeeded)195return result;196197return RC_OK;198}199200void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response) {201rc_buffer_destroy(&response->response.buffer);202}203204/* --- Update Achievement --- */205206static const char* rc_type_string(uint32_t type) {207switch (type) {208case RC_ACHIEVEMENT_TYPE_MISSABLE: return "missable";209case RC_ACHIEVEMENT_TYPE_PROGRESSION: return "progression";210case RC_ACHIEVEMENT_TYPE_WIN: return "win_condition";211default: return "";212}213}214215int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params) {216return rc_api_init_update_achievement_request_hosted(request, api_params, &g_host);217}218219int rc_api_init_update_achievement_request_hosted(rc_api_request_t* request,220const rc_api_update_achievement_request_t* api_params,221const rc_api_host_t* host) {222rc_api_url_builder_t builder;223char buffer[33];224md5_state_t md5;225md5_byte_t hash[16];226227rc_api_url_build_dorequest_url(request, host);228229if (api_params->game_id == 0 || api_params->category == 0)230return RC_INVALID_STATE;231if (!api_params->title || !*api_params->title)232return RC_INVALID_STATE;233if (!api_params->description || !*api_params->description)234return RC_INVALID_STATE;235if (!api_params->trigger || !*api_params->trigger)236return RC_INVALID_STATE;237238rc_url_builder_init(&builder, &request->buffer, 128);239if (!rc_api_url_build_dorequest(&builder, "uploadachievement", api_params->username, api_params->api_token))240return builder.result;241242if (api_params->achievement_id)243rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);244rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);245rc_url_builder_append_str_param(&builder, "n", api_params->title);246rc_url_builder_append_str_param(&builder, "d", api_params->description);247rc_url_builder_append_str_param(&builder, "m", api_params->trigger);248rc_url_builder_append_unum_param(&builder, "z", api_params->points);249rc_url_builder_append_unum_param(&builder, "f", api_params->category);250if (api_params->badge)251rc_url_builder_append_str_param(&builder, "b", api_params->badge);252rc_url_builder_append_str_param(&builder, "x", rc_type_string(api_params->type));253254/* Evaluate the signature. */255md5_init(&md5);256md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));257md5_append(&md5, (md5_byte_t*)"SECRET", 6);258snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);259md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));260md5_append(&md5, (md5_byte_t*)"SEC", 3);261md5_append(&md5, (md5_byte_t*)api_params->trigger, (int)strlen(api_params->trigger));262snprintf(buffer, sizeof(buffer), "%u", api_params->points);263md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));264md5_append(&md5, (md5_byte_t*)"RE2", 3);265snprintf(buffer, sizeof(buffer), "%u", api_params->points * 3);266md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));267md5_finish(&md5, hash);268rc_format_md5(buffer, hash);269rc_url_builder_append_str_param(&builder, "h", buffer);270271request->post_data = rc_url_builder_finalize(&builder);272request->content_type = RC_CONTENT_TYPE_URLENCODED;273274return builder.result;275}276277int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) {278rc_api_server_response_t response_obj;279280memset(&response_obj, 0, sizeof(response_obj));281response_obj.body = server_response;282response_obj.body_length = rc_json_get_object_string_length(server_response);283284return rc_api_process_update_achievement_server_response(response, &response_obj);285}286287int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) {288int result;289290rc_json_field_t fields[] = {291RC_JSON_NEW_FIELD("Success"),292RC_JSON_NEW_FIELD("Error"),293RC_JSON_NEW_FIELD("AchievementID")294};295296memset(response, 0, sizeof(*response));297rc_buffer_init(&response->response.buffer);298299result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));300if (result != RC_OK || !response->response.succeeded)301return result;302303if (!rc_json_get_required_unum(&response->achievement_id, &response->response, &fields[2], "AchievementID"))304return RC_MISSING_VALUE;305306return RC_OK;307}308309void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response) {310rc_buffer_destroy(&response->response.buffer);311}312313/* --- Update Leaderboard --- */314315int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params) {316return rc_api_init_update_leaderboard_request_hosted(request, api_params, &g_host);317}318319int rc_api_init_update_leaderboard_request_hosted(rc_api_request_t* request,320const rc_api_update_leaderboard_request_t* api_params,321const rc_api_host_t* host) {322rc_api_url_builder_t builder;323char buffer[33];324md5_state_t md5;325md5_byte_t hash[16];326327rc_api_url_build_dorequest_url(request, host);328329if (api_params->game_id == 0)330return RC_INVALID_STATE;331if (!api_params->title || !*api_params->title)332return RC_INVALID_STATE;333if (!api_params->description)334return RC_INVALID_STATE;335if (!api_params->start_trigger || !*api_params->start_trigger)336return RC_INVALID_STATE;337if (!api_params->submit_trigger || !*api_params->submit_trigger)338return RC_INVALID_STATE;339if (!api_params->cancel_trigger || !*api_params->cancel_trigger)340return RC_INVALID_STATE;341if (!api_params->value_definition || !*api_params->value_definition)342return RC_INVALID_STATE;343if (!api_params->format || !*api_params->format)344return RC_INVALID_STATE;345346rc_url_builder_init(&builder, &request->buffer, 128);347if (!rc_api_url_build_dorequest(&builder, "uploadleaderboard", api_params->username, api_params->api_token))348return builder.result;349350if (api_params->leaderboard_id)351rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);352rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);353rc_url_builder_append_str_param(&builder, "n", api_params->title);354rc_url_builder_append_str_param(&builder, "d", api_params->description);355rc_url_builder_append_str_param(&builder, "s", api_params->start_trigger);356rc_url_builder_append_str_param(&builder, "b", api_params->submit_trigger);357rc_url_builder_append_str_param(&builder, "c", api_params->cancel_trigger);358rc_url_builder_append_str_param(&builder, "l", api_params->value_definition);359rc_url_builder_append_num_param(&builder, "w", api_params->lower_is_better);360rc_url_builder_append_str_param(&builder, "f", api_params->format);361362/* Evaluate the signature. */363md5_init(&md5);364md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));365md5_append(&md5, (md5_byte_t*)"SECRET", 6);366snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);367md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));368md5_append(&md5, (md5_byte_t*)"SEC", 3);369md5_append(&md5, (md5_byte_t*)api_params->start_trigger, (int)strlen(api_params->start_trigger));370md5_append(&md5, (md5_byte_t*)api_params->submit_trigger, (int)strlen(api_params->submit_trigger));371md5_append(&md5, (md5_byte_t*)api_params->cancel_trigger, (int)strlen(api_params->cancel_trigger));372md5_append(&md5, (md5_byte_t*)api_params->value_definition, (int)strlen(api_params->value_definition));373md5_append(&md5, (md5_byte_t*)"RE2", 3);374md5_append(&md5, (md5_byte_t*)api_params->format, (int)strlen(api_params->format));375md5_finish(&md5, hash);376rc_format_md5(buffer, hash);377rc_url_builder_append_str_param(&builder, "h", buffer);378379request->post_data = rc_url_builder_finalize(&builder);380request->content_type = RC_CONTENT_TYPE_URLENCODED;381382return builder.result;383}384385int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) {386rc_api_server_response_t response_obj;387388memset(&response_obj, 0, sizeof(response_obj));389response_obj.body = server_response;390response_obj.body_length = rc_json_get_object_string_length(server_response);391392return rc_api_process_update_leaderboard_server_response(response, &response_obj);393}394395int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) {396int result;397398rc_json_field_t fields[] = {399RC_JSON_NEW_FIELD("Success"),400RC_JSON_NEW_FIELD("Error"),401RC_JSON_NEW_FIELD("LeaderboardID")402};403404memset(response, 0, sizeof(*response));405rc_buffer_init(&response->response.buffer);406407result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));408if (result != RC_OK || !response->response.succeeded)409return result;410411if (!rc_json_get_required_unum(&response->leaderboard_id, &response->response, &fields[2], "LeaderboardID"))412return RC_MISSING_VALUE;413414return RC_OK;415}416417void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response) {418rc_buffer_destroy(&response->response.buffer);419}420421/* --- Update Rich Presence --- */422423int rc_api_init_update_rich_presence_request(rc_api_request_t* request, const rc_api_update_rich_presence_request_t* api_params) {424return rc_api_init_update_rich_presence_request_hosted(request, api_params, &g_host);425}426427int rc_api_init_update_rich_presence_request_hosted(rc_api_request_t* request,428const rc_api_update_rich_presence_request_t* api_params,429const rc_api_host_t* host) {430rc_api_url_builder_t builder;431432rc_api_url_build_dorequest_url(request, host);433434if (api_params->game_id == 0)435return RC_INVALID_STATE;436if (!api_params->script)437return RC_INVALID_STATE;438439rc_url_builder_init(&builder, &request->buffer, 128);440if (!rc_api_url_build_dorequest(&builder, "submitrichpresence", api_params->username, api_params->api_token))441return builder.result;442443rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);444rc_url_builder_append_str_param(&builder, "d", api_params->script);445446request->post_data = rc_url_builder_finalize(&builder);447request->content_type = RC_CONTENT_TYPE_URLENCODED;448449return builder.result;450}451452int rc_api_process_update_rich_presence_server_response(rc_api_update_rich_presence_response_t* response, const rc_api_server_response_t* server_response) {453int result;454455rc_json_field_t fields[] = {456RC_JSON_NEW_FIELD("Success"),457RC_JSON_NEW_FIELD("Error"),458RC_JSON_NEW_FIELD("Code"),459};460461memset(response, 0, sizeof(*response));462rc_buffer_init(&response->response.buffer);463464result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));465if (result != RC_OK || !response->response.succeeded)466return result;467468return RC_OK;469}470471void rc_api_destroy_update_rich_presence_response(rc_api_update_rich_presence_response_t* response) {472rc_buffer_destroy(&response->response.buffer);473}474475/* --- Fetch Badge Range --- */476477int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {478return rc_api_init_fetch_badge_range_request_hosted(request, api_params, &g_host);479}480481int rc_api_init_fetch_badge_range_request_hosted(rc_api_request_t* request,482const rc_api_fetch_badge_range_request_t* api_params,483const rc_api_host_t* host) {484rc_api_url_builder_t builder;485486rc_api_url_build_dorequest_url(request, host);487488rc_url_builder_init(&builder, &request->buffer, 48);489rc_url_builder_append_str_param(&builder, "r", "badgeiter");490491request->post_data = rc_url_builder_finalize(&builder);492request->content_type = RC_CONTENT_TYPE_URLENCODED;493494(void)api_params;495496return builder.result;497}498499int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {500rc_api_server_response_t response_obj;501502memset(&response_obj, 0, sizeof(response_obj));503response_obj.body = server_response;504response_obj.body_length = rc_json_get_object_string_length(server_response);505506return rc_api_process_fetch_badge_range_server_response(response, &response_obj);507}508509int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) {510int result;511512rc_json_field_t fields[] = {513RC_JSON_NEW_FIELD("Success"),514RC_JSON_NEW_FIELD("Error"),515RC_JSON_NEW_FIELD("FirstBadge"),516RC_JSON_NEW_FIELD("NextBadge")517};518519memset(response, 0, sizeof(*response));520rc_buffer_init(&response->response.buffer);521522result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));523if (result != RC_OK || !response->response.succeeded)524return result;525526if (!rc_json_get_required_unum(&response->first_badge_id, &response->response, &fields[2], "FirstBadge"))527return RC_MISSING_VALUE;528if (!rc_json_get_required_unum(&response->next_badge_id, &response->response, &fields[3], "NextBadge"))529return RC_MISSING_VALUE;530531return RC_OK;532}533534void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response) {535rc_buffer_destroy(&response->response.buffer);536}537538/* --- Add Game Hash --- */539540int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params) {541return rc_api_init_add_game_hash_request_hosted(request, api_params, &g_host);542}543544int rc_api_init_add_game_hash_request_hosted(rc_api_request_t* request,545const rc_api_add_game_hash_request_t* api_params,546const rc_api_host_t* host) {547rc_api_url_builder_t builder;548549rc_api_url_build_dorequest_url(request, host);550551if (api_params->console_id == 0)552return RC_INVALID_STATE;553if (!api_params->hash || !*api_params->hash)554return RC_INVALID_STATE;555if (api_params->game_id == 0 && (!api_params->title || !*api_params->title))556return RC_INVALID_STATE;557558rc_url_builder_init(&builder, &request->buffer, 128);559if (!rc_api_url_build_dorequest(&builder, "submitgametitle", api_params->username, api_params->api_token))560return builder.result;561562rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);563rc_url_builder_append_str_param(&builder, "m", api_params->hash);564if (api_params->title)565rc_url_builder_append_str_param(&builder, "i", api_params->title);566if (api_params->game_id)567rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);568if (api_params->hash_description && *api_params->hash_description)569rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);570571request->post_data = rc_url_builder_finalize(&builder);572request->content_type = RC_CONTENT_TYPE_URLENCODED;573574return builder.result;575}576577int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {578rc_api_server_response_t response_obj;579580memset(&response_obj, 0, sizeof(response_obj));581response_obj.body = server_response;582response_obj.body_length = rc_json_get_object_string_length(server_response);583584return rc_api_process_add_game_hash_server_response(response, &response_obj);585}586587int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) {588int result;589590rc_json_field_t fields[] = {591RC_JSON_NEW_FIELD("Success"),592RC_JSON_NEW_FIELD("Error"),593RC_JSON_NEW_FIELD("Response")594};595596rc_json_field_t response_fields[] = {597RC_JSON_NEW_FIELD("GameID")598/* unused fields599RC_JSON_NEW_FIELD("MD5"),600RC_JSON_NEW_FIELD("ConsoleID"),601RC_JSON_NEW_FIELD("GameTitle"),602RC_JSON_NEW_FIELD("Success")603*/604};605606memset(response, 0, sizeof(*response));607rc_buffer_init(&response->response.buffer);608609result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));610if (result != RC_OK || !response->response.succeeded)611return result;612613if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))614return RC_MISSING_VALUE;615616if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[0], "GameID"))617return RC_MISSING_VALUE;618619return RC_OK;620}621622void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response) {623rc_buffer_destroy(&response->response.buffer);624}625626627