Skip to content

Commit 2aad8ad

Browse files
authored
update to rcheevos 12.1 (#18298)
1 parent 8ec8b7c commit 2aad8ad

16 files changed

Lines changed: 228 additions & 79 deletions

deps/rcheevos/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# v12.1.0
2+
* add rc_client_get_user_subset_summary
3+
* add validation warning for using MeasuredIf without Measured
4+
* add validation warning for using ResetIf without hit targets
5+
* add rapi function for update_rich_presence
6+
* add gap to RC_CONSOLE_WII memory map to make it easier to convert pointers
7+
* improve range validation logic
8+
* fix error Remembering float value
9+
* fix MeasuredIf evaluation in rich presence
10+
* fix parsing of code notes with addresses above 0x7FFFFFFF
11+
* fix double evaluation of rich presence parameters
12+
* fix validation of SubSource chain
13+
* fix error if rc_client_allow_background_memory_reads called before calling rc_client_begin_load_raintegration
14+
* fix memory corruption when mixing legacy and new-format macros in rich presence
15+
* fix invalid pointer reference when iterator gets cloned
16+
117
# v12.0.0
218
* rc_client changes
319
* add RC_CLIENT_EVENT_SUBSET_COMPLETED event

deps/rcheevos/include/rc_client.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ typedef struct rc_client_user_game_summary_t {
211211

212212
uint32_t points_core;
213213
uint32_t points_unlocked;
214+
215+
/* minimum version: 12.1 */
216+
time_t beaten_time;
217+
time_t completed_time;
214218
} rc_client_user_game_summary_t;
215219

216220
/**
@@ -358,6 +362,8 @@ typedef struct rc_client_subset_t {
358362

359363
RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
360364

365+
RC_EXPORT void RC_CCONV rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary);
366+
361367
/*****************************************************************************\
362368
| Fetch Game Hashes |
363369
\*****************************************************************************/
@@ -582,7 +588,7 @@ enum {
582588
RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
583589

584590
/**
585-
* Destroys a list allocated by rc_client_get_leaderboard_list.
591+
* Destroys a list allocated by rc_client_create_leaderboard_list.
586592
*/
587593
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
588594

deps/rcheevos/src/rapi/rc_api_common.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,17 +273,17 @@ static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_
273273
iterator.json = server_response->body;
274274
iterator.end = server_response->body + server_response->body_length;
275275

276-
/* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */
276+
/* assume the title contains the most appropriate message to display to the user */
277277
if (rc_json_find_substring(&iterator, "<title>")) {
278278
const char* title_start = iterator.json + 7;
279-
if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "</title>")) {
279+
if (rc_json_find_substring(&iterator, "</title>")) {
280280
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start);
281281
response->succeeded = 0;
282282
return RC_INVALID_JSON;
283283
}
284284
}
285285

286-
/* title not found, or did not start with an error code, return the first line of the response */
286+
/* title not found, return the first line of the response (up to 200 characters) */
287287
iterator.json = server_response->body;
288288

289289
while (iterator.json < iterator.end && *iterator.json != '\n' &&

deps/rcheevos/src/rc_client.c

Lines changed: 114 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -905,11 +905,22 @@ int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], si
905905
return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_USER, user->display_name);
906906
}
907907

908-
static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t* subset,
909-
rc_client_user_game_summary_t* summary, const uint8_t unlock_bit)
908+
static void rc_client_subset_get_user_game_summary(const rc_client_t* client,
909+
const rc_client_subset_info_t* subset, rc_client_user_game_summary_t* summary)
910910
{
911911
rc_client_achievement_info_t* achievement = subset->achievements;
912912
rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements;
913+
time_t last_unlock_time = 0;
914+
time_t last_progression_time = 0;
915+
time_t first_win_time = 0;
916+
int num_progression_achievements = 0;
917+
int num_win_achievements = 0;
918+
int num_unlocked_progression_achievements = 0;
919+
const uint8_t unlock_bit = (client->state.hardcore) ?
920+
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
921+
922+
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
923+
913924
for (; achievement < stop; ++achievement) {
914925
switch (achievement->public_.category) {
915926
case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE:
@@ -919,10 +930,28 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
919930
if (achievement->public_.unlocked & unlock_bit) {
920931
++summary->num_unlocked_achievements;
921932
summary->points_unlocked += achievement->public_.points;
933+
934+
if (achievement->public_.unlock_time > last_unlock_time)
935+
last_unlock_time = achievement->public_.unlock_time;
936+
937+
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION) {
938+
++num_unlocked_progression_achievements;
939+
if (achievement->public_.unlock_time > last_progression_time)
940+
last_progression_time = achievement->public_.unlock_time;
941+
}
942+
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN) {
943+
if (first_win_time == 0 || achievement->public_.unlock_time < first_win_time)
944+
first_win_time = achievement->public_.unlock_time;
945+
}
922946
}
923-
if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) {
947+
948+
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION)
949+
++num_progression_achievements;
950+
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN)
951+
++num_win_achievements;
952+
953+
if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED)
924954
++summary->num_unsupported_achievements;
925-
}
926955

927956
break;
928957

