Skip to content

Commit fdce929

Browse files
authored
update to rcheevos 12.3 (#18719)
1 parent 24027ab commit fdce929

9 files changed

Lines changed: 154 additions & 21 deletions

File tree

cheevos/cheevos.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,15 @@ static void rcheevos_award_achievement(const rc_client_achievement_t* cheevo)
335335
snprintf(subtitle, sizeof(subtitle), "%s (%lu)", cheevo->title, (unsigned long)cheevo->points);
336336

337337
gfx_widgets_push_achievement(title, subtitle, cheevo->badge_name);
338+
339+
/* if all badges haven't been loaded, preload the next one assuming it will be the next needed */
340+
if (!rcheevos_locals.badges_loaded)
341+
{
342+
const rc_client_achievement_t* next_locked_achievement =
343+
rc_client_get_next_achievement_info(rcheevos_locals.client, cheevo, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
344+
if (next_locked_achievement)
345+
rcheevos_client_download_badge_from_url(next_locked_achievement->badge_url, next_locked_achievement->badge_name);
346+
}
338347
}
339348
else
340349
#endif
@@ -1436,12 +1445,18 @@ static void rcheevos_finalize_game_load(rc_client_t* client)
14361445
#endif
14371446
if (want_badges) /* prefetch the game badge */
14381447
{
1448+
const rc_client_achievement_t* first_locked_achievement = NULL;
14391449
const rc_client_game_t* game = rc_client_get_game_info(client);
14401450
char badge[32];
14411451

14421452
badge[0] = 'i';
14431453
strlcpy(&badge[1], game->badge_name, sizeof(badge) - 1);
14441454
rcheevos_client_download_badge_from_url(game->badge_url, badge);
1455+
1456+
/* predownload the first badge the player hasn't earned, assuming it will be the next to unlock */
1457+
first_locked_achievement = rc_client_get_next_achievement_info(client, NULL, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
1458+
if (first_locked_achievement)
1459+
rcheevos_client_download_badge_from_url(first_locked_achievement->badge_url, first_locked_achievement->badge_name);
14451460
}
14461461

14471462
if (!rc_client_is_processing_required(client))

cheevos/cheevos_client.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,15 +327,15 @@ void rcheevos_client_server_call(const rc_api_request_t* request,
327327
rcheevos_log_post_url(request->url, request->post_data);
328328

329329
#ifdef CHEEVOS_JSON_OVERRIDE
330-
if (strstr(request->post_data, "r=patch"))
330+
if (strstr(request->post_data, "r=patch") || strstr(request->post_data, "r=achievementsets"))
331331
{
332332
rcheevos_client_http_load_response(request, callback, callback_data);
333333
return;
334334
}
335335
#endif
336336

337337
#ifdef CHEEVOS_SAVE_JSON
338-
if (strstr(request->post_data, "r=patch"))
338+
if (strstr(request->post_data, "r=patch") || strstr(request->post_data, "r=achievementsets"))
339339
{
340340
task_push_http_post_transfer_with_user_agent(request->url,
341341
request->post_data, true, "POST", rcheevos_locals->user_agent_core,

deps/rcheevos/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# v12.3.0
2+
* add rc_client_get_next_achievement_info
3+
* rc_client image functions will now return RC_INSUFFICENT_BUFFER instead of truncating if buffer is not large enough
4+
* fix race condition where rich presence from previous game may get associated to current game
5+
* fix rc_client_has_leaderboards returning true if the game only has hidden leaderboards
6+
* fix memory leak parsing large achievements
7+
* fix incomplete rich presence display condition affecting later display conditions
8+
19
# v12.2.1
210
* fix parsing of leaderboards with comparisons in legacy-formatted values
311
* fix validation warning on long AddSource chains

deps/rcheevos/include/rc_client.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,11 @@ typedef struct rc_client_achievement_t {
523523
*/
524524
RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id);
525525

526+
/**
527+
* Gets the next achievement after a provided achievement that fits in the specified bucket. Returns NULL if none found.
528+
*/
529+
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);
530+
526531
/**
527532
* Gets the URL for the achievement image.
528533
* Returns RC_OK on success.

deps/rcheevos/src/rc_client.c

Lines changed: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,13 @@ static int rc_client_get_image_url(char buffer[], size_t buffer_size, int image_
637637
image_request.image_name = image_name;
638638
result = rc_api_init_fetch_image_request_hosted(&request, &image_request, NULL);
639639
if (result == RC_OK)
640-
snprintf(buffer, buffer_size, "%s", request.url);
640+
{
641+
const size_t len = strlen(request.url);
642+
if (len >= buffer_size)
643+
result = RC_INSUFFICIENT_BUFFER;
644+
else
645+
memcpy(buffer, request.url, len + 1);
646+
}
641647

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

900906
if (user->avatar_url) {
901-
snprintf(buffer, buffer_size, "%s", user->avatar_url);
907+
const size_t len = strlen(user->avatar_url);
908+
if (len >= buffer_size)
909+
return RC_INSUFFICIENT_BUFFER;
910+
911+
memcpy(buffer, user->avatar_url, len + 1);
902912
return RC_OK;
903913
}
904914

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

35163526
if (game->badge_url) {
3517-
snprintf(buffer, buffer_size, "%s", game->badge_url);
3527+
const size_t len = strlen(game->badge_url);
3528+
if (len >= buffer_size)
3529+
return RC_INSUFFICIENT_BUFFER;
3530+
3531+
memcpy(buffer, game->badge_url, len + 1);
35183532
return RC_OK;
35193533
}
35203534

@@ -4307,6 +4321,62 @@ const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* clien
43074321
return NULL;
43084322
}
43094323

4324+
const rc_client_achievement_t* rc_client_get_next_achievement_info(rc_client_t* client,
4325+
const rc_client_achievement_t* achievement, int bucket)
4326+
{
4327+
const rc_client_achievement_info_t* after = (const rc_client_achievement_info_t*)achievement;
4328+
rc_client_achievement_info_t* achievement_info;
4329+
time_t recent_unlock_time;
4330+
rc_client_subset_info_t* subset;
4331+
4332+
if (!client)
4333+
return NULL;
4334+
4335+
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
4336+
if (client->state.external_client && client->state.external_client->get_next_achievement_info)
4337+
return client->state.external_client->get_next_achievement_info(achievement ? achievement->id : 0, bucket);
4338+
#endif
4339+
4340+
if (!client->game)
4341+
return NULL;
4342+
4343+
recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS;
4344+
for (subset = client->game->subsets; subset; subset = subset->next) {
4345+
if (subset->active && subset->public_.num_achievements > 0) {
4346+
const rc_client_achievement_info_t* start = subset->achievements;
4347+
const rc_client_achievement_info_t* stop = start + subset->public_.num_achievements;
4348+
if (after == NULL || (after >= start && after <= stop)) {
4349+
/* found a subset containing the provided achievement. look for the next
4350+
* achievement matching the requested bucket */
4351+
uint32_t index = after ? (uint32_t)(after - start) + 1 : 0;
4352+
do {
4353+
if (index >= subset->public_.num_achievements) {
4354+
/* done with this subset. find the next active subset with achievements */
4355+
do {
4356+
subset = subset->next;
4357+
if (!subset)
4358+
return NULL;
4359+
} while (!subset->active || subset->public_.num_achievements == 0);
4360+
4361+
index = 0;
4362+
}
4363+
4364+
/* found an achievement. check to see if it matches the requested bucket. */
4365+
achievement_info = &subset->achievements[index];
4366+
rc_client_update_achievement_display_information(client, achievement_info, recent_unlock_time);
4367+
if (achievement_info->public_.bucket == bucket)
4368+
return &achievement_info->public_;
4369+
4370+
++index;
4371+
} while (1);
4372+
}
4373+
}
4374+
}
4375+
4376+
return NULL;
4377+
}
4378+
4379+
43104380
int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size)
43114381
{
43124382
const int image_type = (state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) ?
@@ -4316,12 +4386,20 @@ int rc_client_achievement_get_image_url(const rc_client_achievement_t* achieveme
43164386
return rc_client_get_image_url(buffer, buffer_size, image_type, "00000");
43174387

43184388
if (image_type == RC_IMAGE_TYPE_ACHIEVEMENT && achievement->badge_url) {
4319-
snprintf(buffer, buffer_size, "%s", achievement->badge_url);
4389+
const size_t len = strlen(achievement->badge_url);
4390+
if (len >= buffer_size)
4391+
return RC_INSUFFICIENT_BUFFER;
4392+
4393+
memcpy(buffer, achievement->badge_url, len + 1);
43204394
return RC_OK;
43214395
}
43224396

43234397
if (image_type == RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED && achievement->badge_locked_url) {
4324-
snprintf(buffer, buffer_size, "%s", achievement->badge_locked_url);
4398+
const size_t len = strlen(achievement->badge_locked_url);
4399+
if (len >= buffer_size)
4400+
return RC_INSUFFICIENT_BUFFER;
4401+
4402+
memcpy(buffer, achievement->badge_locked_url, len + 1);
43254403
return RC_OK;
43264404
}
43274405

@@ -4870,6 +4948,7 @@ int rc_client_has_leaderboards(rc_client_t* client)
48704948
{
48714949
rc_client_subset_info_t* subset;
48724950
int result;
4951+
uint32_t i;
48734952

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

48854964
rc_mutex_lock(&client->state.mutex);
48864965

4887-
subset = client->game->subsets;
48884966
result = 0;
4889-
for (; subset; subset = subset->next)
4967+
for (subset = client->game->subsets; subset; subset = subset->next)
48904968
{
48914969
if (!subset->active)
48924970
continue;
48934971

4894-
if (subset->public_.num_leaderboards > 0) {
4895-
result = 1;
4896-
break;
4972+
for (i = 0; i < subset->public_.num_leaderboards; ++i) {
4973+
if (!subset->leaderboards[i].hidden) {
4974+
result = 1;
4975+
break;
4976+
}
48974977
}
4978+
4979+
if (result)
4980+
break;
48984981
}
48994982

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

5524+
memset(&api_params, 0, sizeof(api_params));
5525+
api_params.username = client->user.username;
5526+
api_params.api_token = client->user.token;
5527+
api_params.game_id = client->game->public_.id;
5528+
api_params.rich_presence = buffer;
5529+
api_params.game_hash = client->game->public_.hash;
5530+
api_params.hardcore = client->state.hardcore;
5531+
54415532
if (!client->callbacks.rich_presence_override ||
54425533
!client->callbacks.rich_presence_override(client, buffer, sizeof(buffer))) {
54435534
rc_mutex_lock(&client->state.mutex);
@@ -5448,13 +5539,12 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
54485539
rc_mutex_unlock(&client->state.mutex);
54495540
}
54505541

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

54595549
result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
54605550
if (result != RC_OK) {

deps/rcheevos/src/rc_client_external.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include "rc_api_runtime.h"
77

8+
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
9+
810
#define RC_CONVERSION_FILL(obj, obj_type, src_type) memset((uint8_t*)obj + sizeof(src_type), 0, sizeof(obj_type) - sizeof(src_type))
911

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

276278
return (rc_client_achievement_list_t*)new_list;
277279
}
280+
281+
#endif /* RC_CLIENT_SUPPORTS_EXTERNAL */

deps/rcheevos/src/rc_client_external.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef void (RC_CCONV* rc_client_external_add_game_hash_func_t)(const char* has
4646
struct rc_client_achievement_list_info_t;
4747
typedef struct rc_client_achievement_list_info_t* (RC_CCONV *rc_client_external_create_achievement_list_func_t)(int category, int grouping);
4848
typedef const rc_client_achievement_t* (RC_CCONV *rc_client_external_get_achievement_info_func_t)(uint32_t id);
49+
typedef const rc_client_achievement_t* (RC_CCONV* rc_client_external_get_next_achievement_info_func_t)(uint32_t id, int grouping);
4950

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

156+
/* VERSION 7 */
157+
rc_client_external_get_next_achievement_info_func_t get_next_achievement_info;
158+
155159
} rc_client_external_t;
156160

157-
#define RC_CLIENT_EXTERNAL_VERSION 5
161+
#define RC_CLIENT_EXTERNAL_VERSION 7
158162

159163
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
160164
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);

deps/rcheevos/src/rcheevos/condset.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,12 @@ static int rc_find_next_classification(const char* memaddr) {
134134
break;
135135

136136
default:
137+
rc_destroy_parse_state(&parse);
137138
return classification;
138139
}
139140
} while (*memaddr++ == '_');
140141

142+
rc_destroy_parse_state(&parse);
141143
return RC_CONDITION_CLASSIFICATION_OTHER;
142144
}
143145

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

262264
next = &self->conditions;
263265

266+
/* prevent bleedthrough of incomplete conditions from other groups */
267+
parse->addsource_oper = RC_OPERATOR_NONE;
268+
parse->addsource_parent.type = RC_OPERAND_NONE;
269+
parse->indirect_parent.type = RC_OPERAND_NONE;
270+
264271
/* each condition set has a functionally new recall accumulator */
265272
parse->remember.type = RC_OPERAND_NONE;
266273

deps/rcheevos/src/rcheevos/runtime_progress.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, void* unused_L)
913913

914914
result = rc_runtime_progress_serialize_internal(&progress);
915915
if (result != RC_OK)
916-
return result;
916+
return 0;
917917

918918
return progress.offset;
919919
}

0 commit comments

Comments
 (0)