Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cheevos/cheevos.c
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,15 @@ static void rcheevos_award_achievement(const rc_client_achievement_t* cheevo)
snprintf(subtitle, sizeof(subtitle), "%s (%lu)", cheevo->title, (unsigned long)cheevo->points);

gfx_widgets_push_achievement(title, subtitle, cheevo->badge_name);

/* if all badges haven't been loaded, preload the next one assuming it will be the next needed */
if (!rcheevos_locals.badges_loaded)
{
const rc_client_achievement_t* next_locked_achievement =
rc_client_get_next_achievement_info(rcheevos_locals.client, cheevo, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
if (next_locked_achievement)
rcheevos_client_download_badge_from_url(next_locked_achievement->badge_url, next_locked_achievement->badge_name);
}
}
else
#endif
Expand Down Expand Up @@ -1436,12 +1445,18 @@ static void rcheevos_finalize_game_load(rc_client_t* client)
#endif
if (want_badges) /* prefetch the game badge */
{
const rc_client_achievement_t* first_locked_achievement = NULL;
const rc_client_game_t* game = rc_client_get_game_info(client);
char badge[32];

badge[0] = 'i';
strlcpy(&badge[1], game->badge_name, sizeof(badge) - 1);
rcheevos_client_download_badge_from_url(game->badge_url, badge);

/* predownload the first badge the player hasn't earned, assuming it will be the next to unlock */
first_locked_achievement = rc_client_get_next_achievement_info(client, NULL, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
if (first_locked_achievement)
rcheevos_client_download_badge_from_url(first_locked_achievement->badge_url, first_locked_achievement->badge_name);
}

if (!rc_client_is_processing_required(client))
Expand Down
4 changes: 2 additions & 2 deletions cheevos/cheevos_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,15 @@ void rcheevos_client_server_call(const rc_api_request_t* request,
rcheevos_log_post_url(request->url, request->post_data);

#ifdef CHEEVOS_JSON_OVERRIDE
if (strstr(request->post_data, "r=patch"))
if (strstr(request->post_data, "r=patch") || strstr(request->post_data, "r=achievementsets"))
{
rcheevos_client_http_load_response(request, callback, callback_data);
return;
}
#endif

#ifdef CHEEVOS_SAVE_JSON
if (strstr(request->post_data, "r=patch"))
if (strstr(request->post_data, "r=patch") || strstr(request->post_data, "r=achievementsets"))
{
task_push_http_post_transfer_with_user_agent(request->url,
request->post_data, true, "POST", rcheevos_locals->user_agent_core,
Expand Down
8 changes: 8 additions & 0 deletions deps/rcheevos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v12.3.0
* add rc_client_get_next_achievement_info
* rc_client image functions will now return RC_INSUFFICENT_BUFFER instead of truncating if buffer is not large enough
* fix race condition where rich presence from previous game may get associated to current game
* fix rc_client_has_leaderboards returning true if the game only has hidden leaderboards
* fix memory leak parsing large achievements
* fix incomplete rich presence display condition affecting later display conditions

# v12.2.1
* fix parsing of leaderboards with comparisons in legacy-formatted values
* fix validation warning on long AddSource chains
Expand Down
5 changes: 5 additions & 0 deletions deps/rcheevos/include/rc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,11 @@ typedef struct rc_client_achievement_t {
*/
RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id);

/**
* Gets the next achievement after a provided achievement that fits in the specified bucket. Returns NULL if none found.
*/
RC_EXPORT const rc_client_achievement_t * RC_CCONV rc_client_get_next_achievement_info(rc_client_t * client, const rc_client_achievement_t * achievement, int bucket);

/**
* Gets the URL for the achievement image.
* Returns RC_OK on success.
Expand Down
124 changes: 107 additions & 17 deletions deps/rcheevos/src/rc_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,13 @@ static int rc_client_get_image_url(char buffer[], size_t buffer_size, int image_
image_request.image_name = image_name;
result = rc_api_init_fetch_image_request_hosted(&request, &image_request, NULL);
if (result == RC_OK)
snprintf(buffer, buffer_size, "%s", request.url);
{
const size_t len = strlen(request.url);
if (len >= buffer_size)
result = RC_INSUFFICIENT_BUFFER;
else
memcpy(buffer, request.url, len + 1);
}

rc_api_destroy_request(&request);
return result;
Expand Down Expand Up @@ -898,7 +904,11 @@ int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], si
return RC_INVALID_STATE;

if (user->avatar_url) {
snprintf(buffer, buffer_size, "%s", user->avatar_url);
const size_t len = strlen(user->avatar_url);
if (len >= buffer_size)
return RC_INSUFFICIENT_BUFFER;

memcpy(buffer, user->avatar_url, len + 1);
return RC_OK;
}

Expand Down Expand Up @@ -3514,7 +3524,11 @@ int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], si
return RC_INVALID_STATE;

if (game->badge_url) {
snprintf(buffer, buffer_size, "%s", game->badge_url);
const size_t len = strlen(game->badge_url);
if (len >= buffer_size)
return RC_INSUFFICIENT_BUFFER;

memcpy(buffer, game->badge_url, len + 1);
return RC_OK;
}

Expand Down Expand Up @@ -4307,6 +4321,62 @@ const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* clien
return NULL;
}

const rc_client_achievement_t* rc_client_get_next_achievement_info(rc_client_t* client,
const rc_client_achievement_t* achievement, int bucket)
{
const rc_client_achievement_info_t* after = (const rc_client_achievement_info_t*)achievement;
rc_client_achievement_info_t* achievement_info;
time_t recent_unlock_time;
rc_client_subset_info_t* subset;

if (!client)
return NULL;

#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_next_achievement_info)
return client->state.external_client->get_next_achievement_info(achievement ? achievement->id : 0, bucket);
#endif

if (!client->game)
return NULL;

recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS;
for (subset = client->game->subsets; subset; subset = subset->next) {
if (subset->active && subset->public_.num_achievements > 0) {
const rc_client_achievement_info_t* start = subset->achievements;
const rc_client_achievement_info_t* stop = start + subset->public_.num_achievements;
if (after == NULL || (after >= start && after <= stop)) {
/* found a subset containing the provided achievement. look for the next
* achievement matching the requested bucket */
uint32_t index = after ? (uint32_t)(after - start) + 1 : 0;
do {
if (index >= subset->public_.num_achievements) {
/* done with this subset. find the next active subset with achievements */
do {
subset = subset->next;
if (!subset)
return NULL;
} while (!subset->active || subset->public_.num_achievements == 0);

index = 0;
}

/* found an achievement. check to see if it matches the requested bucket. */
achievement_info = &subset->achievements[index];
rc_client_update_achievement_display_information(client, achievement_info, recent_unlock_time);
if (achievement_info->public_.bucket == bucket)
return &achievement_info->public_;

++index;
} while (1);
}
}
}

return NULL;
}


