diff --git a/dep/rcheevos/CMakeLists.txt b/dep/rcheevos/CMakeLists.txt index 4f050e78f..df1d72b39 100644 --- a/dep/rcheevos/CMakeLists.txt +++ b/dep/rcheevos/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(rcheevos src/rcheevos/lboard.c src/rcheevos/memref.c src/rcheevos/operand.c + src/rcheevos/rc_client.c src/rcheevos/rc_compat.h src/rcheevos/rc_internal.h src/rcheevos/richpresence.c diff --git a/dep/rcheevos/include/rc_api_user.h b/dep/rcheevos/include/rc_api_user.h index 8f96044c8..b04b6271b 100644 --- a/dep/rcheevos/include/rc_api_user.h +++ b/dep/rcheevos/include/rc_api_user.h @@ -3,6 +3,8 @@ #include "rc_api_request.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -65,10 +67,34 @@ typedef struct rc_api_start_session_request_t { } rc_api_start_session_request_t; +/** + * Response data for an achievement unlock. + */ +typedef struct rc_api_unlock_entry_t { + /* The unique identifier of the unlocked achievement */ + unsigned achievement_id; + /* When the achievement was unlocked */ + time_t when; +} +rc_api_unlock_entry_t; + /** * Response data for a start session request. */ typedef struct rc_api_start_session_response_t { + /* An array of hardcore user unlocks */ + rc_api_unlock_entry_t* hardcore_unlocks; + /* An array of user unlocks */ + rc_api_unlock_entry_t* unlocks; + + /* The number of items in the hardcore_unlocks array */ + unsigned num_hardcore_unlocks; + /* The number of items in the unlocks array */ + unsigned num_unlocks; + + /* The server timestamp when the response was generated */ + time_t server_now; + /* Common server-provided response information */ rc_api_response_t response; } diff --git a/dep/rcheevos/include/rc_client.h b/dep/rcheevos/include/rc_client.h index 40e9a977a..2e828bb1d 100644 --- a/dep/rcheevos/include/rc_client.h +++ b/dep/rcheevos/include/rc_client.h @@ -46,11 +46,6 @@ typedef void (*rc_client_callback_t)(int result, const char* error_message, rc_c */ typedef void (*rc_client_message_callback_t)(const char* message, const rc_client_t* client); -/** - * Marks an async process as aborted. The associated callback will not be called. - */ -void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); - /*****************************************************************************\ | Runtime | \*****************************************************************************/ @@ -130,6 +125,19 @@ void* rc_client_get_userdata(const rc_client_t* client); */ void rc_client_set_host(const rc_client_t* client, const char* hostname); +typedef uint64_t rc_clock_t; +typedef rc_clock_t (*rc_get_time_millisecs_func_t)(const rc_client_t* client); + +/** + * Specifies a function that returns a value that increases once per millisecond. + */ +void rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler); + +/** + * Marks an async process as aborted. The associated callback will not be called. + */ +void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); + /*****************************************************************************\ | Logging | \*****************************************************************************/ @@ -502,7 +510,9 @@ enum { RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */ RC_CLIENT_EVENT_RESET = 13, /* emulated system should be reset (as the result of enabling hardcore) */ RC_CLIENT_EVENT_GAME_COMPLETED = 14, /* all achievements for the game have been earned */ - RC_CLIENT_EVENT_SERVER_ERROR = 15 /* an API response returned a [server_error] and will not be retried */ + RC_CLIENT_EVENT_SERVER_ERROR = 15, /* an API response returned a [server_error] and will not be retried */ + RC_CLIENT_EVENT_DISCONNECTED = 16, /* an unlock request could not be completed and is pending */ + RC_CLIENT_EVENT_RECONNECTED = 17 /* all pending unlocks have been completed */ }; typedef struct rc_client_server_error_t diff --git a/dep/rcheevos/include/rc_runtime_types.h b/dep/rcheevos/include/rc_runtime_types.h index dc3ab60c9..06990cbc6 100644 --- a/dep/rcheevos/include/rc_runtime_types.h +++ b/dep/rcheevos/include/rc_runtime_types.h @@ -57,6 +57,7 @@ enum { RC_MEMSIZE_FLOAT, RC_MEMSIZE_MBF32, RC_MEMSIZE_MBF32_LE, + RC_MEMSIZE_FLOAT_BE, RC_MEMSIZE_VARIABLE }; diff --git a/dep/rcheevos/rcheevos.vcxproj b/dep/rcheevos/rcheevos.vcxproj index 870d7daf2..198c28611 100644 --- a/dep/rcheevos/rcheevos.vcxproj +++ b/dep/rcheevos/rcheevos.vcxproj @@ -16,6 +16,7 @@ + @@ -33,6 +34,7 @@ + @@ -40,6 +42,7 @@ + diff --git a/dep/rcheevos/rcheevos.vcxproj.filters b/dep/rcheevos/rcheevos.vcxproj.filters index 66e080824..6e65b5604 100644 --- a/dep/rcheevos/rcheevos.vcxproj.filters +++ b/dep/rcheevos/rcheevos.vcxproj.filters @@ -87,6 +87,9 @@ rapi + + rcheevos + @@ -137,5 +140,11 @@ rapi + + rcheevos + + + include + \ No newline at end of file diff --git a/dep/rcheevos/src/rapi/rc_api_common.c b/dep/rcheevos/src/rapi/rc_api_common.c index 7fdc7beb1..1abcebe40 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.c +++ b/dep/rcheevos/src/rapi/rc_api_common.c @@ -419,6 +419,18 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r } int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { +#ifndef NDEBUG + if (strcmp(field->name, field_name) != 0) + return 0; +#endif + + if (!rc_json_get_optional_array(num_entries, array_field, response, field, field_name)) + return rc_json_missing_field(response, field); + + return 1; +} + +int rc_json_get_optional_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; @@ -428,7 +440,7 @@ int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_fie if (!field->value_start || *field->value_start != '[') { *num_entries = 0; - return rc_json_missing_field(response, field); + return 0; } memcpy(array_field, field, sizeof(*array_field)); diff --git a/dep/rcheevos/src/rapi/rc_api_common.h b/dep/rcheevos/src/rapi/rc_api_common.h index 99b17c6d6..c8c534898 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.h +++ b/dep/rcheevos/src/rapi/rc_api_common.h @@ -53,6 +53,7 @@ void rc_json_get_optional_string(const char** out, rc_api_response_t* response, void rc_json_get_optional_num(int* out, const rc_json_field_t* field, const char* field_name, int default_value); void rc_json_get_optional_unum(unsigned* out, const rc_json_field_t* field, const char* field_name, unsigned default_value); void rc_json_get_optional_bool(int* out, const rc_json_field_t* field, const char* field_name, int default_value); +int rc_json_get_optional_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_num(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_unum(unsigned* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); diff --git a/dep/rcheevos/src/rapi/rc_api_user.c b/dep/rcheevos/src/rapi/rc_api_user.c index 4a13b8df2..d1d76b20b 100644 --- a/dep/rcheevos/src/rapi/rc_api_user.c +++ b/dep/rcheevos/src/rapi/rc_api_user.c @@ -91,16 +91,8 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st return RC_INVALID_STATE; rc_url_builder_init(&builder, &request->buffer, 48); - if (rc_api_url_build_dorequest(&builder, "postactivity", api_params->username, api_params->api_token)) { - /* activity type enum (only 3 is used ) - * 1 = earned achievement - handled by awardachievement - * 2 = logged in - handled by login - * 3 = started playing - * 4 = uploaded achievement - handled by uploadachievement - * 5 = modified achievement - handled by uploadachievement - */ - rc_url_builder_append_unum_param(&builder, "a", 3); - rc_url_builder_append_unum_param(&builder, "m", api_params->game_id); + if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) { + rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING); request->post_data = rc_url_builder_finalize(&builder); request->content_type = RC_CONTENT_TYPE_URLENCODED; @@ -120,15 +112,78 @@ int rc_api_process_start_session_response(rc_api_start_session_response_t* respo } int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) { + rc_api_unlock_entry_t* unlock; + rc_json_field_t array_field; + rc_json_iterator_t iterator; + unsigned timet; + int result; + rc_json_field_t fields[] = { RC_JSON_NEW_FIELD("Success"), - RC_JSON_NEW_FIELD("Error") + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Unlocks"), + RC_JSON_NEW_FIELD("HardcoreUnlocks"), + RC_JSON_NEW_FIELD("ServerNow") + }; + + rc_json_field_t unlock_entry_fields[] = { + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("When") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + if (result != RC_OK || !response->response.succeeded) + return result; + + if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &response->response, &fields[2], "Unlocks") && response->num_unlocks) { + response->unlocks = (rc_api_unlock_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t)); + if (!response->unlocks) + return RC_OUT_OF_MEMORY; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + + unlock = response->unlocks; + while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) { + if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When")) + return RC_MISSING_VALUE; + unlock->when = (time_t)timet; + + ++unlock; + } + } + + if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &response->response, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) { + response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t)); + if (!response->hardcore_unlocks) + return RC_OUT_OF_MEMORY; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + + unlock = response->hardcore_unlocks; + while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) { + if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When")) + return RC_MISSING_VALUE; + unlock->when = (time_t)timet; + + ++unlock; + } + } + + rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0); + response->server_now = (time_t)timet; + + return RC_OK; } void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) { diff --git a/dep/rcheevos/src/rcheevos/memref.c b/dep/rcheevos/src/rcheevos/memref.c index fff2a6bca..ae0f99a8e 100644 --- a/dep/rcheevos/src/rcheevos/memref.c +++ b/dep/rcheevos/src/rcheevos/memref.c @@ -94,6 +94,7 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) { ++aux; switch (*aux++) { case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break; + case 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; break; case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break; case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break; @@ -185,6 +186,18 @@ static void rc_transform_memref_float(rc_typed_value_t* value) { value->type = RC_VALUE_TYPE_FLOAT; } +static void rc_transform_memref_float_be(rc_typed_value_t* value) { + /* decodes an IEEE 754 float in big endian format */ + const unsigned mantissa = ((value->value.u32 & 0xFF000000) >> 24) | + ((value->value.u32 & 0x00FF0000) >> 8) | + ((value->value.u32 & 0x00007F00) << 8); + const int exponent = (int)(((value->value.u32 & 0x0000007F) << 1) | + ((value->value.u32 & 0x00008000) >> 15)) - 127; + const int sign = (value->value.u32 & 0x00000080); + value->value.f32 = rc_build_float(mantissa, exponent, sign); + value->type = RC_VALUE_TYPE_FLOAT; +} + static void rc_transform_memref_mbf32(rc_typed_value_t* value) { /* decodes a Microsoft Binary Format float */ /* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */ @@ -305,6 +318,10 @@ void rc_transform_memref_value(rc_typed_value_t* value, char size) { rc_transform_memref_float(value); break; + case RC_MEMSIZE_FLOAT_BE: + rc_transform_memref_float_be(value); + break; + case RC_MEMSIZE_MBF32: rc_transform_memref_mbf32(value); break; @@ -340,6 +357,7 @@ static const unsigned rc_memref_masks[] = { 0xffffffff, /* RC_MEMSIZE_FLOAT */ 0xffffffff, /* RC_MEMSIZE_MBF32 */ 0xffffffff, /* RC_MEMSIZE_MBF32_LE */ + 0xffffffff, /* RC_MEMSIZE_FLOAT_BE */ 0xffffffff /* RC_MEMSIZE_VARIABLE */ }; @@ -376,6 +394,7 @@ static const char rc_memref_shared_sizes[] = { RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */ RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */ }; diff --git a/dep/rcheevos/src/rcheevos/operand.c b/dep/rcheevos/src/rcheevos/operand.c index f8abbbd7b..f8b46ed70 100644 --- a/dep/rcheevos/src/rcheevos/operand.c +++ b/dep/rcheevos/src/rcheevos/operand.c @@ -301,6 +301,7 @@ static int rc_luapeek(lua_State* L) { int rc_operand_is_float_memref(const rc_operand_t* self) { switch (self->size) { case RC_MEMSIZE_FLOAT: + case RC_MEMSIZE_FLOAT_BE: case RC_MEMSIZE_MBF32: case RC_MEMSIZE_MBF32_LE: return 1; diff --git a/dep/rcheevos/src/rcheevos/rc_client.c b/dep/rcheevos/src/rcheevos/rc_client.c index 76b7bf127..bb2776aaa 100644 --- a/dep/rcheevos/src/rcheevos/rc_client.c +++ b/dep/rcheevos/src/rcheevos/rc_client.c @@ -11,14 +11,17 @@ #include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#endif + #define RC_CLIENT_UNKNOWN_GAME_ID (uint32_t)-1 #define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */ -/* clock_t can wrap. For most cases, we won't have to worry about it because it won't - * overflow until after over a month of runtime. But some cases can overflow in as short as - * 36 minutes. Use substraction as a secondary check to ensure an overflow hasn't occurred. */ -#define RC_CLIENT_CLOCK_IS_BEFORE(clk, cmp_clk) (clk < cmp_clk && (cmp_clk - clk) > 0) - struct rc_client_async_handle_t { uint8_t aborted; }; @@ -52,10 +55,7 @@ typedef struct rc_client_load_state_t rc_hash_iterator_t hash_iterator; rc_client_pending_media_t* pending_media; - uint32_t* hardcore_unlocks; - uint32_t* softcore_unlocks; - uint32_t num_hardcore_unlocks; - uint32_t num_softcore_unlocks; + rc_api_start_session_response_t *start_session_response; rc_client_async_handle_t async_handle; @@ -68,11 +68,13 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_dat static void rc_client_hide_progress_tracker(rc_client_t* client, rc_client_game_info_t* game); static void rc_client_load_error(rc_client_load_state_t* load_state, int result, const char* error_message); static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* load_state, const char* hash, const char* file_path); -static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now); +static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); static void rc_client_raise_leaderboard_events(rc_client_t* client, rc_client_subset_info_t* subset); static void rc_client_raise_pending_events(rc_client_t* client, rc_client_game_info_t* game); static void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); -static void rc_client_reschedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* callback, clock_t when); +static void rc_client_reschedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* callback, rc_clock_t when); +static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); +static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); /* ===== Construction/Destruction ===== */ @@ -92,6 +94,7 @@ rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, client->callbacks.server_call = server_call_function; client->callbacks.event_handler = rc_client_dummy_event_handler; rc_client_set_legacy_peek(client, RC_CLIENT_LEGACY_PEEK_AUTO); + rc_client_set_get_time_millisecs_function(client, NULL); rc_mutex_init(&client->state.mutex); @@ -207,6 +210,68 @@ void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_ /* ===== Common ===== */ +static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client) +{ +#if defined(CLOCK_MONOTONIC) + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) + return 0; + + /* round nanoseconds to nearest millisecond and add to seconds */ + return ((rc_clock_t)now.tv_sec * 1000 + ((rc_clock_t)now.tv_nsec / 1000000)); +#elif defined(_WIN32) + static LARGE_INTEGER freq; + LARGE_INTEGER ticks; + + /* Frequency is the number of ticks per second and is guaranteed to not change. */ + if (!freq.QuadPart) { + if (!QueryPerformanceFrequency(&freq)) + return 0; + + /* convert to number of ticks per millisecond to simplify later calculations */ + freq.QuadPart /= 1000; + } + + if (!QueryPerformanceCounter(&ticks)) + return 0; + + return (rc_clock_t)(ticks.QuadPart / freq.QuadPart); +#else + const clock_t clock_now = clock(); + if (sizeof(clock_t) == 4) { + static uint32_t clock_wraps = 0; + static clock_t last_clock = 0; + static time_t last_timet = 0; + const time_t time_now = time(NULL); + + if (last_timet != 0) { + const time_t seconds_per_clock_t = (time_t)(((uint64_t)1 << 32) / CLOCKS_PER_SEC); + if (clock_now < last_clock) { + /* clock() has wrapped */ + ++clock_wraps; + } + else if (time_now - last_timet > seconds_per_clock_t) { + /* it's been long enough that clock() has wrapped and is higher than the last time it was read */ + ++clock_wraps; + } + } + + last_timet = time_now; + last_clock = clock_now; + + return (rc_clock_t)((((uint64_t)clock_wraps << 32) | clock_now) / (CLOCKS_PER_SEC / 1000)); + } + else { + return (rc_clock_t)(clock_now / (CLOCKS_PER_SEC / 1000)); + } +#endif +} + +void rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler) +{ + client->callbacks.get_time_millisecs = handler ? handler : rc_client_clock_get_now_millisecs; +} + static int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle) { int aborted; @@ -261,6 +326,56 @@ static void rc_client_raise_server_error_event(rc_client_t* client, const char* client->callbacks.event_handler(&client_event, client); } +static void rc_client_update_disconnect_state(rc_client_t* client) +{ + rc_client_scheduled_callback_data_t* scheduled_callback; + uint8_t new_state = RC_CLIENT_DISCONNECT_HIDDEN; + + rc_mutex_lock(&client->state.mutex); + + scheduled_callback = client->state.scheduled_callbacks; + for (; scheduled_callback; scheduled_callback = scheduled_callback->next) { + if (scheduled_callback->callback == rc_client_award_achievement_retry || + scheduled_callback->callback == rc_client_submit_leaderboard_entry_retry) { + new_state = RC_CLIENT_DISCONNECT_VISIBLE; + break; + } + } + + if ((client->state.disconnect & RC_CLIENT_DISCONNECT_VISIBLE) != new_state) { + if (new_state == RC_CLIENT_DISCONNECT_VISIBLE) + client->state.disconnect = RC_CLIENT_DISCONNECT_HIDDEN | RC_CLIENT_DISCONNECT_SHOW_PENDING; + else + client->state.disconnect = RC_CLIENT_DISCONNECT_VISIBLE | RC_CLIENT_DISCONNECT_HIDE_PENDING; + } + else { + client->state.disconnect = new_state; + } + + rc_mutex_unlock(&client->state.mutex); +} + +static void rc_client_raise_disconnect_events(rc_client_t* client) +{ + rc_client_event_t client_event; + uint8_t new_state; + + rc_mutex_lock(&client->state.mutex); + + if (client->state.disconnect & RC_CLIENT_DISCONNECT_SHOW_PENDING) + new_state = RC_CLIENT_DISCONNECT_VISIBLE; + else + new_state = RC_CLIENT_DISCONNECT_HIDDEN; + client->state.disconnect = new_state; + + rc_mutex_unlock(&client->state.mutex); + + memset(&client_event, 0, sizeof(client_event)); + client_event.type = (new_state == RC_CLIENT_DISCONNECT_VISIBLE) ? + RC_CLIENT_EVENT_DISCONNECTED : RC_CLIENT_EVENT_RECONNECTED; + client->callbacks.event_handler(&client_event, client); +} + static int rc_client_should_retry(const rc_api_server_response_t* server_response) { switch (server_response->http_status_code) { @@ -276,6 +391,22 @@ static int rc_client_should_retry(const rc_api_server_response_t* server_respons /* too many unlocks occurred at the same time */ return 1; + case 521: /* 521 Web Server is Down */ + /* cloudfare could not find the server */ + return 1; + + case 522: /* 522 Connection Timed Out */ + /* timeout connecting to server from cloudfare */ + return 1; + + case 523: /* 523 Origin is Unreachable */ + /* cloudfare cannot find server */ + return 1; + + case 524: /* 524 A Timeout Occurred */ + /* connection to server from cloudfare was dropped before request was completed */ + return 1; + default: return 0; } @@ -530,7 +661,7 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t ++summary->num_unlocked_achievements; summary->points_unlocked += achievement->public_.points; } - else if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) { + if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) { ++summary->num_unsupported_achievements; } @@ -581,10 +712,10 @@ static void rc_client_free_load_state(rc_client_load_state_t* load_state) if (load_state->game) rc_client_free_game(load_state->game); - if (load_state->hardcore_unlocks) - free(load_state->hardcore_unlocks); - if (load_state->softcore_unlocks) - free(load_state->softcore_unlocks); + if (load_state->start_session_response) { + rc_api_destroy_start_session_response(load_state->start_session_response); + free(load_state->start_session_response); + } free(load_state); } @@ -1001,19 +1132,24 @@ static void rc_client_deactivate_leaderboards(rc_client_game_info_t* game, rc_cl game->runtime.lboard_count = 0; } -static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, uint32_t* unlocks, uint32_t num_unlocks, uint8_t mode) +static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, rc_api_unlock_entry_t* unlocks, uint32_t num_unlocks, uint8_t mode) { rc_client_achievement_info_t* start = subset->achievements; rc_client_achievement_info_t* stop = start + subset->public_.num_achievements; rc_client_achievement_info_t* scan; - unsigned i; + rc_api_unlock_entry_t* unlock = unlocks; + rc_api_unlock_entry_t* unlock_stop = unlocks + num_unlocks; - for (i = 0; i < num_unlocks; ++i) { - uint32_t id = unlocks[i]; + for (; unlock < unlock_stop; ++unlock) { for (scan = start; scan < stop; ++scan) { - if (scan->public_.id == id) { + if (scan->public_.id == unlock->achievement_id) { scan->public_.unlocked |= mode; + if (mode & RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) + scan->unlock_time_hardcore = unlock->when; + if (mode & RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE) + scan->unlock_time_softcore = unlock->when; + if (scan == start) ++start; else if (scan + 1 == stop) @@ -1024,7 +1160,7 @@ static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, uint32_t* u } } -static void rc_client_activate_game(rc_client_load_state_t* load_state) +static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_start_session_response_t *start_session_response) { rc_client_t* client = load_state->client; @@ -1039,18 +1175,17 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state) if (load_state->callback) load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); } - else if ((!load_state->softcore_unlocks || !load_state->hardcore_unlocks) && - client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) { + else if (!start_session_response && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) { /* unlocks not available - assume malloc failed */ if (load_state->callback) load_state->callback(RC_INVALID_STATE, "Unlock arrays were not allocated", client, load_state->callback_userdata); } else { if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) { - rc_client_apply_unlocks(load_state->subset, load_state->softcore_unlocks, - load_state->num_softcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); - rc_client_apply_unlocks(load_state->subset, load_state->hardcore_unlocks, - load_state->num_hardcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH); + rc_client_apply_unlocks(load_state->subset, start_session_response->hardcore_unlocks, + start_session_response->num_hardcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH); + rc_client_apply_unlocks(load_state->subset, start_session_response->unlocks, + start_session_response->num_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); } rc_mutex_lock(&client->state.mutex); @@ -1094,11 +1229,11 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state) memset(callback_data, 0, sizeof(*callback_data)); callback_data->callback = rc_client_ping; callback_data->related_id = load_state->game->public_.id; - callback_data->when = clock() + 30 * CLOCKS_PER_SEC; + callback_data->when = client->callbacks.get_time_millisecs(client) + 30 * 1000; rc_client_schedule_callback(client, callback_data); } - RC_CLIENT_LOG_INFO_FORMATTED(client, "Game %u loaded, hardcode %s%s", load_state->game->public_.id, + RC_CLIENT_LOG_INFO_FORMATTED(client, "Game %u loaded, hardcore %s%s", load_state->game->public_.id, client->state.hardcore ? "enabled" : "disabled", (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) ? ", spectating" : ""); } @@ -1142,80 +1277,30 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser else if (outstanding_requests < 0) { /* previous load state was aborted, load_state was free'd */ } + else if (outstanding_requests == 0) { + rc_client_activate_game(load_state, &start_session_response); + } else { - if (outstanding_requests == 0) - rc_client_activate_game(load_state); + load_state->start_session_response = + (rc_api_start_session_response_t*)malloc(sizeof(rc_api_start_session_response_t)); + + if (!load_state->start_session_response) { + rc_client_load_error(callback_data, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY)); + } + else { + /* safer to parse the response again than to try to copy it */ + rc_api_process_start_session_response(load_state->start_session_response, server_response->body); + } } rc_api_destroy_start_session_response(&start_session_response); } -static void rc_client_unlocks_callback(const rc_api_server_response_t* server_response, void* callback_data, int mode) -{ - rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data; - rc_api_fetch_user_unlocks_response_t fetch_user_unlocks_response; - int outstanding_requests; - const char* error_message; - int result; - - if (rc_client_async_handle_aborted(load_state->client, &load_state->async_handle)) { - rc_client_t* client = load_state->client; - rc_client_load_aborted(load_state); - RC_CLIENT_LOG_VERBOSE(client, "Load aborted while fetching unlocks"); - return; - } - - result = rc_api_process_fetch_user_unlocks_server_response(&fetch_user_unlocks_response, server_response); - error_message = rc_client_server_error_message(&result, server_response->http_status_code, &fetch_user_unlocks_response.response); - outstanding_requests = rc_client_end_load_state(load_state); - - if (error_message) { - rc_client_load_error(callback_data, result, error_message); - } - else if (outstanding_requests < 0) { - /* previous load state was aborted, load_state was free'd */ - } - else { - if (mode == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) { - const size_t array_size = fetch_user_unlocks_response.num_achievement_ids * sizeof(uint32_t); - load_state->num_hardcore_unlocks = fetch_user_unlocks_response.num_achievement_ids; - load_state->hardcore_unlocks = (uint32_t*)malloc(array_size); - if (load_state->hardcore_unlocks) - memcpy(load_state->hardcore_unlocks, fetch_user_unlocks_response.achievement_ids, array_size); - } - else { - const size_t array_size = fetch_user_unlocks_response.num_achievement_ids * sizeof(uint32_t); - load_state->num_softcore_unlocks = fetch_user_unlocks_response.num_achievement_ids; - load_state->softcore_unlocks = (uint32_t*)malloc(array_size); - if (load_state->softcore_unlocks) - memcpy(load_state->softcore_unlocks, fetch_user_unlocks_response.achievement_ids, array_size); - } - - if (outstanding_requests == 0) - rc_client_activate_game(load_state); - } - - rc_api_destroy_fetch_user_unlocks_response(&fetch_user_unlocks_response); -} - -static void rc_client_hardcore_unlocks_callback(const rc_api_server_response_t* server_response, void* callback_data) -{ - rc_client_unlocks_callback(server_response, callback_data, RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE); -} - -static void rc_client_softcore_unlocks_callback(const rc_api_server_response_t* server_response, void* callback_data) -{ - rc_client_unlocks_callback(server_response, callback_data, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); -} - static void rc_client_begin_start_session(rc_client_load_state_t* load_state) { rc_api_start_session_request_t start_session_params; - rc_api_fetch_user_unlocks_request_t unlock_params; rc_client_t* client = load_state->client; rc_api_request_t start_session_request; - rc_api_request_t hardcore_unlock_request; - rc_api_request_t softcore_unlock_request; int result; memset(&start_session_params, 0, sizeof(start_session_params)); @@ -1228,38 +1313,9 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state) rc_client_load_error(load_state, result, rc_error_str(result)); } else { - memset(&unlock_params, 0, sizeof(unlock_params)); - unlock_params.username = client->user.username; - unlock_params.api_token = client->user.token; - unlock_params.game_id = load_state->hash->game_id; - unlock_params.hardcore = 1; - - result = rc_api_init_fetch_user_unlocks_request(&hardcore_unlock_request, &unlock_params); - if (result != RC_OK) { - rc_client_load_error(load_state, result, rc_error_str(result)); - } - else { - unlock_params.hardcore = 0; - - result = rc_api_init_fetch_user_unlocks_request(&softcore_unlock_request, &unlock_params); - if (result != RC_OK) { - rc_client_load_error(load_state, result, rc_error_str(result)); - } - else { - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 3); - - /* TODO: create single server request to do all three of these */ - RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id); - client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client); - client->callbacks.server_call(&hardcore_unlock_request, rc_client_hardcore_unlocks_callback, load_state, client); - client->callbacks.server_call(&softcore_unlock_request, rc_client_softcore_unlocks_callback, load_state, client); - - rc_api_destroy_request(&softcore_unlock_request); - } - - rc_api_destroy_request(&hardcore_unlock_request); - } - + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1); + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id); + client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client); rc_api_destroy_request(&start_session_request); } } @@ -1590,13 +1646,18 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s scan->next = subset; } + if (load_state->client->callbacks.post_process_game_data_response) { + load_state->client->callbacks.post_process_game_data_response(server_response, + &fetch_game_data_response, load_state->client, load_state->callback_userdata); + } + outstanding_requests = rc_client_end_load_state(load_state); if (outstanding_requests < 0) { /* previous load state was aborted, load_state was free'd */ } else { if (outstanding_requests == 0) - rc_client_activate_game(load_state); + rc_client_activate_game(load_state, load_state->start_session_response); } } @@ -2299,7 +2360,7 @@ void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_cli return; } - snprintf(buffer, sizeof(buffer), "[SUBSET%u]", subset_id); + snprintf(buffer, sizeof(buffer), "[SUBSET%lu]", (unsigned long)subset_id); load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state)); if (!load_state) { @@ -2372,11 +2433,11 @@ static void rc_client_update_achievement_display_information(rc_client_t* client if (!achievement->trigger->measured_as_percent) { snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress), - "%u/%u", new_measured_value, achievement->trigger->measured_target); + "%lu/%lu", (unsigned long)new_measured_value, (unsigned long)achievement->trigger->measured_target); } else if (achievement->public_.measured_percent >= 1.0) { snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress), - "%u%%", (uint32_t)achievement->public_.measured_percent); + "%lu%%", (unsigned long)achievement->public_.measured_percent); } } } @@ -2704,7 +2765,7 @@ typedef struct rc_client_award_achievement_callback_data_t static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data); -static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now) { rc_client_award_achievement_callback_data_t* ach_data = (rc_client_award_achievement_callback_data_t*)callback_data->data; @@ -2736,7 +2797,7 @@ static void rc_client_award_achievement_callback(const rc_api_server_response_t* else { /* double wait time between each attempt until we hit a maximum delay of two minutes */ /* 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s -> 120s ...*/ - const uint32_t delay = (ach_data->retry_count > 7) ? 120 : (1 << (ach_data->retry_count - 1)); + const uint32_t delay = (ach_data->retry_count > 8) ? 120 : (1 << (ach_data->retry_count - 2)); RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error awarding achievement %u: %s, retrying in %u seconds", ach_data->id, error_message, delay); if (!ach_data->scheduled_callback_data) { @@ -2751,9 +2812,12 @@ static void rc_client_award_achievement_callback(const rc_api_server_response_t* ach_data->scheduled_callback_data->related_id = ach_data->id; } - ach_data->scheduled_callback_data->when = clock() + delay * CLOCKS_PER_SEC; + ach_data->scheduled_callback_data->when = + ach_data->client->callbacks.get_time_millisecs(ach_data->client) + delay * 1000; rc_client_schedule_callback(ach_data->client, ach_data->scheduled_callback_data); + + rc_client_update_disconnect_state(ach_data->client); return; } } @@ -2803,6 +2867,9 @@ static void rc_client_award_achievement_callback(const rc_api_server_response_t* } } + if (ach_data->retry_count) + rc_client_update_disconnect_state(ach_data->client); + if (ach_data->scheduled_callback_data) free(ach_data->scheduled_callback_data); free(ach_data); @@ -2860,6 +2927,12 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme rc_mutex_unlock(&client->state.mutex); + if (client->callbacks.can_submit_achievement_unlock && + !client->callbacks.can_submit_achievement_unlock(achievement->public_.id, client)) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Achievement %u unlock blocked by client", achievement->public_.id); + return; + } + /* can't unlock unofficial achievements on the server */ if (achievement->public_.category != RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Unlocked unofficial achievement %u: %s", achievement->public_.id, achievement->public_.title); @@ -3264,7 +3337,7 @@ typedef struct rc_client_submit_leaderboard_entry_callback_data_t static void rc_client_submit_leaderboard_entry_server_call(rc_client_submit_leaderboard_entry_callback_data_t* lboard_data); -static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now) { rc_client_submit_leaderboard_entry_callback_data_t* lboard_data = (rc_client_submit_leaderboard_entry_callback_data_t*)callback_data->data; @@ -3296,7 +3369,7 @@ static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_resp else { /* double wait time between each attempt until we hit a maximum delay of two minutes */ /* 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s -> 120s ...*/ - const uint32_t delay = (lboard_data->retry_count > 7) ? 120 : (1 << (lboard_data->retry_count - 1)); + const uint32_t delay = (lboard_data->retry_count > 8) ? 120 : (1 << (lboard_data->retry_count - 2)); RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error submitting leaderboard entry %u: %s, retrying in %u seconds", lboard_data->id, error_message, delay); if (!lboard_data->scheduled_callback_data) { @@ -3311,9 +3384,12 @@ static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_resp lboard_data->scheduled_callback_data->related_id = lboard_data->id; } - lboard_data->scheduled_callback_data->when = clock() + delay * CLOCKS_PER_SEC; + lboard_data->scheduled_callback_data->when = + lboard_data->client->callbacks.get_time_millisecs(lboard_data->client) + delay * 1000; rc_client_schedule_callback(lboard_data->client, lboard_data->scheduled_callback_data); + + rc_client_update_disconnect_state(lboard_data->client); return; } } @@ -3327,6 +3403,9 @@ static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_resp } } + if (lboard_data->retry_count) + rc_client_update_disconnect_state(lboard_data->client); + if (lboard_data->scheduled_callback_data) free(lboard_data->scheduled_callback_data); free(lboard_data); @@ -3360,6 +3439,12 @@ static void rc_client_submit_leaderboard_entry(rc_client_t* client, rc_client_le { rc_client_submit_leaderboard_entry_callback_data_t* callback_data; + if (client->callbacks.can_submit_leaderboard_entry && + !client->callbacks.can_submit_leaderboard_entry(leaderboard->public_.id, client)) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Leaderboard %u entry submission blocked by client", leaderboard->public_.id); + return; + } + /* don't actually submit leaderboard entries when spectating */ if (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectated %s (%d) for leaderboard %u: %s", @@ -3587,7 +3672,7 @@ static void rc_client_ping_callback(const rc_api_server_response_t* server_respo rc_api_destroy_ping_response(&response); } -static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now) { rc_api_ping_request_t api_params; rc_api_request_t request; @@ -3611,7 +3696,7 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r client->callbacks.server_call(&request, rc_client_ping_callback, client, client); } - callback_data->when = now + 120 * CLOCKS_PER_SEC; + callback_data->when = now + 120 * 1000; rc_client_schedule_callback(client, callback_data); } @@ -3738,8 +3823,12 @@ static unsigned rc_client_peek(unsigned address, unsigned num_bytes, void* ud) void rc_client_set_legacy_peek(rc_client_t* client, int method) { if (method == RC_CLIENT_LEGACY_PEEK_AUTO) { - uint8_t buffer[4] = { 1,0,0,0 }; - method = (*((uint32_t*)buffer) == 1) ? + union { + uint32_t whole; + uint8_t parts[4]; + } u; + u.whole = 1; + method = (u.parts[0] == 1) ? RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS : RC_CLIENT_LEGACY_PEEK_CONSTRUCTED; } @@ -3860,7 +3949,7 @@ static void rc_client_hide_progress_tracker(rc_client_t* client, rc_client_game_ } } -static void rc_client_progress_tracker_timer_elapsed(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +static void rc_client_progress_tracker_timer_elapsed(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now) { rc_client_event_t client_event; memset(&client_event, 0, sizeof(client_event)); @@ -3890,7 +3979,8 @@ static void rc_client_do_frame_update_progress_tracker(rc_client_t* client, rc_c else game->progress_tracker.action = RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE; - rc_client_reschedule_callback(client, game->progress_tracker.hide_callback, clock() + 2 * CLOCKS_PER_SEC); + rc_client_reschedule_callback(client, game->progress_tracker.hide_callback, + client->callbacks.get_time_millisecs(client) + 2 * 1000); } static void rc_client_raise_progress_tracker_events(rc_client_t* client, rc_client_game_info_t* game) @@ -4216,13 +4306,13 @@ void rc_client_idle(rc_client_t* client) scheduled_callback = client->state.scheduled_callbacks; if (scheduled_callback) { - const clock_t now = clock(); + const rc_clock_t now = client->callbacks.get_time_millisecs(client); do { rc_mutex_lock(&client->state.mutex); scheduled_callback = client->state.scheduled_callbacks; if (scheduled_callback) { - if (RC_CLIENT_CLOCK_IS_BEFORE(now, scheduled_callback->when)) { + if (scheduled_callback->when > now) { /* not time for next callback yet, ignore it */ scheduled_callback = NULL; } @@ -4239,6 +4329,9 @@ void rc_client_idle(rc_client_t* client) scheduled_callback->callback(scheduled_callback, client, now); } while (1); } + + if (client->state.disconnect & ~RC_CLIENT_DISCONNECT_VISIBLE) + rc_client_raise_disconnect_events(client); } void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback) @@ -4251,7 +4344,7 @@ void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callba last = &client->state.scheduled_callbacks; do { next = *last; - if (!next || RC_CLIENT_CLOCK_IS_BEFORE(scheduled_callback->when, next->when)) { + if (!next || scheduled_callback->when < next->when) { scheduled_callback->next = next; *last = scheduled_callback; break; @@ -4264,7 +4357,7 @@ void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callba } static void rc_client_reschedule_callback(rc_client_t* client, - rc_client_scheduled_callback_data_t* callback, clock_t when) + rc_client_scheduled_callback_data_t* callback, rc_clock_t when) { rc_client_scheduled_callback_data_t** last; rc_client_scheduled_callback_data_t* next; @@ -4290,7 +4383,7 @@ static void rc_client_reschedule_callback(rc_client_t* client, break; } - if (RC_CLIENT_CLOCK_IS_BEFORE(when, next->next->when)) { + if (when < next->next->when) { /* already in the correct place */ break; } @@ -4301,7 +4394,7 @@ static void rc_client_reschedule_callback(rc_client_t* client, continue; } - if (!next || RC_CLIENT_CLOCK_IS_BEFORE(when, next->when)) { + if (!next || when < next->when) { /* insert here */ callback->next = next; *last = callback; diff --git a/dep/rcheevos/src/rcheevos/rc_client_internal.h b/dep/rcheevos/src/rcheevos/rc_client_internal.h index 9d0dd9f99..483d4efea 100644 --- a/dep/rcheevos/src/rcheevos/rc_client_internal.h +++ b/dep/rcheevos/src/rcheevos/rc_client_internal.h @@ -11,21 +11,31 @@ extern "C" { #include "rc_runtime.h" #include "rc_runtime_types.h" +struct rc_api_fetch_game_data_response_t; +typedef void (*rc_client_post_process_game_data_response_t)(const rc_api_server_response_t* server_response, + struct rc_api_fetch_game_data_response_t* game_data_response, rc_client_t* client, void* userdata); +typedef int (*rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client); +typedef int (*rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client); + typedef struct rc_client_callbacks_t { rc_client_read_memory_func_t read_memory; rc_client_event_handler_t event_handler; rc_client_server_call_t server_call; rc_client_message_callback_t log_call; + rc_get_time_millisecs_func_t get_time_millisecs; + rc_client_post_process_game_data_response_t post_process_game_data_response; + rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock; + rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry; void* client_data; } rc_client_callbacks_t; struct rc_client_scheduled_callback_data_t; -typedef void (*rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now); +typedef void (*rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); typedef struct rc_client_scheduled_callback_data_t { - clock_t when; + rc_clock_t when; unsigned related_id; rc_client_scheduled_callback_t callback; void* data; @@ -211,6 +221,13 @@ enum { RC_CLIENT_SPECTATOR_MODE_LOCKED }; +enum { + RC_CLIENT_DISCONNECT_HIDDEN = 0, + RC_CLIENT_DISCONNECT_VISIBLE = (1 << 0), + RC_CLIENT_DISCONNECT_SHOW_PENDING = (1 << 1), + RC_CLIENT_DISCONNECT_HIDE_PENDING = (1 << 2) +}; + struct rc_client_load_state_t; typedef struct rc_client_state_t { @@ -225,6 +242,7 @@ typedef struct rc_client_state_t { uint8_t unofficial_enabled; uint8_t log_level; uint8_t user; + uint8_t disconnect; struct rc_client_load_state_t* load; rc_memref_t* processing_memref;