dep/rcheevos: Add game list functions to rc_client

This commit is contained in:
Stenzek
2026-06-14 15:37:26 +10:00
parent 498921157a
commit 63caefb96b
2 changed files with 241 additions and 0 deletions
+54
View File
@@ -455,6 +455,60 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_titles(
*/
RC_EXPORT void RC_CCONV rc_client_destroy_game_title_list(rc_client_game_title_list_t* list);
/*****************************************************************************\
| Fetch Games List|
\*****************************************************************************/
typedef struct rc_client_game_list_entry_t {
/* The unique identifier of the game */
uint32_t id;
/* The number of achievements in the game */
uint32_t num_achievements;
/* The number of leaderboards in the game */
uint32_t num_leaderboards;
/* The number of points in the game */
uint32_t points;
/* The name of the game */
const char* name;
/* The image name for the game badge */
const char* image_name;
/* The URL for the game badge image */
const char* image_url;
/* An array of supported hashes */
const char** supported_hashes;
/* An array of unsupported hashes */
const char** unsupported_hashes;
/* The number of items in the supported_hashes array */
uint32_t num_supported_hashes;
/* The number of items in the unsupported_hashes array */
uint32_t num_unsupported_hashes;
} rc_client_game_list_entry_t;
typedef struct rc_client_game_list_t {
rc_client_game_list_entry_t* entries;
uint32_t num_entries;
} rc_client_game_list_t;
/**
* Callback that is fired when a games list request completes. list may be null if the query failed.
*/
typedef void(RC_CCONV* rc_client_fetch_game_list_callback_t)(int result, const char* error_message,
rc_client_game_list_t* list, rc_client_t* client,
void* callback_userdata);
/**
* Starts an asynchronous request for all games for the given console.
* This request returns a mapping from hashes to the game's unique identifier. A single game may have multiple
* hashes in the case of multi-disc games, or variants that are still compatible with the same achievement set.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV 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);
/**
* Destroys a previously-allocated result from the rc_client_destroy_game_list() callback.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_game_list(rc_client_game_list_t* list);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
+187
View File
@@ -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)