@@ -934,13 +963,18 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
934963
continue;
935964
}
936965
}
966+
967+
rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
968+
969+
if (summary->num_unlocked_achievements == summary->num_core_achievements)
970+
summary->completed_time = last_unlock_time;
971+
972+
if ((first_win_time || num_win_achievements == 0) && num_unlocked_progression_achievements == num_progression_achievements)
973+
summary->beaten_time = (first_win_time > last_progression_time) ? first_win_time : last_progression_time;
937974
}
938975

939976
void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary)
940977
{
941-
const uint8_t unlock_bit = (client->state.hardcore) ?
942-
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
943-
944978
if (!summary)
945979
return;
946980

@@ -949,20 +983,47 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
949983
return;
950984

951985
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
952-
if (client->state.external_client && client->state.external_client->get_user_game_summary) {
953-
client->state.external_client->get_user_game_summary(summary);
954-
return;
986+
if (client->state.external_client) {
987+
if (client->state.external_client->get_user_game_summary_v5) {
988+
client->state.external_client->get_user_game_summary_v5(summary);
989+
return;
990+
}
991+
if (client->state.external_client->get_user_game_summary) {
992+
client->state.external_client->get_user_game_summary(summary);
993+
return;
994+
}
955995
}
956996
#endif
957997

958-
if (!rc_client_is_game_loaded(client))
998+
if (rc_client_is_game_loaded(client))
999+
rc_client_subset_get_user_game_summary(client, client->game->subsets, summary);
1000+
}
1001+
1002+
void rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary)
1003+
{
1004+
if (!summary)
9591005
return;
9601006

961-
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
1007+
memset(summary, 0, sizeof(*summary));
1008+
if (!client || !subset_id)
1009+
return;
9621010

963-
rc_client_subset_get_user_game_summary(client->game->subsets, summary, unlock_bit);
1011+
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
1012+
if (client->state.external_client && client->state.external_client->get_user_subset_summary) {
1013+
client->state.external_client->get_user_subset_summary(subset_id, summary);
1014+
return;
1015+
}
1016+
#endif
9641017

965-
rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
1018+
if (rc_client_is_game_loaded(client)) {
1019+
const rc_client_subset_info_t* subset = client->game->subsets;
1020+
for (; subset; subset = subset->next) {
1021+
if (subset->public_.id == subset_id) {
1022+
rc_client_subset_get_user_game_summary(client, subset, summary);
1023+
break;
1024+
}
1025+
}
1026+
}
9661027
}
9671028

9681029
typedef struct rc_client_fetch_all_user_progress_callback_data_t {
@@ -1376,29 +1437,34 @@ static uint32_t rc_client_subset_toggle_hardcore_achievements(rc_client_subset_i
13761437
break;
13771438
}
13781439
}
1379-
else if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
1380-
achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) {
1440+
else {
1441+
achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ?
1442+
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
13811443

1382-
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
1383-
if (client->state.encore_mode) {
1384-
++active_count;
1385-
continue;
1386-
}
1444+
if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
1445+
achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) {
1446+
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
1447+
if (client->state.encore_mode) {
1448+
++active_count;
1449+
continue;
1450+
}
13871451

1388-
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
1389-
achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ?
1390-
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
1452+
/* switch to inactive */
1453+
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
1454+
1455+
if (achievement->trigger && rc_trigger_state_active(achievement->trigger->state)) {
1456+
/* hide any active challenge indicators */
1457+
if (achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) {
1458+
rc_client_event_t client_event;
1459+
memset(&client_event, 0, sizeof(client_event));
1460+
client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE;
1461+
client_event.achievement = &achievement->public_;
1462+
client->callbacks.event_handler(&client_event, client);
1463+
}
13911464

1392-
if (achievement->trigger && achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) {
1393-
rc_client_event_t client_event;
1394-
memset(&client_event, 0, sizeof(client_event));
1395-
client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE;
1396-
client_event.achievement = &achievement->public_;
1397-
client->callbacks.event_handler(&client_event, client);
1465+
achievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
1466+
}
13981467
}
1399-
1400-
if (achievement->trigger && rc_trigger_state_active(achievement->trigger->state))
1401-
achievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
14021468
}
14031469
}
14041470

