Path: blob/master/dep/rcheevos/src/rapi/rc_api_info.c
4253 views
#include "rc_api_info.h"1#include "rc_api_common.h"2#include "rc_api_runtime.h"34#include "rc_runtime_types.h"56#include "../rc_compat.h"78#include <stdlib.h>9#include <string.h>1011/* --- Fetch Achievement Info --- */1213int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params) {14return rc_api_init_fetch_achievement_info_request_hosted(request, api_params, &g_host);15}1617int rc_api_init_fetch_achievement_info_request_hosted(rc_api_request_t* request,18const rc_api_fetch_achievement_info_request_t* api_params,19const rc_api_host_t* host) {20rc_api_url_builder_t builder;2122rc_api_url_build_dorequest_url(request, host);2324if (api_params->achievement_id == 0)25return RC_INVALID_STATE;2627rc_url_builder_init(&builder, &request->buffer, 48);28if (rc_api_url_build_dorequest(&builder, "achievementwondata", api_params->username, api_params->api_token)) {29rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);3031if (api_params->friends_only)32rc_url_builder_append_unum_param(&builder, "f", 1);33if (api_params->first_entry > 1)34rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */35rc_url_builder_append_unum_param(&builder, "c", api_params->count);3637request->post_data = rc_url_builder_finalize(&builder);38request->content_type = RC_CONTENT_TYPE_URLENCODED;39}4041return builder.result;42}4344int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) {45rc_api_server_response_t response_obj;4647memset(&response_obj, 0, sizeof(response_obj));48response_obj.body = server_response;49response_obj.body_length = rc_json_get_object_string_length(server_response);5051return rc_api_process_fetch_achievement_info_server_response(response, &response_obj);52}5354int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) {55rc_api_achievement_awarded_entry_t* entry;56rc_json_field_t array_field;57rc_json_iterator_t iterator;58uint32_t timet;59int result;6061rc_json_field_t fields[] = {62RC_JSON_NEW_FIELD("Success"),63RC_JSON_NEW_FIELD("Error"),64RC_JSON_NEW_FIELD("AchievementID"),65RC_JSON_NEW_FIELD("Response")66/* unused fields67RC_JSON_NEW_FIELD("Offset"),68RC_JSON_NEW_FIELD("Count"),69RC_JSON_NEW_FIELD("FriendsOnly")70* unused fields */71};7273rc_json_field_t response_fields[] = {74RC_JSON_NEW_FIELD("NumEarned"),75RC_JSON_NEW_FIELD("TotalPlayers"),76RC_JSON_NEW_FIELD("GameID"),77RC_JSON_NEW_FIELD("RecentWinner") /* array */78};7980rc_json_field_t entry_fields[] = {81RC_JSON_NEW_FIELD("User"),82RC_JSON_NEW_FIELD("DateAwarded"),83RC_JSON_NEW_FIELD("AvatarUrl")84};8586memset(response, 0, sizeof(*response));87rc_buffer_init(&response->response.buffer);8889result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));90if (result != RC_OK)91return result;9293if (!rc_json_get_required_unum(&response->id, &response->response, &fields[2], "AchievementID"))94return RC_MISSING_VALUE;95if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[3], "Response"))96return RC_MISSING_VALUE;9798if (!rc_json_get_required_unum(&response->num_awarded, &response->response, &response_fields[0], "NumEarned"))99return RC_MISSING_VALUE;100if (!rc_json_get_required_unum(&response->num_players, &response->response, &response_fields[1], "TotalPlayers"))101return RC_MISSING_VALUE;102if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))103return RC_MISSING_VALUE;104105if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner"))106return RC_MISSING_VALUE;107108if (response->num_recently_awarded) {109response->recently_awarded = (rc_api_achievement_awarded_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_recently_awarded * sizeof(rc_api_achievement_awarded_entry_t));110if (!response->recently_awarded)111return RC_OUT_OF_MEMORY;112113memset(&iterator, 0, sizeof(iterator));114iterator.json = array_field.value_start;115iterator.end = array_field.value_end;116117entry = response->recently_awarded;118while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {119if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))120return RC_MISSING_VALUE;121122if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[1], "DateAwarded"))123return RC_MISSING_VALUE;124entry->awarded = (time_t)timet;125126rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[2], "AvatarUrl", NULL);127if (!entry->avatar_url)128entry->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, entry->username);129130++entry;131}132}133134return RC_OK;135}136137void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response) {138rc_buffer_destroy(&response->response.buffer);139}140141/* --- Fetch Leaderboard Info --- */142143int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) {144return rc_api_init_fetch_leaderboard_info_request_hosted(request, api_params, &g_host);145}146147int rc_api_init_fetch_leaderboard_info_request_hosted(rc_api_request_t* request,148const rc_api_fetch_leaderboard_info_request_t* api_params,149const rc_api_host_t* host) {150rc_api_url_builder_t builder;151152rc_api_url_build_dorequest_url(request, host);153154if (api_params->leaderboard_id == 0)155return RC_INVALID_STATE;156157rc_url_builder_init(&builder, &request->buffer, 48);158rc_url_builder_append_str_param(&builder, "r", "lbinfo");159rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);160161if (api_params->username)162rc_url_builder_append_str_param(&builder, "u", api_params->username);163else if (api_params->first_entry > 1)164rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */165166rc_url_builder_append_unum_param(&builder, "c", api_params->count);167request->post_data = rc_url_builder_finalize(&builder);168request->content_type = RC_CONTENT_TYPE_URLENCODED;169170return builder.result;171}172173int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) {174rc_api_server_response_t response_obj;175176memset(&response_obj, 0, sizeof(response_obj));177response_obj.body = server_response;178response_obj.body_length = rc_json_get_object_string_length(server_response);179180return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj);181}182183int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) {184rc_api_lboard_info_entry_t* entry;185rc_json_field_t array_field;186rc_json_iterator_t iterator;187uint32_t timet;188int result;189size_t len;190char format[16];191192rc_json_field_t fields[] = {193RC_JSON_NEW_FIELD("Success"),194RC_JSON_NEW_FIELD("Error"),195RC_JSON_NEW_FIELD("LeaderboardData")196};197198rc_json_field_t leaderboarddata_fields[] = {199RC_JSON_NEW_FIELD("LBID"),200RC_JSON_NEW_FIELD("LBFormat"),201RC_JSON_NEW_FIELD("LowerIsBetter"),202RC_JSON_NEW_FIELD("LBTitle"),203RC_JSON_NEW_FIELD("LBDesc"),204RC_JSON_NEW_FIELD("LBMem"),205RC_JSON_NEW_FIELD("GameID"),206RC_JSON_NEW_FIELD("LBAuthor"),207RC_JSON_NEW_FIELD("LBCreated"),208RC_JSON_NEW_FIELD("LBUpdated"),209RC_JSON_NEW_FIELD("Entries"), /* array */210RC_JSON_NEW_FIELD("TotalEntries")211};212213rc_json_field_t entry_fields[] = {214RC_JSON_NEW_FIELD("User"),215RC_JSON_NEW_FIELD("Rank"),216RC_JSON_NEW_FIELD("Index"),217RC_JSON_NEW_FIELD("Score"),218RC_JSON_NEW_FIELD("DateSubmitted"),219RC_JSON_NEW_FIELD("AvatarUrl")220};221222memset(response, 0, sizeof(*response));223rc_buffer_init(&response->response.buffer);224225result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));226if (result != RC_OK)227return result;228229if (!rc_json_get_required_object(leaderboarddata_fields, sizeof(leaderboarddata_fields) / sizeof(leaderboarddata_fields[0]), &response->response, &fields[2], "LeaderboardData"))230return RC_MISSING_VALUE;231232if (!rc_json_get_required_unum(&response->id, &response->response, &leaderboarddata_fields[0], "LBID"))233return RC_MISSING_VALUE;234if (!rc_json_get_required_unum(&response->lower_is_better, &response->response, &leaderboarddata_fields[2], "LowerIsBetter"))235return RC_MISSING_VALUE;236if (!rc_json_get_required_string(&response->title, &response->response, &leaderboarddata_fields[3], "LBTitle"))237return RC_MISSING_VALUE;238if (!rc_json_get_required_string(&response->description, &response->response, &leaderboarddata_fields[4], "LBDesc"))239return RC_MISSING_VALUE;240if (!rc_json_get_required_string(&response->definition, &response->response, &leaderboarddata_fields[5], "LBMem"))241return RC_MISSING_VALUE;242if (!rc_json_get_required_unum(&response->game_id, &response->response, &leaderboarddata_fields[6], "GameID"))243return RC_MISSING_VALUE;244if (!rc_json_get_required_string(&response->author, &response->response, &leaderboarddata_fields[7], "LBAuthor"))245return RC_MISSING_VALUE;246if (!rc_json_get_required_datetime(&response->created, &response->response, &leaderboarddata_fields[8], "LBCreated"))247return RC_MISSING_VALUE;248if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))249return RC_MISSING_VALUE;250if (!rc_json_get_required_unum(&response->total_entries, &response->response, &leaderboarddata_fields[11], "TotalEntries"))251return RC_MISSING_VALUE;252253if (!leaderboarddata_fields[1].value_end)254return RC_MISSING_VALUE;255len = leaderboarddata_fields[1].value_end - leaderboarddata_fields[1].value_start - 2;256if (len < sizeof(format) - 1) {257memcpy(format, leaderboarddata_fields[1].value_start + 1, len);258format[len] = '\0';259response->format = rc_parse_format(format);260}261else {262response->format = RC_FORMAT_VALUE;263}264265if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries"))266return RC_MISSING_VALUE;267268if (response->num_entries) {269response->entries = (rc_api_lboard_info_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_lboard_info_entry_t));270if (!response->entries)271return RC_OUT_OF_MEMORY;272273memset(&iterator, 0, sizeof(iterator));274iterator.json = array_field.value_start;275iterator.end = array_field.value_end;276277entry = response->entries;278while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {279if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))280return RC_MISSING_VALUE;281282if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))283return RC_MISSING_VALUE;284285if (!rc_json_get_required_unum(&entry->index, &response->response, &entry_fields[2], "Index"))286return RC_MISSING_VALUE;287288if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[3], "Score"))289return RC_MISSING_VALUE;290291if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[4], "DateSubmitted"))292return RC_MISSING_VALUE;293entry->submitted = (time_t)timet;294295rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[5], "AvatarUrl", NULL);296if (!entry->avatar_url)297entry->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, entry->username);298299++entry;300}301}302303return RC_OK;304}305306void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response) {307rc_buffer_destroy(&response->response.buffer);308}309310/* --- Fetch Games List --- */311312int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) {313return rc_api_init_fetch_games_list_request_hosted(request, api_params, &g_host);314}315316int rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request,317const rc_api_fetch_games_list_request_t* api_params,318const rc_api_host_t* host) {319rc_api_url_builder_t builder;320321rc_api_url_build_dorequest_url(request, host);322323if (api_params->console_id == 0)324return RC_INVALID_STATE;325326rc_url_builder_init(&builder, &request->buffer, 48);327rc_url_builder_append_str_param(&builder, "r", "gameslist");328rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);329330request->post_data = rc_url_builder_finalize(&builder);331request->content_type = RC_CONTENT_TYPE_URLENCODED;332333return builder.result;334}335336int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) {337rc_api_server_response_t response_obj;338339memset(&response_obj, 0, sizeof(response_obj));340response_obj.body = server_response;341response_obj.body_length = rc_json_get_object_string_length(server_response);342343return rc_api_process_fetch_games_list_server_response(response, &response_obj);344}345346int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) {347rc_api_game_list_entry_t* entry;348rc_json_iterator_t iterator;349rc_json_field_t field;350int result;351char* end;352353rc_json_field_t fields[] = {354RC_JSON_NEW_FIELD("Success"),355RC_JSON_NEW_FIELD("Error"),356RC_JSON_NEW_FIELD("Response")357};358359memset(response, 0, sizeof(*response));360rc_buffer_init(&response->response.buffer);361362result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));363if (result != RC_OK)364return result;365366if (!fields[2].value_start) {367/* call rc_json_get_required_object to generate the error message */368rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");369return RC_MISSING_VALUE;370}371372response->num_entries = fields[2].array_size;373rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t)));374375response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t));376if (!response->entries)377return RC_OUT_OF_MEMORY;378379memset(&iterator, 0, sizeof(iterator));380iterator.json = fields[2].value_start;381iterator.end = fields[2].value_end;382383entry = response->entries;384while (rc_json_get_next_object_field(&iterator, &field)) {385entry->id = strtol(field.name, &end, 10);386387field.name = "";388if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))389return RC_MISSING_VALUE;390391++entry;392}393394return RC_OK;395}396397void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {398rc_buffer_destroy(&response->response.buffer);399}400401/* --- Fetch Game Titles --- */402403int rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params) {404return rc_api_init_fetch_game_titles_request_hosted(request, api_params, &g_host);405}406407int rc_api_init_fetch_game_titles_request_hosted(rc_api_request_t* request,408const rc_api_fetch_game_titles_request_t* api_params,409const rc_api_host_t* host) {410rc_api_url_builder_t builder;411char num[16];412uint32_t i;413414rc_api_url_build_dorequest_url(request, host);415416if (api_params->num_game_ids == 0)417return RC_INVALID_STATE;418419rc_url_builder_init(&builder, &request->buffer, 48);420rc_url_builder_append_str_param(&builder, "r", "gameinfolist");421rc_url_builder_append_unum_param(&builder, "g", api_params->game_ids[0]);422423for (i = 1; i < api_params->num_game_ids; i++) {424int chars = snprintf(num, sizeof(num), "%u", api_params->game_ids[i]);425rc_url_builder_append(&builder, ",", 1);426rc_url_builder_append(&builder, num, chars);427}428429request->post_data = rc_url_builder_finalize(&builder);430request->content_type = RC_CONTENT_TYPE_URLENCODED;431432return builder.result;433}434435int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response) {436rc_api_game_title_entry_t* entry;437rc_json_iterator_t iterator;438rc_json_field_t array_field;439int result;440441rc_json_field_t fields[] = {442RC_JSON_NEW_FIELD("Success"),443RC_JSON_NEW_FIELD("Error"),444RC_JSON_NEW_FIELD("Response")445};446447rc_json_field_t entry_fields[] = {448RC_JSON_NEW_FIELD("ID"),449RC_JSON_NEW_FIELD("Title"),450RC_JSON_NEW_FIELD("ImageIcon")451};452453memset(response, 0, sizeof(*response));454rc_buffer_init(&response->response.buffer);455456result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));457if (result != RC_OK)458return result;459460if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response"))461return RC_MISSING_VALUE;462463if (response->num_entries) {464response->entries = (rc_api_game_title_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_title_entry_t));465if (!response->entries)466return RC_OUT_OF_MEMORY;467468memset(&iterator, 0, sizeof(iterator));469iterator.json = array_field.value_start;470iterator.end = array_field.value_end;471472entry = response->entries;473while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {474if (!rc_json_get_required_unum(&entry->id, &response->response, &entry_fields[0], "ID"))475return RC_MISSING_VALUE;476if (!rc_json_get_required_string(&entry->title, &response->response, &entry_fields[1], "Title"))477return RC_MISSING_VALUE;478479/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */480rc_json_extract_filename(&entry_fields[2]);481if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon"))482return RC_MISSING_VALUE;483484++entry;485}486}487488return RC_OK;489}490491void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) {492rc_buffer_destroy(&response->response.buffer);493}494495/* --- Fetch Game Hashes --- */496497int rc_api_init_fetch_hash_library_request(rc_api_request_t* request,498const rc_api_fetch_hash_library_request_t* api_params)499{500return rc_api_init_fetch_hash_library_request_hosted(request, api_params, &g_host);501}502503int rc_api_init_fetch_hash_library_request_hosted(rc_api_request_t* request,504const rc_api_fetch_hash_library_request_t* api_params,505const rc_api_host_t* host)506{507rc_api_url_builder_t builder;508rc_api_url_build_dorequest_url(request, host);509510/* note: unauthenticated request */511rc_url_builder_init(&builder, &request->buffer, 48);512rc_url_builder_append_str_param(&builder, "r", "hashlibrary");513if (api_params->console_id != 0)514rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);515516request->post_data = rc_url_builder_finalize(&builder);517request->content_type = RC_CONTENT_TYPE_URLENCODED;518519return builder.result;520}521522int rc_api_process_fetch_hash_library_server_response(rc_api_fetch_hash_library_response_t* response,523const rc_api_server_response_t* server_response)524{525rc_api_hash_library_entry_t* entry;526rc_json_iterator_t iterator;527rc_json_field_t field;528int result;529530rc_json_field_t fields[] = {531RC_JSON_NEW_FIELD("Success"),532RC_JSON_NEW_FIELD("Error"),533RC_JSON_NEW_FIELD("MD5List"),534};535536memset(response, 0, sizeof(*response));537rc_buffer_init(&response->response.buffer);538539result =540rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));541if (result != RC_OK)542return result;543544if (!fields[2].value_start) {545/* call rc_json_get_required_object to generate the error message */546rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "MD5List");547return RC_MISSING_VALUE;548}549550response->num_entries = fields[2].array_size;551if (response->num_entries > 0) {552rc_buffer_reserve(&response->response.buffer, response->num_entries * (33 + sizeof(rc_api_hash_library_entry_t)));553554response->entries = (rc_api_hash_library_entry_t*)rc_buffer_alloc(555&response->response.buffer, response->num_entries * sizeof(rc_api_hash_library_entry_t));556if (!response->entries)557return RC_OUT_OF_MEMORY;558559memset(&iterator, 0, sizeof(iterator));560iterator.json = fields[2].value_start;561iterator.end = fields[2].value_end;562563entry = response->entries;564while (rc_json_get_next_object_field(&iterator, &field)) {565entry->hash = rc_buffer_strncpy(&response->response.buffer, field.name, field.name_len);566567field.name = "";568if (!rc_json_get_unum(&entry->game_id, &field, ""))569return RC_MISSING_VALUE;570571++entry;572}573}574575return RC_OK;576}577578void rc_api_destroy_fetch_hash_library_response(rc_api_fetch_hash_library_response_t* response)579{580rc_buffer_destroy(&response->response.buffer);581}582583584