mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 22:05:38 +00:00
dep/rcheevos: Update to ffddcdb
This commit is contained in:
parent
7d178c04d3
commit
58d62e1ab4
|
@ -26,6 +26,7 @@ add_library(rcheevos
|
||||||
src/rcheevos/lboard.c
|
src/rcheevos/lboard.c
|
||||||
src/rcheevos/memref.c
|
src/rcheevos/memref.c
|
||||||
src/rcheevos/operand.c
|
src/rcheevos/operand.c
|
||||||
|
src/rcheevos/rc_client.c
|
||||||
src/rcheevos/rc_compat.h
|
src/rcheevos/rc_compat.h
|
||||||
src/rcheevos/rc_internal.h
|
src/rcheevos/rc_internal.h
|
||||||
src/rcheevos/richpresence.c
|
src/rcheevos/richpresence.c
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "rc_api_request.h"
|
#include "rc_api_request.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
@ -65,10 +67,34 @@ typedef struct rc_api_start_session_request_t {
|
||||||
}
|
}
|
||||||
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.
|
* Response data for a start session request.
|
||||||
*/
|
*/
|
||||||
typedef struct rc_api_start_session_response_t {
|
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 */
|
/* Common server-provided response information */
|
||||||
rc_api_response_t response;
|
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);
|
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 |
|
| 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);
|
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 |
|
| Logging |
|
||||||
\*****************************************************************************/
|
\*****************************************************************************/
|
||||||
|
@ -502,7 +510,9 @@ enum {
|
||||||
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */
|
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_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_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
|
typedef struct rc_client_server_error_t
|
||||||
|
|
|
@ -57,6 +57,7 @@ enum {
|
||||||
RC_MEMSIZE_FLOAT,
|
RC_MEMSIZE_FLOAT,
|
||||||
RC_MEMSIZE_MBF32,
|
RC_MEMSIZE_MBF32,
|
||||||
RC_MEMSIZE_MBF32_LE,
|
RC_MEMSIZE_MBF32_LE,
|
||||||
|
RC_MEMSIZE_FLOAT_BE,
|
||||||
RC_MEMSIZE_VARIABLE
|
RC_MEMSIZE_VARIABLE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<ClCompile Include="src\rcheevos\lboard.c" />
|
<ClCompile Include="src\rcheevos\lboard.c" />
|
||||||
<ClCompile Include="src\rcheevos\memref.c" />
|
<ClCompile Include="src\rcheevos\memref.c" />
|
||||||
<ClCompile Include="src\rcheevos\operand.c" />
|
<ClCompile Include="src\rcheevos\operand.c" />
|
||||||
|
<ClCompile Include="src\rcheevos\rc_client.c" />
|
||||||
<ClCompile Include="src\rcheevos\richpresence.c" />
|
<ClCompile Include="src\rcheevos\richpresence.c" />
|
||||||
<ClCompile Include="src\rcheevos\runtime.c" />
|
<ClCompile Include="src\rcheevos\runtime.c" />
|
||||||
<ClCompile Include="src\rcheevos\runtime_progress.c" />
|
<ClCompile Include="src\rcheevos\runtime_progress.c" />
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
<ClInclude Include="include\rc_api_request.h" />
|
<ClInclude Include="include\rc_api_request.h" />
|
||||||
<ClInclude Include="include\rc_api_runtime.h" />
|
<ClInclude Include="include\rc_api_runtime.h" />
|
||||||
<ClInclude Include="include\rc_api_user.h" />
|
<ClInclude Include="include\rc_api_user.h" />
|
||||||
|
<ClInclude Include="include\rc_client.h" />
|
||||||
<ClInclude Include="include\rc_consoles.h" />
|
<ClInclude Include="include\rc_consoles.h" />
|
||||||
<ClInclude Include="include\rc_error.h" />
|
<ClInclude Include="include\rc_error.h" />
|
||||||
<ClInclude Include="include\rc_hash.h" />
|
<ClInclude Include="include\rc_hash.h" />
|
||||||
|
@ -40,6 +42,7 @@
|
||||||
<ClInclude Include="include\rc_runtime_types.h" />
|
<ClInclude Include="include\rc_runtime_types.h" />
|
||||||
<ClInclude Include="include\rc_url.h" />
|
<ClInclude Include="include\rc_url.h" />
|
||||||
<ClInclude Include="src\rapi\rc_api_common.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_compat.h" />
|
||||||
<ClInclude Include="src\rcheevos\rc_internal.h" />
|
<ClInclude Include="src\rcheevos\rc_internal.h" />
|
||||||
<ClInclude Include="src\rhash\md5.h" />
|
<ClInclude Include="src\rhash\md5.h" />
|
||||||
|
|
|
@ -87,6 +87,9 @@
|
||||||
<ClCompile Include="src\rapi\rc_api_common.c">
|
<ClCompile Include="src\rapi\rc_api_common.c">
|
||||||
<Filter>rapi</Filter>
|
<Filter>rapi</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="src\rcheevos\rc_client.c">
|
||||||
|
<Filter>rcheevos</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="include\rc_consoles.h">
|
<ClInclude Include="include\rc_consoles.h">
|
||||||
|
@ -137,5 +140,11 @@
|
||||||
<ClInclude Include="src\rapi\rc_api_common.h">
|
<ClInclude Include="src\rapi\rc_api_common.h">
|
||||||
<Filter>rapi</Filter>
|
<Filter>rapi</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="src\rcheevos\rc_client_internal.h">
|
||||||
|
<Filter>rcheevos</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\rc_client.h">
|
||||||
|
<Filter>include</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</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) {
|
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
|
#ifndef NDEBUG
|
||||||
if (strcmp(field->name, field_name) != 0)
|
if (strcmp(field->name, field_name) != 0)
|
||||||
return 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 != '[') {
|
if (!field->value_start || *field->value_start != '[') {
|
||||||
*num_entries = 0;
|
*num_entries = 0;
|
||||||
return rc_json_missing_field(response, field);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(array_field, field, sizeof(*array_field));
|
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_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_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);
|
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_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_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);
|
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;
|
return RC_INVALID_STATE;
|
||||||
|
|
||||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||||
if (rc_api_url_build_dorequest(&builder, "postactivity", api_params->username, api_params->api_token)) {
|
if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) {
|
||||||
/* activity type enum (only 3 is used )
|
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||||
* 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);
|
|
||||||
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
|
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
|
||||||
request->post_data = rc_url_builder_finalize(&builder);
|
request->post_data = rc_url_builder_finalize(&builder);
|
||||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
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) {
|
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_field_t fields[] = {
|
||||||
RC_JSON_NEW_FIELD("Success"),
|
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));
|
memset(response, 0, sizeof(*response));
|
||||||
rc_buf_init(&response->response.buffer);
|
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) {
|
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;
|
++aux;
|
||||||
switch (*aux++) {
|
switch (*aux++) {
|
||||||
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
|
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 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
|
||||||
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; 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;
|
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) {
|
static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
|
||||||
/* decodes a Microsoft Binary Format float */
|
/* decodes a Microsoft Binary Format float */
|
||||||
/* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */
|
/* 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);
|
rc_transform_memref_float(value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RC_MEMSIZE_FLOAT_BE:
|
||||||
|
rc_transform_memref_float_be(value);
|
||||||
|
break;
|
||||||
|
|
||||||
case RC_MEMSIZE_MBF32:
|
case RC_MEMSIZE_MBF32:
|
||||||
rc_transform_memref_mbf32(value);
|
rc_transform_memref_mbf32(value);
|
||||||
break;
|
break;
|
||||||
|
@ -340,6 +357,7 @@ static const unsigned rc_memref_masks[] = {
|
||||||
0xffffffff, /* RC_MEMSIZE_FLOAT */
|
0xffffffff, /* RC_MEMSIZE_FLOAT */
|
||||||
0xffffffff, /* RC_MEMSIZE_MBF32 */
|
0xffffffff, /* RC_MEMSIZE_MBF32 */
|
||||||
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
|
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
|
||||||
|
0xffffffff, /* RC_MEMSIZE_FLOAT_BE */
|
||||||
0xffffffff /* RC_MEMSIZE_VARIABLE */
|
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_FLOAT */
|
||||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
|
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
|
||||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
|
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
|
||||||
|
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */
|
||||||
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
|
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) {
|
int rc_operand_is_float_memref(const rc_operand_t* self) {
|
||||||
switch (self->size) {
|
switch (self->size) {
|
||||||
case RC_MEMSIZE_FLOAT:
|
case RC_MEMSIZE_FLOAT:
|
||||||
|
case RC_MEMSIZE_FLOAT_BE:
|
||||||
case RC_MEMSIZE_MBF32:
|
case RC_MEMSIZE_MBF32:
|
||||||
case RC_MEMSIZE_MBF32_LE:
|
case RC_MEMSIZE_MBF32_LE:
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -11,14 +11,17 @@
|
||||||
|
|
||||||
#include <stdarg.h>
|
#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_UNKNOWN_GAME_ID (uint32_t)-1
|
||||||
#define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */
|
#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 {
|
struct rc_client_async_handle_t {
|
||||||
uint8_t aborted;
|
uint8_t aborted;
|
||||||
};
|
};
|
||||||
|
@ -52,10 +55,7 @@ typedef struct rc_client_load_state_t
|
||||||
rc_hash_iterator_t hash_iterator;
|
rc_hash_iterator_t hash_iterator;
|
||||||
rc_client_pending_media_t* pending_media;
|
rc_client_pending_media_t* pending_media;
|
||||||
|
|
||||||
uint32_t* hardcore_unlocks;
|
rc_api_start_session_response_t *start_session_response;
|
||||||
uint32_t* softcore_unlocks;
|
|
||||||
uint32_t num_hardcore_unlocks;
|
|
||||||
uint32_t num_softcore_unlocks;
|
|
||||||
|
|
||||||
rc_client_async_handle_t async_handle;
|
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_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 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 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_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_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_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 ===== */
|
/* ===== 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.server_call = server_call_function;
|
||||||
client->callbacks.event_handler = rc_client_dummy_event_handler;
|
client->callbacks.event_handler = rc_client_dummy_event_handler;
|
||||||
rc_client_set_legacy_peek(client, RC_CLIENT_LEGACY_PEEK_AUTO);
|
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);
|
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 ===== */
|
/* ===== 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)
|
static int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle)
|
||||||
{
|
{
|
||||||
int aborted;
|
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);
|
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)
|
static int rc_client_should_retry(const rc_api_server_response_t* server_response)
|
||||||
{
|
{
|
||||||
switch (server_response->http_status_code) {
|
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 */
|
/* too many unlocks occurred at the same time */
|
||||||
return 1;
|
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:
|
default:
|
||||||
return 0;
|
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->num_unlocked_achievements;
|
||||||
summary->points_unlocked += achievement->public_.points;
|
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;
|
++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)
|
if (load_state->game)
|
||||||
rc_client_free_game(load_state->game);
|
rc_client_free_game(load_state->game);
|
||||||
|
|
||||||
if (load_state->hardcore_unlocks)
|
if (load_state->start_session_response) {
|
||||||
free(load_state->hardcore_unlocks);
|
rc_api_destroy_start_session_response(load_state->start_session_response);
|
||||||
if (load_state->softcore_unlocks)
|
free(load_state->start_session_response);
|
||||||
free(load_state->softcore_unlocks);
|
}
|
||||||
|
|
||||||
free(load_state);
|
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;
|
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* start = subset->achievements;
|
||||||
rc_client_achievement_info_t* stop = start + subset->public_.num_achievements;
|
rc_client_achievement_info_t* stop = start + subset->public_.num_achievements;
|
||||||
rc_client_achievement_info_t* scan;
|
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) {
|
for (; unlock < unlock_stop; ++unlock) {
|
||||||
uint32_t id = unlocks[i];
|
|
||||||
for (scan = start; scan < stop; ++scan) {
|
for (scan = start; scan < stop; ++scan) {
|
||||||
if (scan->public_.id == id) {
|
if (scan->public_.id == unlock->achievement_id) {
|
||||||
scan->public_.unlocked |= mode;
|
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)
|
if (scan == start)
|
||||||
++start;
|
++start;
|
||||||
else if (scan + 1 == stop)
|
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;
|
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)
|
if (load_state->callback)
|
||||||
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
|
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) &&
|
else if (!start_session_response && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) {
|
||||||
client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) {
|
|
||||||
/* unlocks not available - assume malloc failed */
|
/* unlocks not available - assume malloc failed */
|
||||||
if (load_state->callback)
|
if (load_state->callback)
|
||||||
load_state->callback(RC_INVALID_STATE, "Unlock arrays were not allocated", client, load_state->callback_userdata);
|
load_state->callback(RC_INVALID_STATE, "Unlock arrays were not allocated", client, load_state->callback_userdata);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) {
|
if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) {
|
||||||
rc_client_apply_unlocks(load_state->subset, load_state->softcore_unlocks,
|
rc_client_apply_unlocks(load_state->subset, start_session_response->hardcore_unlocks,
|
||||||
load_state->num_softcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
|
start_session_response->num_hardcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH);
|
||||||
rc_client_apply_unlocks(load_state->subset, load_state->hardcore_unlocks,
|
rc_client_apply_unlocks(load_state->subset, start_session_response->unlocks,
|
||||||
load_state->num_hardcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH);
|
start_session_response->num_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
rc_mutex_lock(&client->state.mutex);
|
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));
|
memset(callback_data, 0, sizeof(*callback_data));
|
||||||
callback_data->callback = rc_client_ping;
|
callback_data->callback = rc_client_ping;
|
||||||
callback_data->related_id = load_state->game->public_.id;
|
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_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.hardcore ? "enabled" : "disabled",
|
||||||
(client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) ? ", spectating" : "");
|
(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) {
|
else if (outstanding_requests < 0) {
|
||||||
/* previous load state was aborted, load_state was free'd */
|
/* 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 {
|
else {
|
||||||
if (outstanding_requests == 0)
|
load_state->start_session_response =
|
||||||
rc_client_activate_game(load_state);
|
(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);
|
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)
|
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_start_session_request_t start_session_params;
|
||||||
rc_api_fetch_user_unlocks_request_t unlock_params;
|
|
||||||
rc_client_t* client = load_state->client;
|
rc_client_t* client = load_state->client;
|
||||||
rc_api_request_t start_session_request;
|
rc_api_request_t start_session_request;
|
||||||
rc_api_request_t hardcore_unlock_request;
|
|
||||||
rc_api_request_t softcore_unlock_request;
|
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
memset(&start_session_params, 0, sizeof(start_session_params));
|
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));
|
rc_client_load_error(load_state, result, rc_error_str(result));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
memset(&unlock_params, 0, sizeof(unlock_params));
|
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
|
||||||
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);
|
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(&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_api_destroy_request(&start_session_request);
|
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;
|
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);
|
outstanding_requests = rc_client_end_load_state(load_state);
|
||||||
if (outstanding_requests < 0) {
|
if (outstanding_requests < 0) {
|
||||||
/* previous load state was aborted, load_state was free'd */
|
/* previous load state was aborted, load_state was free'd */
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (outstanding_requests == 0)
|
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;
|
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));
|
load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state));
|
||||||
if (!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) {
|
if (!achievement->trigger->measured_as_percent) {
|
||||||
snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress),
|
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) {
|
else if (achievement->public_.measured_percent >= 1.0) {
|
||||||
snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress),
|
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_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* ach_data =
|
||||||
(rc_client_award_achievement_callback_data_t*)callback_data->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 {
|
else {
|
||||||
/* double wait time between each attempt until we hit a maximum delay of two minutes */
|
/* 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 ...*/
|
/* 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);
|
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) {
|
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->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_schedule_callback(ach_data->client, ach_data->scheduled_callback_data);
|
||||||
|
|
||||||
|
rc_client_update_disconnect_state(ach_data->client);
|
||||||
return;
|
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)
|
if (ach_data->scheduled_callback_data)
|
||||||
free(ach_data->scheduled_callback_data);
|
free(ach_data->scheduled_callback_data);
|
||||||
free(ach_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);
|
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 */
|
/* can't unlock unofficial achievements on the server */
|
||||||
if (achievement->public_.category != RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE) {
|
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);
|
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_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* lboard_data =
|
||||||
(rc_client_submit_leaderboard_entry_callback_data_t*)callback_data->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 {
|
else {
|
||||||
/* double wait time between each attempt until we hit a maximum delay of two minutes */
|
/* 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 ...*/
|
/* 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);
|
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) {
|
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->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_schedule_callback(lboard_data->client, lboard_data->scheduled_callback_data);
|
||||||
|
|
||||||
|
rc_client_update_disconnect_state(lboard_data->client);
|
||||||
return;
|
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)
|
if (lboard_data->scheduled_callback_data)
|
||||||
free(lboard_data->scheduled_callback_data);
|
free(lboard_data->scheduled_callback_data);
|
||||||
free(lboard_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;
|
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 */
|
/* don't actually submit leaderboard entries when spectating */
|
||||||
if (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) {
|
if (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) {
|
||||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectated %s (%d) for leaderboard %u: %s",
|
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);
|
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_ping_request_t api_params;
|
||||||
rc_api_request_t request;
|
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);
|
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);
|
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)
|
void rc_client_set_legacy_peek(rc_client_t* client, int method)
|
||||||
{
|
{
|
||||||
if (method == RC_CLIENT_LEGACY_PEEK_AUTO) {
|
if (method == RC_CLIENT_LEGACY_PEEK_AUTO) {
|
||||||
uint8_t buffer[4] = { 1,0,0,0 };
|
union {
|
||||||
method = (*((uint32_t*)buffer) == 1) ?
|
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;
|
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;
|
rc_client_event_t client_event;
|
||||||
memset(&client_event, 0, sizeof(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
|
else
|
||||||
game->progress_tracker.action = RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE;
|
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)
|
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;
|
scheduled_callback = client->state.scheduled_callbacks;
|
||||||
if (scheduled_callback) {
|
if (scheduled_callback) {
|
||||||
const clock_t now = clock();
|
const rc_clock_t now = client->callbacks.get_time_millisecs(client);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
rc_mutex_lock(&client->state.mutex);
|
rc_mutex_lock(&client->state.mutex);
|
||||||
scheduled_callback = client->state.scheduled_callbacks;
|
scheduled_callback = client->state.scheduled_callbacks;
|
||||||
if (scheduled_callback) {
|
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 */
|
/* not time for next callback yet, ignore it */
|
||||||
scheduled_callback = NULL;
|
scheduled_callback = NULL;
|
||||||
}
|
}
|
||||||
|
@ -4239,6 +4329,9 @@ void rc_client_idle(rc_client_t* client)
|
||||||
scheduled_callback->callback(scheduled_callback, client, now);
|
scheduled_callback->callback(scheduled_callback, client, now);
|
||||||
} while (1);
|
} 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)
|
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;
|
last = &client->state.scheduled_callbacks;
|
||||||
do {
|
do {
|
||||||
next = *last;
|
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;
|
scheduled_callback->next = next;
|
||||||
*last = scheduled_callback;
|
*last = scheduled_callback;
|
||||||
break;
|
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,
|
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** last;
|
||||||
rc_client_scheduled_callback_data_t* next;
|
rc_client_scheduled_callback_data_t* next;
|
||||||
|
@ -4290,7 +4383,7 @@ static void rc_client_reschedule_callback(rc_client_t* client,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RC_CLIENT_CLOCK_IS_BEFORE(when, next->next->when)) {
|
if (when < next->next->when) {
|
||||||
/* already in the correct place */
|
/* already in the correct place */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -4301,7 +4394,7 @@ static void rc_client_reschedule_callback(rc_client_t* client,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!next || RC_CLIENT_CLOCK_IS_BEFORE(when, next->when)) {
|
if (!next || when < next->when) {
|
||||||
/* insert here */
|
/* insert here */
|
||||||
callback->next = next;
|
callback->next = next;
|
||||||
*last = callback;
|
*last = callback;
|
||||||
|
|
|
@ -11,21 +11,31 @@ extern "C" {
|
||||||
#include "rc_runtime.h"
|
#include "rc_runtime.h"
|
||||||
#include "rc_runtime_types.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 {
|
typedef struct rc_client_callbacks_t {
|
||||||
rc_client_read_memory_func_t read_memory;
|
rc_client_read_memory_func_t read_memory;
|
||||||
rc_client_event_handler_t event_handler;
|
rc_client_event_handler_t event_handler;
|
||||||
rc_client_server_call_t server_call;
|
rc_client_server_call_t server_call;
|
||||||
rc_client_message_callback_t log_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;
|
void* client_data;
|
||||||
} rc_client_callbacks_t;
|
} rc_client_callbacks_t;
|
||||||
|
|
||||||
struct rc_client_scheduled_callback_data_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
|
typedef struct rc_client_scheduled_callback_data_t
|
||||||
{
|
{
|
||||||
clock_t when;
|
rc_clock_t when;
|
||||||
unsigned related_id;
|
unsigned related_id;
|
||||||
rc_client_scheduled_callback_t callback;
|
rc_client_scheduled_callback_t callback;
|
||||||
void* data;
|
void* data;
|
||||||
|
@ -211,6 +221,13 @@ enum {
|
||||||
RC_CLIENT_SPECTATOR_MODE_LOCKED
|
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;
|
struct rc_client_load_state_t;
|
||||||
|
|
||||||
typedef struct rc_client_state_t {
|
typedef struct rc_client_state_t {
|
||||||
|
@ -225,6 +242,7 @@ typedef struct rc_client_state_t {
|
||||||
uint8_t unofficial_enabled;
|
uint8_t unofficial_enabled;
|
||||||
uint8_t log_level;
|
uint8_t log_level;
|
||||||
uint8_t user;
|
uint8_t user;
|
||||||
|
uint8_t disconnect;
|
||||||
|
|
||||||
struct rc_client_load_state_t* load;
|
struct rc_client_load_state_t* load;
|
||||||
rc_memref_t* processing_memref;
|
rc_memref_t* processing_memref;
|
||||||
|
|
Loading…
Reference in a new issue