|
|
|
|
@ -3902,6 +3902,193 @@ void rc_client_destroy_game_title_list(rc_client_game_title_list_t* list)
|
|
|
|
|
free(list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ===== Fetch Games List ===== */
|
|
|
|
|
|
|
|
|
|
typedef struct rc_client_fetch_game_list_callback_data_t {
|
|
|
|
|
rc_client_t* client;
|
|
|
|
|
rc_client_fetch_game_list_callback_t callback;
|
|
|
|
|
void* callback_userdata;
|
|
|
|
|
rc_client_async_handle_t async_handle;
|
|
|
|
|
} rc_client_fetch_game_list_callback_data_t;
|
|
|
|
|
|
|
|
|
|
static void rc_client_fetch_game_list_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
|
|
|
|
{
|
|
|
|
|
rc_client_fetch_game_list_callback_data_t* list_callback_data =
|
|
|
|
|
(rc_client_fetch_game_list_callback_data_t*)callback_data;
|
|
|
|
|
rc_client_t* client = list_callback_data->client;
|
|
|
|
|
rc_api_fetch_games_list_response_t list_response;
|
|
|
|
|
const char* error_message;
|
|
|
|
|
int result;
|
|
|
|
|
|
|
|
|
|
result = rc_client_end_async(client, &list_callback_data->async_handle);
|
|
|
|
|
if (result) {
|
|
|
|
|
if (result != RC_CLIENT_ASYNC_DESTROYED)
|
|
|
|
|
RC_CLIENT_LOG_VERBOSE(client, "Fetch game list aborted");
|
|
|
|
|
|
|
|
|
|
free(list_callback_data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = rc_api_process_fetch_games_list_server_response(&list_response, server_response);
|
|
|
|
|
error_message =
|
|
|
|
|
rc_client_server_error_message(&result, server_response->http_status_code, &list_response.response);
|
|
|
|
|
if (error_message) {
|
|
|
|
|
RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch game list failed: %s", error_message);
|
|
|
|
|
list_callback_data->callback(result, error_message, NULL, client, list_callback_data->callback_userdata);
|
|
|
|
|
} else {
|
|
|
|
|
rc_client_game_list_t* list;
|
|
|
|
|
size_t strings_size = 0, hashes_size = 0;
|
|
|
|
|
const rc_api_game_list_entry_t* src;
|
|
|
|
|
const rc_api_game_list_entry_t* stop;
|
|
|
|
|
size_t list_size;
|
|
|
|
|
uint32_t i;
|
|
|
|
|
|
|
|
|
|
/* calculate string buffer size */
|
|
|
|
|
for (src = list_response.entries, stop = src + list_response.num_entries; src < stop; ++src) {
|
|
|
|
|
if (src->name)
|
|
|
|
|
strings_size += strlen(src->name) + 1;
|
|
|
|
|
if (src->image_name)
|
|
|
|
|
strings_size += strlen(src->image_name) + 1;
|
|
|
|
|
if (src->image_url)
|
|
|
|
|
strings_size += strlen(src->image_url) + 1;
|
|
|
|
|
hashes_size += src->num_supported_hashes * sizeof(const char*);
|
|
|
|
|
for (i = 0; i < src->num_supported_hashes; i++)
|
|
|
|
|
strings_size += strlen(src->supported_hashes[i]) + 1;
|
|
|
|
|
hashes_size += src->num_unsupported_hashes * sizeof(const char*);
|
|
|
|
|
for (i = 0; i < src->num_unsupported_hashes; i++)
|
|
|
|
|
strings_size += strlen(src->unsupported_hashes[i]) + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_size = sizeof(*list) + sizeof(rc_client_game_list_entry_t) * list_response.num_entries +
|
|
|
|
|
sizeof(const char*) * hashes_size + strings_size;
|
|
|
|
|
list = (rc_client_game_list_t*)malloc(list_size);
|
|
|
|
|
if (!list) {
|
|
|
|
|
list_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client,
|
|
|
|
|
list_callback_data->callback_userdata);
|
|
|
|
|
} else {
|
|
|
|
|
rc_client_game_list_entry_t* entry = list->entries =
|
|
|
|
|
(rc_client_game_list_entry_t*)((uint8_t*)list + sizeof(*list));
|
|
|
|
|
const char** hash_list =
|
|
|
|
|
(const char**)((uint8_t*)entry + sizeof(rc_client_game_list_entry_t) * list_response.num_entries);
|
|
|
|
|
char* strings = (char*)((uint8_t*)hash_list + sizeof(const char*) * hashes_size);
|
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
|
|
for (src = list_response.entries, stop = src + list_response.num_entries; src < stop; ++src, ++entry) {
|
|
|
|
|
entry->id = src->id;
|
|
|
|
|
entry->num_achievements = src->num_achievements;
|
|
|
|
|
entry->num_leaderboards = src->num_leaderboards;
|
|
|
|
|
entry->points = src->points;
|
|
|
|
|
|
|
|
|
|
if (src->name) {
|
|
|
|
|
len = strlen(src->name) + 1;
|
|
|
|
|
entry->name = strings;
|
|
|
|
|
memcpy(strings, src->name, len);
|
|
|
|
|
strings += len;
|
|
|
|
|
} else {
|
|
|
|
|
entry->name = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (src->image_name) {
|
|
|
|
|
len = strlen(src->image_name) + 1;
|
|
|
|
|
entry->image_name = strings;
|
|
|
|
|
memcpy(strings, src->image_name, len);
|
|
|
|
|
strings += len;
|
|
|
|
|
} else {
|
|
|
|
|
entry->image_name = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (src->image_url) {
|
|
|
|
|
len = strlen(src->image_url) + 1;
|
|
|
|
|
entry->image_url = strings;
|
|
|
|
|
memcpy(strings, src->image_url, len);
|
|
|
|
|
strings += len;
|
|
|
|
|
} else {
|
|
|
|
|
entry->image_url = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((entry->num_supported_hashes = src->num_supported_hashes) > 0) {
|
|
|
|
|
entry->supported_hashes = hash_list;
|
|
|
|
|
for (i = 0; i < src->num_supported_hashes; i++) {
|
|
|
|
|
len = strlen(src->supported_hashes[i]) + 1;
|
|
|
|
|
*(hash_list++) = strings;
|
|
|
|
|
memcpy(strings, src->supported_hashes[i], len);
|
|
|
|
|
strings += len;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
entry->supported_hashes = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((entry->num_unsupported_hashes = src->num_unsupported_hashes) > 0) {
|
|
|
|
|
entry->unsupported_hashes = hash_list;
|
|
|
|
|
for (i = 0; i < src->num_unsupported_hashes; i++) {
|
|
|
|
|
len = strlen(src->unsupported_hashes[i]) + 1;
|
|
|
|
|
*(hash_list++) = strings;
|
|
|
|
|
memcpy(strings, src->unsupported_hashes[i], len);
|
|
|
|
|
strings += len;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
entry->unsupported_hashes = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list->num_entries = list_response.num_entries;
|
|
|
|
|
|
|
|
|
|
list_callback_data->callback(RC_OK, NULL, list, client, list_callback_data->callback_userdata);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc_api_destroy_fetch_games_list_response(&list_response);
|
|
|
|
|
free(list_callback_data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc_client_async_handle_t* rc_client_begin_fetch_game_list(rc_client_t* client, uint32_t console_id,
|
|
|
|
|
rc_client_fetch_game_list_callback_t callback,
|
|
|
|
|
void* callback_userdata)
|
|
|
|
|
{
|
|
|
|
|
rc_api_fetch_games_list_request_t api_params;
|
|
|
|
|
rc_client_fetch_game_list_callback_data_t* callback_data;
|
|
|
|
|
rc_client_async_handle_t* async_handle;
|
|
|
|
|
rc_api_request_t request;
|
|
|
|
|
int result;
|
|
|
|
|
const char* error_message;
|
|
|
|
|
|
|
|
|
|
if (!client) {
|
|
|
|
|
callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
api_params.console_id = console_id;
|
|
|
|
|
result = rc_api_init_fetch_games_list_request_hosted(&request, &api_params, &client->state.host);
|
|
|
|
|
|
|
|
|
|
if (result != RC_OK) {
|
|
|
|
|
error_message = rc_error_str(result);
|
|
|
|
|
callback(result, error_message, NULL, client, callback_userdata);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback_data = (rc_client_fetch_game_list_callback_data_t*)calloc(1, sizeof(*callback_data));
|
|
|
|
|
if (!callback_data) {
|
|
|
|
|
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback_data->client = client;
|
|
|
|
|
callback_data->callback = callback;
|
|
|
|
|
callback_data->callback_userdata = callback_userdata;
|
|
|
|
|
|
|
|
|
|
async_handle = &callback_data->async_handle;
|
|
|
|
|
rc_client_begin_async(client, async_handle);
|
|
|
|
|
client->callbacks.server_call(&request, rc_client_fetch_game_list_callback, callback_data, client);
|
|
|
|
|
rc_api_destroy_request(&request);
|
|
|
|
|
|
|
|
|
|
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void rc_client_destroy_game_list(rc_client_game_list_t* list)
|
|
|
|
|
{
|
|
|
|
|
free(list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ===== Achievements ===== */
|
|
|
|
|
|
|
|
|
|
static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time)
|
|
|
|
|
|