Duckstation/dep/rcheevos/src/rapi/rc_api_runtime.c

649 lines
25 KiB
C
Raw Normal View History

2022-07-18 12:46:12 +00:00
#include "rc_api_runtime.h"
#include "rc_api_common.h"
#include "rc_runtime.h"
#include "rc_runtime_types.h"
2023-11-06 09:41:10 +00:00
#include "../rc_compat.h"
2022-07-18 12:46:12 +00:00
#include "../rhash/md5.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* --- Resolve Hash --- */
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (!api_params->game_hash || !*api_params->game_hash)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "gameid");
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
2022-07-18 12:46:12 +00:00
return builder.result;
}
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_resolve_hash_server_response(response, &response_obj);
}
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) {
2022-07-18 12:46:12 +00:00
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("GameID")
2022-07-18 12:46:12 +00:00
};
memset(response, 0, sizeof(*response));
2023-11-06 09:41:10 +00:00
rc_buffer_init(&response->response.buffer);
2022-07-18 12:46:12 +00:00
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
2022-07-18 12:46:12 +00:00
if (result != RC_OK)
return result;
rc_json_get_required_unum(&response->game_id, &response->response, &fields[2], "GameID");
return RC_OK;
}
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response) {
2023-11-06 09:41:10 +00:00
rc_buffer_destroy(&response->response.buffer);
2022-07-18 12:46:12 +00:00
}
/* --- Fetch Game Data --- */
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
2022-07-18 12:46:12 +00:00
}
return builder.result;
}
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
}
2024-01-13 04:24:04 +00:00
static int rc_parse_achievement_type(const char* type)
{
if (strcmp(type, "missable") == 0)
return RC_ACHIEVEMENT_TYPE_MISSABLE;
if (strcmp(type, "win_condition") == 0)
return RC_ACHIEVEMENT_TYPE_WIN;
if (strcmp(type, "progression") == 0)
return RC_ACHIEVEMENT_TYPE_PROGRESSION;
return RC_ACHIEVEMENT_TYPE_STANDARD;
}
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
2022-07-18 12:46:12 +00:00
rc_api_achievement_definition_t* achievement;
rc_api_leaderboard_definition_t* leaderboard;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
2022-07-18 12:46:12 +00:00
const char* str;
const char* last_author = "";
const char* last_author_field = "";
2022-07-18 12:46:12 +00:00
size_t last_author_len = 0;
size_t len;
2023-11-06 09:41:10 +00:00
uint32_t timet;
2022-07-18 12:46:12 +00:00
int result;
char format[16];
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("PatchData") /* nested object */
2022-07-18 12:46:12 +00:00
};
rc_json_field_t patchdata_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("ImageIcon"),
RC_JSON_NEW_FIELD("RichPresencePatch"),
RC_JSON_NEW_FIELD("Achievements"), /* array */
RC_JSON_NEW_FIELD("Leaderboards") /* array */
2022-07-18 12:46:12 +00:00
};
rc_json_field_t achievement_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Description"),
RC_JSON_NEW_FIELD("Flags"),
RC_JSON_NEW_FIELD("Points"),
RC_JSON_NEW_FIELD("MemAddr"),
RC_JSON_NEW_FIELD("Author"),
RC_JSON_NEW_FIELD("BadgeName"),
RC_JSON_NEW_FIELD("Created"),
2024-01-13 04:24:04 +00:00
RC_JSON_NEW_FIELD("Modified"),
RC_JSON_NEW_FIELD("Type"),
RC_JSON_NEW_FIELD("Rarity"),
RC_JSON_NEW_FIELD("RarityHardcore")
2022-07-18 12:46:12 +00:00
};
rc_json_field_t leaderboard_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Description"),
RC_JSON_NEW_FIELD("Mem"),
RC_JSON_NEW_FIELD("Format"),
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("Hidden")
2022-07-18 12:46:12 +00:00
};
memset(response, 0, sizeof(*response));
2023-11-06 09:41:10 +00:00
rc_buffer_init(&response->response.buffer);
2022-07-18 12:46:12 +00:00
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
2022-07-18 12:46:12 +00:00
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->title, &response->response, &patchdata_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->console_id, &response->response, &patchdata_fields[2], "ConsoleID"))
return RC_MISSING_VALUE;
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
if (patchdata_fields[3].value_end) {
str = patchdata_fields[3].value_end - 5;
if (memcmp(str, ".png\"", 5) == 0) {
patchdata_fields[3].value_end -= 5;
while (str > patchdata_fields[3].value_start && str[-1] != '/')
--str;
patchdata_fields[3].value_start = str;
}
}
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
and add space for the structures. */
len = patchdata_fields[4].value_end - patchdata_fields[4].value_start; /* rich presence */
len += (patchdata_fields[5].value_end - patchdata_fields[5].value_start) - /* achievements */
patchdata_fields[5].array_size * (130 - sizeof(rc_api_achievement_definition_t));
len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* leaderboards */
patchdata_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
2023-11-06 09:41:10 +00:00
rc_buffer_reserve(&response->response.buffer, len);
2022-07-18 12:46:12 +00:00
/* end estimation */
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[4], "RichPresencePatch", "");
if (!response->rich_presence_script)
response->rich_presence_script = "";
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements"))
2022-07-18 12:46:12 +00:00
return RC_MISSING_VALUE;
if (response->num_achievements) {
2023-11-06 09:41:10 +00:00
response->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_achievements * sizeof(rc_api_achievement_definition_t));
2022-07-18 12:46:12 +00:00
if (!response->achievements)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
2022-07-18 12:46:12 +00:00
achievement = response->achievements;
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
return RC_MISSING_VALUE;
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
2022-07-18 12:46:12 +00:00
achievement->author = last_author;
}
else {
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
return RC_MISSING_VALUE;
last_author = achievement->author;
last_author_field = achievement_fields[6].value_start;
2022-07-18 12:46:12 +00:00
last_author_len = len;
}
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
return RC_MISSING_VALUE;
achievement->created = (time_t)timet;
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
return RC_MISSING_VALUE;
achievement->updated = (time_t)timet;
2024-01-13 04:24:04 +00:00
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
if (achievement_fields[10].value_end) {
len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, achievement_fields[10].value_start + 1, len);
format[len] = '\0';
achievement->type = rc_parse_achievement_type(format);
}
}
/* legacy support : if title contains[m], change type to missable and remove[m] from title */
if (memcmp(achievement->title, "[m]", 3) == 0) {
len = 3;
while (achievement->title[len] == ' ')
++len;
achievement->title += len;
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
}
else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) {
len = strlen(achievement->title) - 3;
while (achievement->title[len - 1] == ' ')
--len;
((char*)achievement->title)[len] = '\0';
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
}
rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0);
rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0);
2022-07-18 12:46:12 +00:00
++achievement;
}
}
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards"))
2022-07-18 12:46:12 +00:00
return RC_MISSING_VALUE;
if (response->num_leaderboards) {
2023-11-06 09:41:10 +00:00
response->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
2022-07-18 12:46:12 +00:00
if (!response->leaderboards)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
2022-07-18 12:46:12 +00:00
leaderboard = response->leaderboards;
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
return RC_MISSING_VALUE;
2023-11-06 09:41:10 +00:00
rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0);
leaderboard->lower_is_better = (uint8_t)result;
rc_json_get_optional_bool(&result, &leaderboard_fields[6], "Hidden", 0);
leaderboard->hidden = (uint8_t)result;
2022-07-18 12:46:12 +00:00
if (!leaderboard_fields[4].value_end)
return RC_MISSING_VALUE;
len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, leaderboard_fields[4].value_start + 1, len);
format[len] = '\0';
leaderboard->format = rc_parse_format(format);
}
else {
leaderboard->format = RC_FORMAT_VALUE;
}
++leaderboard;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response) {
2023-11-06 09:41:10 +00:00
rc_buffer_destroy(&response->response.buffer);
2022-07-18 12:46:12 +00:00
}
/* --- Ping --- */
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "ping", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
if (api_params->rich_presence && *api_params->rich_presence)
rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
2024-01-13 04:24:04 +00:00
if (api_params->game_hash && *api_params->game_hash) {
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
rc_url_builder_append_str_param(&builder, "x", api_params->game_hash);
}
2022-07-18 12:46:12 +00:00
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
2022-07-18 12:46:12 +00:00
}
return builder.result;
}
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_ping_server_response(response, &response_obj);
}
int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response) {
2022-07-18 12:46:12 +00:00
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error")
2022-07-18 12:46:12 +00:00
};
memset(response, 0, sizeof(*response));
2023-11-06 09:41:10 +00:00
rc_buffer_init(&response->response.buffer);
2022-07-18 12:46:12 +00:00
return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
2022-07-18 12:46:12 +00:00
}
void rc_api_destroy_ping_response(rc_api_ping_response_t* response) {
2023-11-06 09:41:10 +00:00
rc_buffer_destroy(&response->response.buffer);
2022-07-18 12:46:12 +00:00
}
/* --- Award Achievement --- */
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t digest[16];
rc_api_url_build_dorequest_url(request);
if (api_params->achievement_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 96);
if (rc_api_url_build_dorequest(&builder, "awardachievement", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
/* Evaluate the signature. */
md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, digest);
2023-11-06 09:41:10 +00:00
rc_format_md5(buffer, digest);
2022-07-18 12:46:12 +00:00
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
2022-07-18 12:46:12 +00:00
}
return builder.result;
}
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_award_achievement_server_response(response, &response_obj);
}
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) {
2022-07-18 12:46:12 +00:00
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("SoftcoreScore"),
RC_JSON_NEW_FIELD("AchievementID"),
RC_JSON_NEW_FIELD("AchievementsRemaining")
2022-07-18 12:46:12 +00:00
};
memset(response, 0, sizeof(*response));
2023-11-06 09:41:10 +00:00
rc_buffer_init(&response->response.buffer);
2022-07-18 12:46:12 +00:00
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
2022-07-18 12:46:12 +00:00
if (result != RC_OK)
return result;
if (!response->response.succeeded) {
if (response->response.error_message &&
memcmp(response->response.error_message, "User already has", 16) == 0) {
/* not really an error, the achievement is unlocked, just not by the current call.
* hardcore: User already has hardcore and regular achievements awarded.
* non-hardcore: User already has this achievement awarded.
*/
response->response.succeeded = 1;
} else {
return result;
}
}
rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0);
rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0);
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0);
rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1);
2022-07-18 12:46:12 +00:00
return RC_OK;
}
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response) {
2023-11-06 09:41:10 +00:00
rc_buffer_destroy(&response->response.buffer);
2022-07-18 12:46:12 +00:00
}
/* --- Submit Leaderboard Entry --- */
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t digest[16];
rc_api_url_build_dorequest_url(request);
if (api_params->leaderboard_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 96);
if (rc_api_url_build_dorequest(&builder, "submitlbentry", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
rc_url_builder_append_num_param(&builder, "s", api_params->score);
if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
/* Evaluate the signature. */
md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->score);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, digest);
2023-11-06 09:41:10 +00:00
rc_format_md5(buffer, digest);
2022-07-18 12:46:12 +00:00
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
2022-07-18 12:46:12 +00:00
}
return builder.result;
}
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_submit_lboard_entry_server_response(response, &response_obj);
}
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) {
2022-07-18 12:46:12 +00:00
rc_api_lboard_entry_t* entry;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
2022-07-18 12:46:12 +00:00
const char* str;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response") /* nested object */
2022-07-18 12:46:12 +00:00
};
rc_json_field_t response_fields[] = {
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("BestScore"),
RC_JSON_NEW_FIELD("RankInfo"), /* nested object */
RC_JSON_NEW_FIELD("TopEntries") /* array */
2022-07-18 12:46:12 +00:00
/* unused fields
RC_JSON_NEW_FIELD("LBData"), / * array * /
RC_JSON_NEW_FIELD("ScoreFormatted"),
RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * /
2022-07-18 12:46:12 +00:00
* unused fields */
};
/* unused fields
rc_json_field_t lbdata_fields[] = {
RC_JSON_NEW_FIELD("Format"),
RC_JSON_NEW_FIELD("LeaderboardID"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("LowerIsBetter")
2022-07-18 12:46:12 +00:00
};
* unused fields */
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("Score")
2022-07-18 12:46:12 +00:00
/* unused fields
RC_JSON_NEW_FIELD("DateSubmitted")
2022-07-18 12:46:12 +00:00
* unused fields */
};
rc_json_field_t rank_info_fields[] = {
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("NumEntries")
2022-07-18 12:46:12 +00:00
/* unused fields
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("UserRank")
2022-07-18 12:46:12 +00:00
* unused fields */
};
memset(response, 0, sizeof(*response));
2023-11-06 09:41:10 +00:00
rc_buffer_init(&response->response.buffer);
2022-07-18 12:46:12 +00:00
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
2022-07-18 12:46:12 +00:00
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->submitted_score, &response->response, &response_fields[0], "Score"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->best_score, &response->response, &response_fields[1], "BestScore"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_object(rank_info_fields, sizeof(rank_info_fields) / sizeof(rank_info_fields[0]), &response->response, &response_fields[2], "RankInfo"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->new_rank, &response->response, &rank_info_fields[0], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&str, &response->response, &rank_info_fields[1], "NumEntries"))
return RC_MISSING_VALUE;
response->num_entries = (unsigned)atoi(str);
if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries"))
2022-07-18 12:46:12 +00:00
return RC_MISSING_VALUE;
if (response->num_top_entries) {
2023-11-06 09:41:10 +00:00
response->top_entries = (rc_api_lboard_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_top_entries * sizeof(rc_api_lboard_entry_t));
2022-07-18 12:46:12 +00:00
if (!response->top_entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
2022-07-18 12:46:12 +00:00
entry = response->top_entries;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[2], "Score"))
return RC_MISSING_VALUE;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response) {
2023-11-06 09:41:10 +00:00
rc_buffer_destroy(&response->response.buffer);
2022-07-18 12:46:12 +00:00
}