@@ -1728,7 +1794,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
17281794
if (load_state->hash->hash[0] != '[') {
17291795
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) {
17301796
/* schedule the periodic ping */
1731-
rc_client_scheduled_callback_data_t* callback_data = (rc_client_scheduled_callback_data_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t));
1797+
rc_client_scheduled_callback_data_t* callback_data = (rc_client_scheduled_callback_data_t*)
1798+
rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t));
1799+
17321800
memset(callback_data, 0, sizeof(*callback_data));
17331801
callback_data->callback = rc_client_ping;
17341802
callback_data->related_id = load_state->game->public_.id;
@@ -1768,7 +1836,9 @@ static void rc_client_dispatch_activate_game(struct rc_client_scheduled_callback
17681836

17691837
static void rc_client_queue_activate_game(rc_client_load_state_t* load_state)
17701838
{
1771-
rc_client_scheduled_callback_data_t* scheduled_callback_data = (rc_client_scheduled_callback_data_t*)calloc(1, sizeof(rc_client_scheduled_callback_data_t));
1839+
rc_client_scheduled_callback_data_t* scheduled_callback_data =
1840+
(rc_client_scheduled_callback_data_t*)calloc(1, sizeof(rc_client_scheduled_callback_data_t));
1841+
17721842
if (!scheduled_callback_data) {
17731843
rc_client_load_error(load_state, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
17741844
return;
@@ -1805,7 +1875,7 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
18051875
outstanding_requests = rc_client_end_load_state(load_state);
18061876

18071877
if (error_message) {
1808-
rc_client_load_error((rc_client_load_state_t*)callback_data, result, error_message);
1878+
rc_client_load_error(load_state, result, error_message);
18091879
}
18101880
else if (outstanding_requests < 0) {
18111881
/* previous load state was aborted, load_state was free'd */
@@ -1818,7 +1888,7 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
18181888
(rc_api_start_session_response_t*)malloc(sizeof(rc_api_start_session_response_t));
18191889

18201890
if (!load_state->start_session_response) {
1821-
rc_client_load_error((rc_client_load_state_t*)callback_data, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
1891+
rc_client_load_error(load_state, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
18221892
}
18231893
else {
18241894
/* safer to parse the response again than to try to copy it */
@@ -2796,14 +2866,14 @@ rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
27962866

27972867
static void rc_client_log_hash_message_verbose(const char* message, const rc_hash_iterator_t* iterator)
27982868
{
2799-
rc_client_load_state_t* load_state = (rc_client_load_state_t*)iterator->userdata;
2869+
const rc_client_load_state_t* load_state = (const rc_client_load_state_t*)iterator->userdata;
28002870
if (load_state->client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO)
28012871
rc_client_log_message(load_state->client, message);
28022872
}
28032873

28042874
static void rc_client_log_hash_message_error(const char* message, const rc_hash_iterator_t* iterator)
28052875
{
2806-
rc_client_load_state_t* load_state = (rc_client_load_state_t*)iterator->userdata;
2876+
const rc_client_load_state_t* load_state = (const rc_client_load_state_t*)iterator->userdata;
28072877
if (load_state->client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR)
28082878
rc_client_log_message(load_state->client, message);
28092879
}
@@ -3113,7 +3183,8 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
31133183
rc_api_request_t request;
31143184
int result;
31153185

3116-
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
3186+
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID || /* previously looked up */
3187+
game_hash->hash[0] == '[') { /* internal use - don't try to look up */
31173188
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
31183189
return NULL;
31193190
}
@@ -4367,15 +4438,15 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
43674438
};
43684439

43694440
if (!client)
4370-
return (rc_client_leaderboard_list_t*)calloc(1, sizeof(rc_client_leaderboard_list_t));
4441+
return (rc_client_leaderboard_list_t*)calloc(1, list_size);
43714442

43724443
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
43734444
if (client->state.external_client && client->state.external_client->create_leaderboard_list)
43744445
return (rc_client_leaderboard_list_t*)client->state.external_client->create_leaderboard_list(grouping);
43754446
#endif
43764447

43774448
if (!client->game)
4378-
return (rc_client_leaderboard_list_t*)calloc(1, sizeof(rc_client_leaderboard_list_t));
4449+
return (rc_client_leaderboard_list_t*)calloc(1, list_size);
43794450

43804451
memset(&bucket_counts, 0, sizeof(bucket_counts));
43814452

@@ -5809,7 +5880,7 @@ void rc_client_do_frame(rc_client_t* client)
58095880

58105881
richpresence = client->game->runtime.richpresence;
58115882
if (richpresence && richpresence->richpresence)
5812-
rc_update_richpresence(richpresence->richpresence, client->state.legacy_peek, client, NULL);
5883+
rc_update_richpresence_internal(richpresence->richpresence, client->state.legacy_peek, client);
58135884

58145885
rc_mutex_unlock(&client->state.mutex);
58155886

deps/rcheevos/src/rc_client_external.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_subse
3535
uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
3636
typedef const rc_client_game_t* (RC_CCONV *rc_client_external_get_game_info_func_t)(void);
3737
typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_func_t)(uint32_t subset_id);
38+
typedef void (RC_CCONV* rc_client_external_get_user_subset_summary_func_t)(uint32_t subset_id, rc_client_user_game_summary_t* summary);
3839
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
3940
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
4041
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
@@ -139,9 +140,13 @@ typedef struct rc_client_external_t
139140
/* VERSION 4 */
140141
rc_client_external_set_int_func_t set_allow_background_memory_reads;
141142

143+
/* VERSION 5 */
144+
rc_client_external_get_user_game_summary_func_t get_user_game_summary_v5;
145+
rc_client_external_get_user_subset_summary_func_t get_user_subset_summary;
146+
142147
} rc_client_external_t;
143148

144-
#define RC_CLIENT_EXTERNAL_VERSION 4
149+
#define RC_CLIENT_EXTERNAL_VERSION 5
145150

146151
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
147152
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);

0 commit comments

Comments
 (0)