int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size)
{
const int image_type = (state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) ?
Expand All @@ -4316,12 +4386,20 @@ int rc_client_achievement_get_image_url(const rc_client_achievement_t* achieveme
return rc_client_get_image_url(buffer, buffer_size, image_type, "00000");

if (image_type == RC_IMAGE_TYPE_ACHIEVEMENT && achievement->badge_url) {
snprintf(buffer, buffer_size, "%s", achievement->badge_url);
const size_t len = strlen(achievement->badge_url);
if (len >= buffer_size)
return RC_INSUFFICIENT_BUFFER;

memcpy(buffer, achievement->badge_url, len + 1);
return RC_OK;
}

if (image_type == RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED && achievement->badge_locked_url) {
snprintf(buffer, buffer_size, "%s", achievement->badge_locked_url);
const size_t len = strlen(achievement->badge_locked_url);
if (len >= buffer_size)
return RC_INSUFFICIENT_BUFFER;

memcpy(buffer, achievement->badge_locked_url, len + 1);
return RC_OK;
}

Expand Down Expand Up @@ -4870,6 +4948,7 @@ int rc_client_has_leaderboards(rc_client_t* client)
{
rc_client_subset_info_t* subset;
int result;
uint32_t i;

if (!client)
return 0;
Expand All @@ -4884,17 +4963,21 @@ int rc_client_has_leaderboards(rc_client_t* client)

rc_mutex_lock(&client->state.mutex);

subset = client->game->subsets;
result = 0;
for (; subset; subset = subset->next)
for (subset = client->game->subsets; subset; subset = subset->next)
{
if (!subset->active)
continue;

if (subset->public_.num_leaderboards > 0) {
result = 1;
break;
for (i = 0; i < subset->public_.num_leaderboards; ++i) {
if (!subset->leaderboards[i].hidden) {
result = 1;
break;
}
}

if (result)
break;
}

rc_mutex_unlock(&client->state.mutex);
Expand Down Expand Up @@ -5438,6 +5521,14 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
if (client->state.frames_processed != client->state.frames_at_last_ping) {
client->state.frames_at_last_ping = client->state.frames_processed;

memset(&api_params, 0, sizeof(api_params));
api_params.username = client->user.username;
api_params.api_token = client->user.token;
api_params.game_id = client->game->public_.id;
api_params.rich_presence = buffer;
api_params.game_hash = client->game->public_.hash;
api_params.hardcore = client->state.hardcore;

if (!client->callbacks.rich_presence_override ||
!client->callbacks.rich_presence_override(client, buffer, sizeof(buffer))) {
rc_mutex_lock(&client->state.mutex);
Expand All @@ -5448,13 +5539,12 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
rc_mutex_unlock(&client->state.mutex);
}

memset(&api_params, 0, sizeof(api_params));
api_params.username = client->user.username;
api_params.api_token = client->user.token;
api_params.game_id = client->game->public_.id;
api_params.rich_presence = buffer;
api_params.game_hash = client->game->public_.hash;
api_params.hardcore = client->state.hardcore;
/* there's a miniscule chance the game will be changed out while we're waiting for the lock.
* if that happens, discard this ping. the new game will have scheduled its own ping.
* don't reschedule this one. */
if (!client->game || client->game->public_.id != api_params.game_id) {
return;
}

result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
Expand Down
4 changes: 4 additions & 0 deletions deps/rcheevos/src/rc_client_external.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include "rc_api_runtime.h"

#ifdef RC_CLIENT_SUPPORTS_EXTERNAL

#define RC_CONVERSION_FILL(obj, obj_type, src_type) memset((uint8_t*)obj + sizeof(src_type), 0, sizeof(obj_type) - sizeof(src_type))

/* https://media.retroachievements.org/Badge/123456_lock.png is 58 with null terminator */
Expand Down Expand Up @@ -275,3 +277,5 @@ rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(con

return (rc_client_achievement_list_t*)new_list;
}

#endif /* RC_CLIENT_SUPPORTS_EXTERNAL */
6 changes: 5 additions & 1 deletion deps/rcheevos/src/rc_client_external.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ typedef void (RC_CCONV* rc_client_external_add_game_hash_func_t)(const char* has
struct rc_client_achievement_list_info_t;
typedef struct rc_client_achievement_list_info_t* (RC_CCONV *rc_client_external_create_achievement_list_func_t)(int category, int grouping);
typedef const rc_client_achievement_t* (RC_CCONV *rc_client_external_get_achievement_info_func_t)(uint32_t id);
typedef const rc_client_achievement_t* (RC_CCONV* rc_client_external_get_next_achievement_info_func_t)(uint32_t id, int grouping);

/* NOTE: rc_client_external_create_leaderboard_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
Expand Down Expand Up @@ -152,9 +153,12 @@ typedef struct rc_client_external_t
/* VERSION 6 */
rc_client_external_create_subset_list_func_t create_subset_list;

/* VERSION 7 */
rc_client_external_get_next_achievement_info_func_t get_next_achievement_info;

} rc_client_external_t;

#define RC_CLIENT_EXTERNAL_VERSION 5
#define RC_CLIENT_EXTERNAL_VERSION 7

void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);
Expand Down
7 changes: 7 additions & 0 deletions deps/rcheevos/src/rcheevos/condset.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,12 @@ static int rc_find_next_classification(const char* memaddr) {
break;

default:
rc_destroy_parse_state(&parse);
return classification;
}
} while (*memaddr++ == '_');

rc_destroy_parse_state(&parse);
return RC_CONDITION_CLASSIFICATION_OTHER;
}

Expand Down Expand Up @@ -261,6 +263,11 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {

next = &self->conditions;

/* prevent bleedthrough of incomplete conditions from other groups */
parse->addsource_oper = RC_OPERATOR_NONE;
parse->addsource_parent.type = RC_OPERAND_NONE;
parse->indirect_parent.type = RC_OPERAND_NONE;

/* each condition set has a functionally new recall accumulator */
parse->remember.type = RC_OPERAND_NONE;

Expand Down
2 changes: 1 addition & 1 deletion deps/rcheevos/src/rcheevos/runtime_progress.c
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, void* unused_L)

result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK)
return result;
return 0;

return progress.offset;
}
Expand Down
Loading