dep/rcheevos: Update to ffddcdb
This commit is contained in:
parent
d437124b99
commit
1335d5f186
|
@ -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
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "rc_api_request.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -57,6 +57,7 @@ enum {
|
|||
RC_MEMSIZE_FLOAT,
|
||||
RC_MEMSIZE_MBF32,
|
||||
RC_MEMSIZE_MBF32_LE,
|
||||
RC_MEMSIZE_FLOAT_BE,
|
||||
RC_MEMSIZE_VARIABLE
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<ClCompile Include="src\rcheevos\lboard.c" />
|
||||
<ClCompile Include="src\rcheevos\memref.c" />
|
||||
<ClCompile Include="src\rcheevos\operand.c" />
|
||||
<ClCompile Include="src\rcheevos\rc_client.c" />
|
||||
<ClCompile Include="src\rcheevos\richpresence.c" />
|
||||
<ClCompile Include="src\rcheevos\runtime.c" />
|
||||
<ClCompile Include="src\rcheevos\runtime_progress.c" />
|
||||
|
@ -33,6 +34,7 @@
|
|||
<ClInclude Include="include\rc_api_request.h" />
|
||||
<ClInclude Include="include\rc_api_runtime.h" />
|
||||
<ClInclude Include="include\rc_api_user.h" />
|
||||
<ClInclude Include="include\rc_client.h" />
|
||||
<ClInclude Include="include\rc_consoles.h" />
|
||||
<ClInclude Include="include\rc_error.h" />
|
||||
<ClInclude Include="include\rc_hash.h" />
|
||||
|
@ -40,6 +42,7 @@
|
|||
<ClInclude Include="include\rc_runtime_types.h" />
|
||||
<ClInclude Include="include\rc_url.h" />
|
||||
<ClInclude Include="src\rapi\rc_api_common.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_client_internal.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_compat.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_internal.h" />
|
||||
<ClInclude Include="src\rhash\md5.h" />
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
<ClCompile Include="src\rapi\rc_api_common.c">
|
||||
<Filter>rapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\rc_client.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\rc_consoles.h">
|
||||
|
@ -137,5 +140,11 @@
|
|||
<ClInclude Include="src\rapi\rc_api_common.h">
|
||||
<Filter>rapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\rcheevos\rc_client_internal.h">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_client.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,14 +11,17 @@
|
|||
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <profileapi.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue