Path: blob/master/dep/rcheevos/src/rapi/rc_api_editor.c
4253 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 = (unsigned)strtol(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/* --- Fetch Badge Range --- */422423int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {424return rc_api_init_fetch_badge_range_request_hosted(request, api_params, &g_host);425}426427int rc_api_init_fetch_badge_range_request_hosted(rc_api_request_t* request,428const rc_api_fetch_badge_range_request_t* api_params,429const rc_api_host_t* host) {430rc_api_url_builder_t builder;431432rc_api_url_build_dorequest_url(request, host);433434rc_url_builder_init(&builder, &request->buffer, 48);435rc_url_builder_append_str_param(&builder, "r", "badgeiter");436437request->post_data = rc_url_builder_finalize(&builder);438request->content_type = RC_CONTENT_TYPE_URLENCODED;439440(void)api_params;441442return builder.result;443}444445int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {446rc_api_server_response_t response_obj;447448memset(&response_obj, 0, sizeof(response_obj));449response_obj.body = server_response;450response_obj.body_length = rc_json_get_object_string_length(server_response);451452return rc_api_process_fetch_badge_range_server_response(response, &response_obj);453}454455int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) {456int result;457458rc_json_field_t fields[] = {459RC_JSON_NEW_FIELD("Success"),460RC_JSON_NEW_FIELD("Error"),461RC_JSON_NEW_FIELD("FirstBadge"),462RC_JSON_NEW_FIELD("NextBadge")463};464465memset(response, 0, sizeof(*response));466rc_buffer_init(&response->response.buffer);467468result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));469if (result != RC_OK || !response->response.succeeded)470return result;471472if (!rc_json_get_required_unum(&response->first_badge_id, &response->response, &fields[2], "FirstBadge"))473return RC_MISSING_VALUE;474if (!rc_json_get_required_unum(&response->next_badge_id, &response->response, &fields[3], "NextBadge"))475return RC_MISSING_VALUE;476477return RC_OK;478}479480void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response) {481rc_buffer_destroy(&response->response.buffer);482}483484/* --- Add Game Hash --- */485486int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params) {487return rc_api_init_add_game_hash_request_hosted(request, api_params, &g_host);488}489490int rc_api_init_add_game_hash_request_hosted(rc_api_request_t* request,491const rc_api_add_game_hash_request_t* api_params,492const rc_api_host_t* host) {493rc_api_url_builder_t builder;494495rc_api_url_build_dorequest_url(request, host);496497if (api_params->console_id == 0)498return RC_INVALID_STATE;499if (!api_params->hash || !*api_params->hash)500return RC_INVALID_STATE;501if (api_params->game_id == 0 && (!api_params->title || !*api_params->title))502return RC_INVALID_STATE;503504rc_url_builder_init(&builder, &request->buffer, 128);505if (!rc_api_url_build_dorequest(&builder, "submitgametitle", api_params->username, api_params->api_token))506return builder.result;507508rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);509rc_url_builder_append_str_param(&builder, "m", api_params->hash);510if (api_params->title)511rc_url_builder_append_str_param(&builder, "i", api_params->title);512if (api_params->game_id)513rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);514if (api_params->hash_description && *api_params->hash_description)515rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);516517request->post_data = rc_url_builder_finalize(&builder);518request->content_type = RC_CONTENT_TYPE_URLENCODED;519520return builder.result;521}522523int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {524rc_api_server_response_t response_obj;525526memset(&response_obj, 0, sizeof(response_obj));527response_obj.body = server_response;528response_obj.body_length = rc_json_get_object_string_length(server_response);529530return rc_api_process_add_game_hash_server_response(response, &response_obj);531}532533int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) {534int result;535536rc_json_field_t fields[] = {537RC_JSON_NEW_FIELD("Success"),538RC_JSON_NEW_FIELD("Error"),539RC_JSON_NEW_FIELD("Response")540};541542rc_json_field_t response_fields[] = {543RC_JSON_NEW_FIELD("GameID")544/* unused fields545RC_JSON_NEW_FIELD("MD5"),546RC_JSON_NEW_FIELD("ConsoleID"),547RC_JSON_NEW_FIELD("GameTitle"),548RC_JSON_NEW_FIELD("Success")549*/550};551552memset(response, 0, sizeof(*response));553rc_buffer_init(&response->response.buffer);554555result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));556if (result != RC_OK || !response->response.succeeded)557return result;558559if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))560return RC_MISSING_VALUE;561562if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[0], "GameID"))563return RC_MISSING_VALUE;564565return RC_OK;566}567568void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response) {569rc_buffer_destroy(&response->response.buffer);570}571572573