@@ -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+
43104380int 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 ) {
0 commit comments