mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-05 05:37:15 +00:00
dep/rcheevos: Bump to 4570cb7
This commit is contained in:
@@ -135,29 +135,6 @@ typedef struct rc_api_achievement_definition_t {
|
||||
}
|
||||
rc_api_achievement_definition_t;
|
||||
|
||||
/* A game subset definition */
|
||||
typedef struct rc_api_subset_definition_t {
|
||||
/* The unique identifier of the subset */
|
||||
uint32_t id;
|
||||
/* The title of the subset */
|
||||
const char* title;
|
||||
/* The image name for the subset badge */
|
||||
const char* image_name;
|
||||
/* The URL for the subset badge */
|
||||
const char* image_url;
|
||||
|
||||
/* An array of achievements for the game */
|
||||
rc_api_achievement_definition_t* achievements;
|
||||
/* The number of items in the achievements array */
|
||||
uint32_t num_achievements;
|
||||
|
||||
/* An array of leaderboards for the game */
|
||||
rc_api_leaderboard_definition_t* leaderboards;
|
||||
/* The number of items in the leaderboards array */
|
||||
uint32_t num_leaderboards;
|
||||
}
|
||||
rc_api_subset_definition_t;
|
||||
|
||||
#define RC_ACHIEVEMENT_CATEGORY_CORE 3
|
||||
#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5
|
||||
|
||||
@@ -193,11 +170,6 @@ typedef struct rc_api_fetch_game_data_response_t {
|
||||
/* The number of items in the leaderboards array */
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
/* An array of subsets for the game */
|
||||
rc_api_subset_definition_t* subsets;
|
||||
/* The number of items in the subsets array */
|
||||
uint32_t num_subsets;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
@@ -210,6 +182,90 @@ RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
|
||||
|
||||
/* --- Fetch Game Sets --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch game data request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_game_sets_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* The generated hash of the game to be identified (ignored if game_id is not 0) */
|
||||
const char* game_hash;
|
||||
}
|
||||
rc_api_fetch_game_sets_request_t;
|
||||
|
||||
#define RC_ACHIEVEMENT_SET_TYPE_CORE 0
|
||||
#define RC_ACHIEVEMENT_SET_TYPE_BONUS 1
|
||||
#define RC_ACHIEVEMENT_SET_TYPE_SPECIALTY 2
|
||||
#define RC_ACHIEVEMENT_SET_TYPE_EXCLUSIVE 3
|
||||
|
||||
/* A game subset definition */
|
||||
typedef struct rc_api_achievement_set_definition_t {
|
||||
/* The unique identifier of the achievement set */
|
||||
uint32_t id;
|
||||
/* The legacy game_id of the achievement set (used for editor API calls) */
|
||||
uint32_t game_id;
|
||||
/* The title of the achievement set */
|
||||
const char* title;
|
||||
/* The image name for the achievement set badge */
|
||||
const char* image_name;
|
||||
/* The URL for the achievement set badge */
|
||||
const char* image_url;
|
||||
|
||||
/* An array of achievements for the achievement set */
|
||||
rc_api_achievement_definition_t* achievements;
|
||||
/* The number of items in the achievements array */
|
||||
uint32_t num_achievements;
|
||||
|
||||
/* An array of leaderboards for the achievement set */
|
||||
rc_api_leaderboard_definition_t* leaderboards;
|
||||
/* The number of items in the leaderboards array */
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
/* The type of the achievement set */
|
||||
uint8_t type;
|
||||
}
|
||||
rc_api_achievement_set_definition_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch game sets request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_game_sets_response_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t id;
|
||||
/* The console associated to the game */
|
||||
uint32_t console_id;
|
||||
/* The title of the game */
|
||||
const char* title;
|
||||
/* The image name for the game badge */
|
||||
const char* image_name;
|
||||
/* The URL for the game badge */
|
||||
const char* image_url;
|
||||
/* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */
|
||||
const char* rich_presence_script;
|
||||
/* The unique identifier of the game to use for session requests (startsession/ping/etc) */
|
||||
uint32_t session_game_id;
|
||||
|
||||
/* An array of sets for the game */
|
||||
rc_api_achievement_set_definition_t* sets;
|
||||
/* The number of items in the sets array */
|
||||
uint32_t num_sets;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_game_sets_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_sets_request(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_sets_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params, const rc_api_host_t* host);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_sets_server_response(rc_api_fetch_game_sets_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_sets_response(rc_api_fetch_game_sets_response_t* response);
|
||||
|
||||
/* --- Ping --- */
|
||||
|
||||
/**
|
||||
|
||||
@@ -217,8 +217,9 @@ typedef struct rc_client_user_game_summary_t {
|
||||
uint32_t points_core;
|
||||
uint32_t points_unlocked;
|
||||
|
||||
time_t beaten_time; /* 0 if not beaten, otherwise the time the game was beaten */
|
||||
time_t completed_time; /* 0 if not mastered, otherwise the time the game was mastered */
|
||||
/* minimum version: 12.1 */
|
||||
time_t beaten_time;
|
||||
time_t completed_time;
|
||||
} rc_client_user_game_summary_t;
|
||||
|
||||
/**
|
||||
@@ -272,6 +273,12 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
|
||||
uint32_t console_id, const char* file_path,
|
||||
const uint8_t* data, size_t data_size,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
struct rc_hash_callbacks;
|
||||
/**
|
||||
* Provide callback functions for interacting with the file system and processing disc-based files when generating hashes.
|
||||
*/
|
||||
RC_EXPORT void rc_client_set_hash_callbacks(rc_client_t* client, const struct rc_hash_callbacks* callbacks);
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -286,9 +293,9 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client
|
||||
RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client);
|
||||
enum {
|
||||
RC_CLIENT_LOAD_GAME_STATE_NONE,
|
||||
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
|
||||
RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN,
|
||||
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA,
|
||||
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
|
||||
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, /* [deprecated] - game data is now returned by identify call */
|
||||
RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION,
|
||||
RC_CLIENT_LOAD_GAME_STATE_DONE,
|
||||
RC_CLIENT_LOAD_GAME_STATE_ABORTED
|
||||
@@ -330,15 +337,17 @@ RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game
|
||||
/**
|
||||
* Changes the active disc in a multi-disc game.
|
||||
*/
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* file_path,
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_change_media(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Changes the active disc in a multi-disc game.
|
||||
*/
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* hash,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
/* this function was renamed in rcheevos 12.0 */
|
||||
#define rc_client_begin_change_media_from_hash rc_client_begin_change_media
|
||||
|
||||
/*****************************************************************************\
|
||||
| Subsets |
|
||||
@@ -358,6 +367,8 @@ typedef struct rc_client_subset_t {
|
||||
|
||||
RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Fetch Game Hashes |
|
||||
\*****************************************************************************/
|
||||
@@ -587,7 +598,7 @@ enum {
|
||||
RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
|
||||
|
||||
/**
|
||||
* Destroys a list allocated by rc_client_get_leaderboard_list.
|
||||
* Destroys a list allocated by rc_client_create_leaderboard_list.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
|
||||
|
||||
@@ -753,6 +764,11 @@ RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_clie
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler);
|
||||
|
||||
/**
|
||||
* Specifies whether rc_client is allowed to read memory outside of rc_client_do_frame/rc_client_idle.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_allow_background_memory_reads(rc_client_t* client, int allowed);
|
||||
|
||||
/**
|
||||
* Determines if there are any active achievements/leaderboards/rich presence that need processing.
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
* #endif
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
#if defined(__cplusplus) && !defined(CXX_BUILD)
|
||||
#define RC_BEGIN_C_DECLS extern "C" {
|
||||
#define RC_END_C_DECLS }
|
||||
#else
|
||||
|
||||
@@ -9,17 +9,19 @@
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
struct rc_hash_iterator;
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* specifies a function to call when an error occurs to display the error message */
|
||||
typedef void (RC_CCONV *rc_hash_message_callback)(const char*);
|
||||
typedef void (RC_CCONV *rc_hash_message_callback_deprecated)(const char*);
|
||||
|
||||
/* specifies a function to call when an error occurs to display the error message */
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback);
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback_deprecated callback);
|
||||
|
||||
/* specifies a function to call for verbose logging */
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
|
||||
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback_deprecated callback);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
@@ -54,6 +56,8 @@ RC_BEGIN_C_DECLS
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
#ifndef RC_HASH_NO_DISC
|
||||
|
||||
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
|
||||
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
|
||||
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */
|
||||
@@ -63,7 +67,7 @@ RC_BEGIN_C_DECLS
|
||||
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
|
||||
*/
|
||||
typedef void* (RC_CCONV *rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);
|
||||
typedef void* (RC_CCONV* rc_hash_cdreader_open_track_filereader_handler)(const char* path, uint32_t track, const rc_hash_filereader_t* filereader);
|
||||
typedef void* (RC_CCONV* rc_hash_cdreader_open_track_iterator_handler)(const char* path, uint32_t track, const struct rc_hash_iterator* iterator);
|
||||
|
||||
/* attempts to read the specified number of bytes from the file starting at the specified absolute sector.
|
||||
* returns the number of bytes actually read.
|
||||
@@ -82,7 +86,7 @@ RC_BEGIN_C_DECLS
|
||||
rc_hash_cdreader_read_sector_handler read_sector;
|
||||
rc_hash_cdreader_close_track_handler close_track;
|
||||
rc_hash_cdreader_first_track_sector_handler first_track_sector;
|
||||
rc_hash_cdreader_open_track_filereader_handler open_track_filereader;
|
||||
rc_hash_cdreader_open_track_iterator_handler open_track_iterator;
|
||||
} rc_hash_cdreader_t;
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader);
|
||||
@@ -91,6 +95,8 @@ RC_BEGIN_C_DECLS
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader);
|
||||
|
||||
#endif /* RC_HASH_NO_DISC */
|
||||
|
||||
#ifndef RC_HASH_NO_ENCRYPTED
|
||||
|
||||
/* specifies a function called to obtain a 3DS CIA decryption normal key.
|
||||
@@ -117,16 +123,20 @@ RC_BEGIN_C_DECLS
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func);
|
||||
|
||||
#endif
|
||||
#endif /* RC_HASH_NO_ENCRYPTED */
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
typedef void (RC_CCONV* rc_hash_message_callback_func)(const char*, const struct rc_hash_iterator* iterator);
|
||||
|
||||
typedef struct rc_hash_callbacks {
|
||||
rc_hash_message_callback verbose_message;
|
||||
rc_hash_message_callback error_message;
|
||||
rc_hash_message_callback_func verbose_message;
|
||||
rc_hash_message_callback_func error_message;
|
||||
|
||||
rc_hash_filereader_t filereader;
|
||||
#ifndef RC_HASH_NO_DISC
|
||||
rc_hash_cdreader_t cdreader;
|
||||
#endif
|
||||
|
||||
#ifndef RC_HASH_NO_ENCRYPTED
|
||||
struct rc_hash_encryption_callbacks {
|
||||
@@ -144,6 +154,7 @@ typedef struct rc_hash_iterator {
|
||||
uint8_t consoles[12];
|
||||
int index;
|
||||
const char* path;
|
||||
void* userdata;
|
||||
|
||||
rc_hash_callbacks_t callbacks;
|
||||
} rc_hash_iterator_t;
|
||||
|
||||
@@ -273,17 +273,17 @@ static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_
|
||||
iterator.json = server_response->body;
|
||||
iterator.end = server_response->body + server_response->body_length;
|
||||
|
||||
/* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */
|
||||
/* assume the title contains the most appropriate message to display to the user */
|
||||
if (rc_json_find_substring(&iterator, "<title>")) {
|
||||
const char* title_start = iterator.json + 7;
|
||||
if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "</title>")) {
|
||||
if (rc_json_find_substring(&iterator, "</title>")) {
|
||||
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start);
|
||||
response->succeeded = 0;
|
||||
return RC_INVALID_JSON;
|
||||
}
|
||||
}
|
||||
|
||||
/* title not found, or did not start with an error code, return the first line of the response */
|
||||
/* title not found, return the first line of the response (up to 200 characters) */
|
||||
iterator.json = server_response->body;
|
||||
|
||||
while (iterator.json < iterator.end && *iterator.json != '\n' &&
|
||||
@@ -317,6 +317,13 @@ static int rc_json_convert_error_code(const char* server_error_code)
|
||||
case 'i':
|
||||
if (strcmp(server_error_code, "invalid_credentials") == 0)
|
||||
return RC_INVALID_CREDENTIALS;
|
||||
if (strcmp(server_error_code, "invalid_parameter") == 0)
|
||||
return RC_INVALID_STATE;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
if (strcmp(server_error_code, "missing_parameter") == 0)
|
||||
return RC_INVALID_STATE;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
@@ -700,6 +707,57 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_json_field_string_matches(const rc_json_field_t* field, const char* text) {
|
||||
int is_quoted = 0;
|
||||
const char* ptr = field->value_start;
|
||||
if (!ptr)
|
||||
return 0;
|
||||
|
||||
if (*ptr == '"') {
|
||||
is_quoted = 1;
|
||||
++ptr;
|
||||
}
|
||||
|
||||
while (ptr < field->value_end) {
|
||||
if (*ptr != *text) {
|
||||
if (*ptr != '\\') {
|
||||
if (*ptr == '"' && is_quoted && (*text == '\0')) {
|
||||
is_quoted = 0;
|
||||
++ptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
++ptr;
|
||||
switch (*ptr) {
|
||||
case 'n':
|
||||
if (*text != '\n')
|
||||
return 0;
|
||||
break;
|
||||
case 'r':
|
||||
if (*text != '\r')
|
||||
return 0;
|
||||
break;
|
||||
case 't':
|
||||
if (*text != '\t')
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
if (*text != *ptr)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++text;
|
||||
++ptr;
|
||||
}
|
||||
|
||||
return !is_quoted && (*text == '\0');
|
||||
}
|
||||
|
||||
void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value) {
|
||||
if (!rc_json_get_string(out, &response->buffer, field, field_name))
|
||||
*out = default_value;
|
||||
|
||||
@@ -68,6 +68,7 @@ int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_fie
|
||||
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator);
|
||||
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
|
||||
int rc_json_get_object_string_length(const char* json);
|
||||
int rc_json_field_string_matches(const rc_json_field_t* field, const char* text);
|
||||
|
||||
void rc_json_extract_filename(rc_json_field_t* field);
|
||||
|
||||
|
||||
@@ -108,27 +108,13 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
||||
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
static int rc_parse_achievement_type(const char* type) {
|
||||
if (strcmp(type, "missable") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
|
||||
if (strcmp(type, "win_condition") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_WIN;
|
||||
|
||||
if (strcmp(type, "progression") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_PROGRESSION;
|
||||
|
||||
return RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
}
|
||||
|
||||
static int rc_api_process_fetch_game_data_achievements(rc_api_fetch_game_data_response_t* response, rc_api_achievement_definition_t* achievement, rc_json_field_t* array_field) {
|
||||
static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* response, rc_api_achievement_definition_t* achievement, rc_json_field_t* array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
const char* last_author = "";
|
||||
const char* last_author_field = "";
|
||||
size_t last_author_len = 0;
|
||||
uint32_t timet;
|
||||
size_t len;
|
||||
char type[16];
|
||||
|
||||
rc_json_field_t achievement_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
@@ -153,35 +139,35 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_fetch_game_data_re
|
||||
iterator.end = array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
|
||||
if (!rc_json_get_required_unum(&achievement->id, response, &achievement_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
|
||||
if (!rc_json_get_required_string(&achievement->title, response, &achievement_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
|
||||
if (!rc_json_get_required_string(&achievement->description, response, &achievement_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
|
||||
if (!rc_json_get_required_unum(&achievement->category, response, &achievement_fields[3], "Flags"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
|
||||
if (!rc_json_get_required_unum(&achievement->points, response, &achievement_fields[4], "Points"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
|
||||
if (!rc_json_get_required_string(&achievement->definition, response, &achievement_fields[5], "MemAddr"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
|
||||
if (!rc_json_get_required_string(&achievement->badge_name, response, &achievement_fields[7], "BadgeName"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_string(&achievement->badge_url, &response->response, &achievement_fields[13], "BadgeURL", "");
|
||||
rc_json_get_optional_string(&achievement->badge_url, response, &achievement_fields[13], "BadgeURL", "");
|
||||
if (!achievement->badge_url[0])
|
||||
achievement->badge_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_ACHIEVEMENT, achievement->badge_name);
|
||||
achievement->badge_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT, achievement->badge_name);
|
||||
|
||||
rc_json_get_optional_string(&achievement->badge_locked_url, &response->response, &achievement_fields[14], "BadgeLockedURL", "");
|
||||
rc_json_get_optional_string(&achievement->badge_locked_url, response, &achievement_fields[14], "BadgeLockedURL", "");
|
||||
if (!achievement->badge_locked_url[0])
|
||||
achievement->badge_locked_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, achievement->badge_name);
|
||||
achievement->badge_locked_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, achievement->badge_name);
|
||||
|
||||
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
|
||||
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
|
||||
achievement->author = last_author;
|
||||
}
|
||||
else {
|
||||
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
|
||||
if (!rc_json_get_required_string(&achievement->author, response, &achievement_fields[6], "Author"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (achievement->author == NULL) {
|
||||
@@ -195,22 +181,23 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_fetch_game_data_re
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
|
||||
if (!rc_json_get_required_unum(&timet, response, &achievement_fields[8], "Created"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->created = (time_t)timet;
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
|
||||
if (!rc_json_get_required_unum(&timet, response, &achievement_fields[9], "Modified"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->updated = (time_t)timet;
|
||||
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
if (achievement_fields[10].value_end) {
|
||||
len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2;
|
||||
if (len < sizeof(type) - 1) {
|
||||
memcpy(type, achievement_fields[10].value_start + 1, len);
|
||||
type[len] = '\0';
|
||||
achievement->type = rc_parse_achievement_type(type);
|
||||
}
|
||||
}
|
||||
if (rc_json_field_string_matches(&achievement_fields[10], ""))
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
else if (rc_json_field_string_matches(&achievement_fields[10], "progression"))
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_PROGRESSION;
|
||||
else if (rc_json_field_string_matches(&achievement_fields[10], "missable"))
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
else if (rc_json_field_string_matches(&achievement_fields[10], "win_condition"))
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_WIN;
|
||||
else
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
|
||||
/* legacy support : if title contains[m], change type to missable and remove[m] from title */
|
||||
if (memcmp(achievement->title, "[m]", 3) == 0) {
|
||||
@@ -237,7 +224,7 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_fetch_game_data_re
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_api_process_fetch_game_data_leaderboards(rc_api_fetch_game_data_response_t* response, rc_api_leaderboard_definition_t* leaderboard, rc_json_field_t* array_field) {
|
||||
static int rc_api_process_fetch_game_data_leaderboards(rc_api_response_t* response, rc_api_leaderboard_definition_t* leaderboard, rc_json_field_t* array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
size_t len;
|
||||
int result;
|
||||
@@ -258,13 +245,13 @@ static int rc_api_process_fetch_game_data_leaderboards(rc_api_fetch_game_data_re
|
||||
iterator.end = array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
|
||||
if (!rc_json_get_required_unum(&leaderboard->id, response, &leaderboard_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
|
||||
if (!rc_json_get_required_string(&leaderboard->title, response, &leaderboard_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
|
||||
if (!rc_json_get_required_string(&leaderboard->description, response, &leaderboard_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
|
||||
if (!rc_json_get_required_string(&leaderboard->definition, response, &leaderboard_fields[3], "Mem"))
|
||||
return RC_MISSING_VALUE;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0);
|
||||
leaderboard->lower_is_better = (uint8_t)result;
|
||||
@@ -289,81 +276,6 @@ static int rc_api_process_fetch_game_data_leaderboards(rc_api_fetch_game_data_re
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_api_process_fetch_game_data_subsets(rc_api_fetch_game_data_response_t* response, rc_api_subset_definition_t* subset, rc_json_field_t* subset_array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
size_t len;
|
||||
int result;
|
||||
|
||||
rc_json_field_t subset_fields[] = {
|
||||
RC_JSON_NEW_FIELD("GameAchievementSetID"),
|
||||
RC_JSON_NEW_FIELD("SetTitle"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("ImageIconURL"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards") /* array */
|
||||
};
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = subset_array_field->value_start;
|
||||
iterator.end = subset_array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(subset_fields, sizeof(subset_fields) / sizeof(subset_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&subset->id, &response->response, &subset_fields[0], "GameAchievementSetID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&subset->title, &response->response, &subset_fields[1], "SetTitle"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
rc_json_extract_filename(&subset_fields[2]);
|
||||
rc_json_get_optional_string(&subset->image_name, &response->response, &subset_fields[2], "ImageIcon", "");
|
||||
|
||||
if (!rc_json_get_required_string(&subset->image_url, &response->response, &subset_fields[3], "ImageIconURL"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* estimate the amount of space necessary to store the achievements, and leaderboards.
|
||||
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
|
||||
and add space for the structures. */
|
||||
len = (subset_fields[4].value_end - subset_fields[4].value_start) - /* achievements */
|
||||
subset_fields[4].array_size * (80 - sizeof(rc_api_achievement_definition_t));
|
||||
len += (subset_fields[5].value_end - subset_fields[5].value_start) - /* leaderboards */
|
||||
subset_fields[5].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
|
||||
|
||||
rc_buffer_reserve(&response->response.buffer, len);
|
||||
/* end estimation */
|
||||
|
||||
if (!rc_json_get_required_array(&subset->num_achievements, &array_field, &response->response, &subset_fields[4], "Achievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (subset->num_achievements) {
|
||||
subset->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, subset->num_achievements * sizeof(rc_api_achievement_definition_t));
|
||||
if (!subset->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_achievements(response, subset->achievements, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&subset->num_leaderboards, &array_field, &response->response, &subset_fields[5], "Leaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (subset->num_leaderboards) {
|
||||
subset->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, subset->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
|
||||
if (!subset->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_leaderboards(response, subset->leaderboards, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
++subset;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t array_field;
|
||||
size_t len;
|
||||
@@ -385,7 +297,6 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
RC_JSON_NEW_FIELD("RichPresencePatch"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards"), /* array */
|
||||
RC_JSON_NEW_FIELD("Sets") /* array */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
@@ -438,7 +349,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
if (!response->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_achievements(response, response->achievements, &array_field);
|
||||
result = rc_api_process_fetch_game_data_achievements(&response->response, response->achievements, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
@@ -451,18 +362,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
if (!response->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_leaderboards(response, response->leaderboards, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
rc_json_get_optional_array(&response->num_subsets, &array_field, &patchdata_fields[8], "Sets");
|
||||
if (response->num_subsets) {
|
||||
response->subsets = (rc_api_subset_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_subsets * sizeof(rc_api_subset_definition_t));
|
||||
if (!response->subsets)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_subsets(response, response->subsets, &array_field);
|
||||
result = rc_api_process_fetch_game_data_leaderboards(&response->response, response->leaderboards, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
@@ -474,6 +374,193 @@ void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t*
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Game Sets --- */
|
||||
|
||||
int rc_api_init_fetch_game_sets_request(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params) {
|
||||
return rc_api_init_fetch_game_sets_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_game_sets_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_game_sets_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (!api_params->game_id && (!api_params->game_hash || !api_params->game_hash[0]))
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "achievementsets", api_params->username, api_params->api_token)) {
|
||||
if (api_params->game_id)
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
else
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
static int rc_api_process_fetch_game_sets_achievement_sets(rc_api_fetch_game_sets_response_t* response,
|
||||
rc_api_achievement_set_definition_t* subset,
|
||||
rc_json_field_t* subset_array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
size_t len;
|
||||
int result;
|
||||
|
||||
rc_json_field_t subset_fields[] = {
|
||||
RC_JSON_NEW_FIELD("AchievementSetId"),
|
||||
RC_JSON_NEW_FIELD("GameId"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("Type"),
|
||||
RC_JSON_NEW_FIELD("ImageIconUrl"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards") /* array */
|
||||
};
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = subset_array_field->value_start;
|
||||
iterator.end = subset_array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(subset_fields, sizeof(subset_fields) / sizeof(subset_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&subset->id, &response->response, &subset_fields[0], "AchievementSetId"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&subset->game_id, &response->response, &subset_fields[1], "GameId"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_string(&subset->title, &response->response, &subset_fields[2], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!subset->title || !subset->title[0])
|
||||
subset->title = response->title;
|
||||
|
||||
if (rc_json_field_string_matches(&subset_fields[3], "core"))
|
||||
subset->type = RC_ACHIEVEMENT_SET_TYPE_CORE;
|
||||
else if (rc_json_field_string_matches(&subset_fields[3], "bonus"))
|
||||
subset->type = RC_ACHIEVEMENT_SET_TYPE_BONUS;
|
||||
else if (rc_json_field_string_matches(&subset_fields[3], "specialty"))
|
||||
subset->type = RC_ACHIEVEMENT_SET_TYPE_SPECIALTY;
|
||||
else if (rc_json_field_string_matches(&subset_fields[3], "exclusive"))
|
||||
subset->type = RC_ACHIEVEMENT_SET_TYPE_EXCLUSIVE;
|
||||
else
|
||||
subset->type = RC_ACHIEVEMENT_SET_TYPE_BONUS;
|
||||
|
||||
if (rc_json_field_string_matches(&subset_fields[4], response->image_url)) {
|
||||
subset->image_url = response->image_url;
|
||||
subset->image_name = response->image_name;
|
||||
}
|
||||
else {
|
||||
if (!rc_json_get_required_string(&subset->image_url, &response->response, &subset_fields[4], "ImageIconUrl"))
|
||||
return RC_MISSING_VALUE;
|
||||
rc_json_extract_filename(&subset_fields[4]);
|
||||
rc_json_get_optional_string(&subset->image_name, &response->response, &subset_fields[4], "ImageIconUrl", "");
|
||||
}
|
||||
|
||||
/* estimate the amount of space necessary to store the achievements, and leaderboards.
|
||||
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
|
||||
and add space for the structures. */
|
||||
len = (subset_fields[5].value_end - subset_fields[5].value_start) - /* achievements */
|
||||
subset_fields[5].array_size * (80 - sizeof(rc_api_achievement_definition_t));
|
||||
len += (subset_fields[6].value_end - subset_fields[6].value_start) - /* leaderboards */
|
||||
subset_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
|
||||
|
||||
rc_buffer_reserve(&response->response.buffer, len);
|
||||
/* end estimation */
|
||||
|
||||
if (!rc_json_get_required_array(&subset->num_achievements, &array_field, &response->response, &subset_fields[5], "Achievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (subset->num_achievements) {
|
||||
subset->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, subset->num_achievements * sizeof(rc_api_achievement_definition_t));
|
||||
if (!subset->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_achievements(&response->response, subset->achievements, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&subset->num_leaderboards, &array_field, &response->response, &subset_fields[6], "Leaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (subset->num_leaderboards) {
|
||||
subset->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, subset->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
|
||||
if (!subset->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_leaderboards(&response->response, subset->leaderboards, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
++subset;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_sets_server_response(rc_api_fetch_game_sets_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t array_field;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Code"),
|
||||
RC_JSON_NEW_FIELD("GameId"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ConsoleId"),
|
||||
RC_JSON_NEW_FIELD("ImageIconUrl"),
|
||||
RC_JSON_NEW_FIELD("RichPresenceGameId"),
|
||||
RC_JSON_NEW_FIELD("RichPresencePatch"),
|
||||
RC_JSON_NEW_FIELD("Sets") /* array */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
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_required_unum(&response->id, &response->response, &fields[3], "GameId"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->title, &response->response, &fields[4], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->console_id, &response->response, &fields[5], "ConsoleId"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_required_string(&response->image_url, &response->response, &fields[6], "ImageIconUrl");
|
||||
rc_json_extract_filename(&fields[6]);
|
||||
rc_json_get_required_string(&response->image_name, &response->response, &fields[6], "ImageIconUrl");
|
||||
|
||||
rc_json_get_optional_unum(&response->session_game_id, &fields[7], "RichPresenceGameId", response->id);
|
||||
|
||||
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &fields[8], "RichPresencePatch", "");
|
||||
if (!response->rich_presence_script)
|
||||
response->rich_presence_script = "";
|
||||
|
||||
rc_json_get_optional_array(&response->num_sets, &array_field, &fields[9], "Sets");
|
||||
if (response->num_sets) {
|
||||
response->sets = (rc_api_achievement_set_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_sets * sizeof(rc_api_achievement_set_definition_t));
|
||||
if (!response->sets)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_sets_achievement_sets(response, response->sets, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_game_sets_response(rc_api_fetch_game_sets_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Ping --- */
|
||||
|
||||
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) {
|
||||
|
||||
@@ -80,7 +80,7 @@ typedef struct rc_client_load_state_t
|
||||
} rc_client_load_state_t;
|
||||
|
||||
static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state);
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data);
|
||||
static void rc_client_begin_fetch_game_sets(rc_client_load_state_t* callback_data);
|
||||
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);
|
||||
@@ -172,6 +172,7 @@ rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function,
|
||||
|
||||
client->state.hardcore = 1;
|
||||
client->state.required_unpaused_frames = RC_MINIMUM_UNPAUSED_FRAMES;
|
||||
client->state.allow_background_memory_reads = 1;
|
||||
|
||||
client->callbacks.read_memory = read_memory_function;
|
||||
client->callbacks.server_call = server_call_function;
|
||||
@@ -227,14 +228,6 @@ void rc_client_destroy(rc_client_t* client)
|
||||
|
||||
/* ===== Logging ===== */
|
||||
|
||||
static rc_client_t* g_hash_client = NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
static void rc_client_log_hash_message(const char* message) {
|
||||
rc_client_log_message(g_hash_client, message);
|
||||
}
|
||||
#endif
|
||||
|
||||
void rc_client_log_message(const rc_client_t* client, const char* message)
|
||||
{
|
||||
if (client->callbacks.log_call)
|
||||
@@ -699,7 +692,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
|
||||
login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata);
|
||||
|
||||
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_begin_fetch_game_sets(load_state);
|
||||
}
|
||||
else {
|
||||
client->user.username = rc_buffer_strcpy(&client->state.buffer, login_response.username);
|
||||
@@ -723,7 +716,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name);
|
||||
|
||||
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_begin_fetch_game_sets(load_state);
|
||||
|
||||
if (login_callback_data->callback)
|
||||
login_callback_data->callback(RC_OK, NULL, client, login_callback_data->callback_userdata);
|
||||
@@ -918,51 +911,53 @@ int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], si
|
||||
return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_USER, user->display_name);
|
||||
}
|
||||
|
||||
static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t* subset,
|
||||
rc_client_user_game_summary_t* summary, const uint8_t unlock_bit)
|
||||
static void rc_client_subset_get_user_game_summary(const rc_client_t* client,
|
||||
const rc_client_subset_info_t* subset, rc_client_user_game_summary_t* summary)
|
||||
{
|
||||
rc_client_achievement_info_t* achievement = subset->achievements;
|
||||
rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements;
|
||||
uint32_t num_progression_achievements = 0, unlocked_progression_achievements = 0;
|
||||
uint32_t num_win_condition_achievements = 0;
|
||||
time_t last_progression_unlock = 0, first_win_condition_unlock = 0;
|
||||
time_t last_achievement_unlock = 0;
|
||||
time_t last_unlock_time = 0;
|
||||
time_t last_progression_time = 0;
|
||||
time_t first_win_time = 0;
|
||||
int num_progression_achievements = 0;
|
||||
int num_win_achievements = 0;
|
||||
int num_unlocked_progression_achievements = 0;
|
||||
const uint8_t unlock_bit = (client->state.hardcore) ?
|
||||
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
|
||||
|
||||
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
|
||||
|
||||
for (; achievement < stop; ++achievement) {
|
||||
switch (achievement->public_.category) {
|
||||
case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE:
|
||||
++summary->num_core_achievements;
|
||||
summary->points_core += achievement->public_.points;
|
||||
|
||||
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION)
|
||||
++num_progression_achievements;
|
||||
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN)
|
||||
++num_win_condition_achievements;
|
||||
|
||||
if (achievement->public_.unlocked & unlock_bit) {
|
||||
++summary->num_unlocked_achievements;
|
||||
summary->points_unlocked += achievement->public_.points;
|
||||
|
||||
last_achievement_unlock = (achievement->public_.unlock_time > last_achievement_unlock) ?
|
||||
achievement->public_.unlock_time :
|
||||
last_achievement_unlock;
|
||||
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION)
|
||||
{
|
||||
++unlocked_progression_achievements;
|
||||
last_progression_unlock = (achievement->public_.unlock_time > last_progression_unlock) ?
|
||||
achievement->public_.unlock_time :
|
||||
last_progression_unlock;
|
||||
if (achievement->public_.unlock_time > last_unlock_time)
|
||||
last_unlock_time = achievement->public_.unlock_time;
|
||||
|
||||
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION) {
|
||||
++num_unlocked_progression_achievements;
|
||||
if (achievement->public_.unlock_time > last_progression_time)
|
||||
last_progression_time = achievement->public_.unlock_time;
|
||||
}
|
||||
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN)
|
||||
{
|
||||
if (first_win_condition_unlock == 0 ||
|
||||
(achievement->public_.unlock_time > 0 && achievement->public_.unlock_time < first_win_condition_unlock))
|
||||
first_win_condition_unlock = achievement->public_.unlock_time;
|
||||
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN) {
|
||||
if (first_win_time == 0 || achievement->public_.unlock_time < first_win_time)
|
||||
first_win_time = achievement->public_.unlock_time;
|
||||
}
|
||||
}
|
||||
|
||||
if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) {
|
||||
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION)
|
||||
++num_progression_achievements;
|
||||
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN)
|
||||
++num_win_achievements;
|
||||
|
||||
if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED)
|
||||
++summary->num_unsupported_achievements;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -975,25 +970,17 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
|
||||
}
|
||||
}
|
||||
|
||||
/* Game considered beaten when all progression achievements are unlocked and any win condition achievement
|
||||
* is unlocked, or all progression achievements are unlocked and there are no win condition achievements. */
|
||||
summary->beaten_time = 0;
|
||||
if (unlocked_progression_achievements == num_progression_achievements &&
|
||||
(num_win_condition_achievements == 0 || first_win_condition_unlock > 0)) {
|
||||
summary->beaten_time = (num_win_condition_achievements == 0) ? last_progression_unlock : first_win_condition_unlock;
|
||||
}
|
||||
rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
|
||||
|
||||
/* Game considered completed when all achievements are unlocked */
|
||||
summary->completed_time = 0;
|
||||
if (summary->num_unlocked_achievements == summary->num_core_achievements)
|
||||
summary->completed_time = last_achievement_unlock;
|
||||
summary->completed_time = last_unlock_time;
|
||||
|
||||
if ((first_win_time || num_win_achievements == 0) && num_unlocked_progression_achievements == num_progression_achievements)
|
||||
summary->beaten_time = (first_win_time > last_progression_time) ? first_win_time : last_progression_time;
|
||||
}
|
||||
|
||||
void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary)
|
||||
{
|
||||
const uint8_t unlock_bit = (client->state.hardcore) ?
|
||||
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
|
||||
|
||||
if (!summary)
|
||||
return;
|
||||
|
||||
@@ -1002,20 +989,47 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
|
||||
return;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->get_user_game_summary) {
|
||||
client->state.external_client->get_user_game_summary(summary);
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->get_user_game_summary_v5) {
|
||||
client->state.external_client->get_user_game_summary_v5(summary);
|
||||
return;
|
||||
}
|
||||
if (client->state.external_client->get_user_game_summary) {
|
||||
client->state.external_client->get_user_game_summary(summary);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rc_client_is_game_loaded(client))
|
||||
rc_client_subset_get_user_game_summary(client, client->game->subsets, summary);
|
||||
}
|
||||
|
||||
void rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary)
|
||||
{
|
||||
if (!summary)
|
||||
return;
|
||||
|
||||
memset(summary, 0, sizeof(*summary));
|
||||
if (!client || !subset_id)
|
||||
return;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->get_user_subset_summary) {
|
||||
client->state.external_client->get_user_subset_summary(subset_id, summary);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!rc_client_is_game_loaded(client))
|
||||
return;
|
||||
|
||||
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
|
||||
|
||||
rc_client_subset_get_user_game_summary(client->game->subsets, summary, unlock_bit);
|
||||
|
||||
rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
|
||||
if (rc_client_is_game_loaded(client)) {
|
||||
const rc_client_subset_info_t* subset = client->game->subsets;
|
||||
for (; subset; subset = subset->next) {
|
||||
if (subset->public_.id == subset_id) {
|
||||
rc_client_subset_get_user_game_summary(client, subset, summary);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct rc_client_fetch_all_user_progress_callback_data_t {
|
||||
@@ -1159,6 +1173,10 @@ static void rc_client_free_load_state(rc_client_load_state_t* load_state)
|
||||
free(load_state->start_session_response);
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
rc_hash_destroy_iterator(&load_state->hash_iterator);
|
||||
#endif
|
||||
|
||||
free(load_state);
|
||||
}
|
||||
|
||||
@@ -1425,29 +1443,34 @@ static uint32_t rc_client_subset_toggle_hardcore_achievements(rc_client_subset_i
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
|
||||
achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) {
|
||||
|
||||
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
|
||||
if (client->state.encore_mode) {
|
||||
++active_count;
|
||||
continue;
|
||||
}
|
||||
|
||||
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
|
||||
else {
|
||||
achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ?
|
||||
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
|
||||
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
|
||||
|
||||
if (achievement->trigger && achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) {
|
||||
rc_client_event_t client_event;
|
||||
memset(&client_event, 0, sizeof(client_event));
|
||||
client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE;
|
||||
client_event.achievement = &achievement->public_;
|
||||
client->callbacks.event_handler(&client_event, client);
|
||||
if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
|
||||
achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) {
|
||||
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
|
||||
if (client->state.encore_mode) {
|
||||
++active_count;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* switch to inactive */
|
||||
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
|
||||
|
||||
if (achievement->trigger && rc_trigger_state_active(achievement->trigger->state)) {
|
||||
/* hide any active challenge indicators */
|
||||
if (achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) {
|
||||
rc_client_event_t client_event;
|
||||
memset(&client_event, 0, sizeof(client_event));
|
||||
client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE;
|
||||
client_event.achievement = &achievement->public_;
|
||||
client->callbacks.event_handler(&client_event, client);
|
||||
}
|
||||
|
||||
achievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
|
||||
}
|
||||
}
|
||||
|
||||
if (achievement->trigger && rc_trigger_state_active(achievement->trigger->state))
|
||||
achievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1669,6 +1692,11 @@ static void rc_client_free_pending_media(rc_client_pending_media_t* pending_medi
|
||||
free(pending_media);
|
||||
}
|
||||
|
||||
/* NOTE: address validation uses the read_memory callback to make sure the client
|
||||
* will return data for the requested address. As such, this function must
|
||||
* respect the `client->state.allow_background_memory_reads setting. Use
|
||||
* rc_client_queue_activate_game to dispatch this function to the do_frame loop/
|
||||
*/
|
||||
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;
|
||||
@@ -1718,11 +1746,11 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
|
||||
* client->state.load->game. since we've detached the load_state, this has to occur after
|
||||
* we've made the game active. */
|
||||
if (pending_media->hash) {
|
||||
rc_client_begin_change_media_from_hash(client, pending_media->hash,
|
||||
rc_client_begin_change_media(client, pending_media->hash,
|
||||
pending_media->callback, pending_media->callback_userdata);
|
||||
} else {
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
rc_client_begin_change_media(client, pending_media->file_path,
|
||||
rc_client_begin_identify_and_change_media(client, pending_media->file_path,
|
||||
pending_media->data, pending_media->data_size,
|
||||
pending_media->callback, pending_media->callback_userdata);
|
||||
#endif
|
||||
@@ -1739,11 +1767,6 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
|
||||
/* if the game is still being loaded, make sure all the required memory addresses are accessible
|
||||
* so we can mark achievements as unsupported before loading them into the runtime. */
|
||||
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
|
||||
/* TODO: it is desirable to not do memory reads from a background thread. Some emulators (like Dolphin) don't
|
||||
* allow it. Dolphin's solution is to use a dummy read function that says all addresses are valid and
|
||||
* switches to the actual read function after the callback is called. latter invalid reads will
|
||||
* mark achievements as unsupported. */
|
||||
|
||||
/* ASSERT: client->game must be set before calling this function so the read_memory callback can query the console_id */
|
||||
rc_client_validate_addresses(load_state->game, client);
|
||||
|
||||
@@ -1777,7 +1800,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
|
||||
if (load_state->hash->hash[0] != '[') {
|
||||
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) {
|
||||
/* schedule the periodic ping */
|
||||
rc_client_scheduled_callback_data_t* callback_data = rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t));
|
||||
rc_client_scheduled_callback_data_t* callback_data = (rc_client_scheduled_callback_data_t*)
|
||||
rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t));
|
||||
|
||||
memset(callback_data, 0, sizeof(*callback_data));
|
||||
callback_data->callback = rc_client_ping;
|
||||
callback_data->related_id = load_state->game->public_.id;
|
||||
@@ -1804,6 +1829,33 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
|
||||
rc_client_free_load_state(load_state);
|
||||
}
|
||||
|
||||
static void rc_client_dispatch_activate_game(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data->data;
|
||||
free(callback_data);
|
||||
|
||||
(void)client;
|
||||
(void)now;
|
||||
|
||||
rc_client_activate_game(load_state, load_state->start_session_response);
|
||||
}
|
||||
|
||||
static void rc_client_queue_activate_game(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_client_scheduled_callback_data_t* scheduled_callback_data =
|
||||
(rc_client_scheduled_callback_data_t*)calloc(1, sizeof(rc_client_scheduled_callback_data_t));
|
||||
|
||||
if (!scheduled_callback_data) {
|
||||
rc_client_load_error(load_state, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
|
||||
return;
|
||||
}
|
||||
|
||||
scheduled_callback_data->callback = rc_client_dispatch_activate_game;
|
||||
scheduled_callback_data->data = load_state;
|
||||
|
||||
rc_client_schedule_callback(load_state->client, scheduled_callback_data);
|
||||
}
|
||||
|
||||
static void rc_client_start_session_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data;
|
||||
@@ -1829,12 +1881,12 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
|
||||
outstanding_requests = rc_client_end_load_state(load_state);
|
||||
|
||||
if (error_message) {
|
||||
rc_client_load_error(callback_data, result, error_message);
|
||||
rc_client_load_error(load_state, result, error_message);
|
||||
}
|
||||
else if (outstanding_requests < 0) {
|
||||
/* previous load state was aborted, load_state was free'd */
|
||||
}
|
||||
else if (outstanding_requests == 0) {
|
||||
else if (outstanding_requests == 0 && load_state->client->state.allow_background_memory_reads) {
|
||||
rc_client_activate_game(load_state, &start_session_response);
|
||||
}
|
||||
else {
|
||||
@@ -1842,12 +1894,19 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
|
||||
(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));
|
||||
rc_client_load_error(load_state, 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);
|
||||
}
|
||||
|
||||
if (outstanding_requests == 0) {
|
||||
if (load_state->client->state.allow_background_memory_reads)
|
||||
rc_client_activate_game(load_state, load_state->start_session_response);
|
||||
else
|
||||
rc_client_queue_activate_game(load_state);
|
||||
}
|
||||
}
|
||||
|
||||
rc_api_destroy_start_session_response(&start_session_response);
|
||||
@@ -1928,7 +1987,7 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
|
||||
|
||||
/* allocate the achievement array */
|
||||
size = sizeof(rc_client_achievement_info_t) * num_achievements;
|
||||
achievement = achievements = rc_buffer_alloc(buffer, size);
|
||||
achievement = achievements = (rc_client_achievement_info_t*)rc_buffer_alloc(buffer, size);
|
||||
memset(achievements, 0, size);
|
||||
|
||||
/* copy the achievement data */
|
||||
@@ -1977,7 +2036,7 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
|
||||
achievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
|
||||
}
|
||||
else {
|
||||
rc_buffer_consume(buffer, preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
|
||||
rc_buffer_consume(buffer, (const uint8_t*)preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
|
||||
}
|
||||
|
||||
rc_destroy_preparse_state(&preparse);
|
||||
@@ -2070,7 +2129,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
|
||||
/* allocate the achievement array */
|
||||
size = sizeof(rc_client_leaderboard_info_t) * num_leaderboards;
|
||||
buffer = &load_state->game->buffer;
|
||||
leaderboard = leaderboards = rc_buffer_alloc(buffer, size);
|
||||
leaderboard = leaderboards = (rc_client_leaderboard_info_t*)rc_buffer_alloc(buffer, size);
|
||||
memset(leaderboards, 0, size);
|
||||
|
||||
/* copy the achievement data */
|
||||
@@ -2120,7 +2179,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
|
||||
leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED;
|
||||
}
|
||||
else {
|
||||
rc_buffer_consume(buffer, preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
|
||||
rc_buffer_consume(buffer, (const uint8_t*)preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
|
||||
}
|
||||
|
||||
rc_destroy_preparse_state(&preparse);
|
||||
@@ -2133,10 +2192,10 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
|
||||
subset->leaderboards = leaderboards;
|
||||
}
|
||||
|
||||
static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
static void rc_client_fetch_game_sets_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data;
|
||||
rc_api_fetch_game_data_response_t fetch_game_data_response;
|
||||
rc_api_fetch_game_sets_response_t fetch_game_sets_response;
|
||||
int outstanding_requests;
|
||||
const char* error_message;
|
||||
int result;
|
||||
@@ -2153,8 +2212,8 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
||||
return;
|
||||
}
|
||||
|
||||
result = rc_api_process_fetch_game_data_server_response(&fetch_game_data_response, server_response);
|
||||
error_message = rc_client_server_error_message(&result, server_response->http_status_code, &fetch_game_data_response.response);
|
||||
result = rc_api_process_fetch_game_sets_server_response(&fetch_game_sets_response, server_response);
|
||||
error_message = rc_client_server_error_message(&result, server_response->http_status_code, &fetch_game_sets_response.response);
|
||||
|
||||
outstanding_requests = rc_client_end_load_state(load_state);
|
||||
|
||||
@@ -2164,18 +2223,18 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
||||
else if (outstanding_requests < 0) {
|
||||
/* previous load state was aborted, load_state was free'd */
|
||||
}
|
||||
else if (fetch_game_data_response.id == 0) {
|
||||
else if (fetch_game_sets_response.id == 0) {
|
||||
load_state->hash->game_id = 0;
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
else {
|
||||
rc_client_subset_info_t** next_subset;
|
||||
rc_client_subset_info_t* core_subset;
|
||||
uint32_t subset_index;
|
||||
rc_client_subset_info_t* first_subset = NULL;
|
||||
uint32_t set_index;
|
||||
|
||||
/* hash exists outside the load state - always update it */
|
||||
load_state->hash->game_id = fetch_game_data_response.id;
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Identified game: %u \"%s\" (%s)", load_state->hash->game_id, fetch_game_data_response.title, load_state->hash->hash);
|
||||
load_state->hash->game_id = fetch_game_sets_response.id;
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Identified game: %u \"%s\" (%s)", load_state->hash->game_id, fetch_game_sets_response.title, load_state->hash->hash);
|
||||
|
||||
if (load_state->hash->hash[0] != '[') {
|
||||
/* not [NO HASH] or [SUBSETxx] */
|
||||
@@ -2183,18 +2242,10 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
||||
load_state->game->public_.hash = load_state->hash->hash;
|
||||
}
|
||||
|
||||
core_subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(core_subset, 0, sizeof(*core_subset));
|
||||
core_subset->public_.id = fetch_game_data_response.id;
|
||||
core_subset->active = 1;
|
||||
snprintf(core_subset->public_.badge_name, sizeof(core_subset->public_.badge_name), "%s", fetch_game_data_response.image_name);
|
||||
core_subset->public_.badge_url = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.image_url);
|
||||
load_state->subset = core_subset;
|
||||
|
||||
if (load_state->game->public_.console_id != RC_CONSOLE_UNKNOWN &&
|
||||
fetch_game_data_response.console_id != load_state->game->public_.console_id) {
|
||||
fetch_game_sets_response.console_id != load_state->game->public_.console_id) {
|
||||
RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Data for game %u is for console %u, expecting console %u",
|
||||
fetch_game_data_response.id, fetch_game_data_response.console_id, load_state->game->public_.console_id);
|
||||
fetch_game_sets_response.id, fetch_game_sets_response.console_id, load_state->game->public_.console_id);
|
||||
}
|
||||
|
||||
/* kick off the start session request while we process the game data */
|
||||
@@ -2208,65 +2259,74 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
||||
}
|
||||
|
||||
/* process the game data */
|
||||
rc_client_copy_achievements(load_state, core_subset,
|
||||
fetch_game_data_response.achievements, fetch_game_data_response.num_achievements);
|
||||
rc_client_copy_leaderboards(load_state, core_subset,
|
||||
fetch_game_data_response.leaderboards, fetch_game_data_response.num_leaderboards);
|
||||
|
||||
/* core set */
|
||||
rc_mutex_lock(&load_state->client->state.mutex);
|
||||
load_state->game->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.title);
|
||||
load_state->game->subsets = core_subset;
|
||||
load_state->game->public_.badge_name = core_subset->public_.badge_name;
|
||||
load_state->game->public_.badge_url = core_subset->public_.badge_url;
|
||||
load_state->game->public_.console_id = fetch_game_data_response.console_id;
|
||||
rc_mutex_unlock(&load_state->client->state.mutex);
|
||||
|
||||
core_subset->public_.title = load_state->game->public_.title;
|
||||
|
||||
if (fetch_game_data_response.rich_presence_script && fetch_game_data_response.rich_presence_script[0]) {
|
||||
result = rc_runtime_activate_richpresence(&load_state->game->runtime, fetch_game_data_response.rich_presence_script, NULL, 0);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing rich presence", result);
|
||||
}
|
||||
}
|
||||
|
||||
next_subset = &core_subset->next;
|
||||
for (subset_index = 0; subset_index < fetch_game_data_response.num_subsets; ++subset_index) {
|
||||
rc_api_subset_definition_t* api_subset = &fetch_game_data_response.subsets[subset_index];
|
||||
next_subset = &first_subset;
|
||||
for (set_index = 0; set_index < fetch_game_sets_response.num_sets; ++set_index) {
|
||||
rc_api_achievement_set_definition_t* set = &fetch_game_sets_response.sets[set_index];
|
||||
rc_client_subset_info_t* subset;
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.id = api_subset->id;
|
||||
subset->public_.id = set->id;
|
||||
subset->active = 1;
|
||||
snprintf(subset->public_.badge_name, sizeof(subset->public_.badge_name), "%s", api_subset->image_name);
|
||||
subset->public_.badge_url = rc_buffer_strcpy(&load_state->game->buffer, api_subset->image_url);
|
||||
subset->public_.title = rc_buffer_strcpy(&load_state->game->buffer, api_subset->title);
|
||||
snprintf(subset->public_.badge_name, sizeof(subset->public_.badge_name), "%s", set->image_name);
|
||||
subset->public_.badge_url = rc_buffer_strcpy(&load_state->game->buffer, set->image_url);
|
||||
subset->public_.title = rc_buffer_strcpy(&load_state->game->buffer, set->title);
|
||||
|
||||
rc_client_copy_achievements(load_state, subset, api_subset->achievements, api_subset->num_achievements);
|
||||
rc_client_copy_leaderboards(load_state, subset, api_subset->leaderboards, api_subset->num_leaderboards);
|
||||
rc_client_copy_achievements(load_state, subset, set->achievements, set->num_achievements);
|
||||
rc_client_copy_leaderboards(load_state, subset, set->leaderboards, set->num_leaderboards);
|
||||
|
||||
*next_subset = subset;
|
||||
next_subset = &subset->next;
|
||||
if (set->type == RC_ACHIEVEMENT_SET_TYPE_CORE) {
|
||||
if (!first_subset)
|
||||
next_subset = &subset->next;
|
||||
subset->next = first_subset;
|
||||
first_subset = subset;
|
||||
}
|
||||
else {
|
||||
*next_subset = subset;
|
||||
next_subset = &subset->next;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (!first_subset) {
|
||||
rc_client_load_error(load_state, RC_NOT_FOUND, "Response contained no sets");
|
||||
} else {
|
||||
load_state->subset = first_subset;
|
||||
|
||||
/* core set */
|
||||
rc_mutex_lock(&load_state->client->state.mutex);
|
||||
load_state->game->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_sets_response.title);
|
||||
load_state->game->subsets = first_subset;
|
||||
load_state->game->public_.badge_name = first_subset->public_.badge_name;
|
||||
load_state->game->public_.badge_url = first_subset->public_.badge_url;
|
||||
load_state->game->public_.console_id = fetch_game_sets_response.console_id;
|
||||
rc_mutex_unlock(&load_state->client->state.mutex);
|
||||
|
||||
if (fetch_game_sets_response.rich_presence_script && fetch_game_sets_response.rich_presence_script[0]) {
|
||||
result = rc_runtime_activate_richpresence(&load_state->game->runtime, fetch_game_sets_response.rich_presence_script, NULL, 0);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing rich presence", result);
|
||||
}
|
||||
}
|
||||
|
||||
if (load_state->client->callbacks.post_process_game_sets_response) {
|
||||
load_state->client->callbacks.post_process_game_sets_response(server_response,
|
||||
&fetch_game_sets_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)
|
||||
else if (outstanding_requests == 0) {
|
||||
if (load_state->client->state.allow_background_memory_reads)
|
||||
rc_client_activate_game(load_state, load_state->start_session_response);
|
||||
else
|
||||
rc_client_queue_activate_game(load_state);
|
||||
}
|
||||
}
|
||||
|
||||
rc_api_destroy_fetch_game_data_response(&fetch_game_data_response);
|
||||
rc_api_destroy_fetch_game_sets_response(&fetch_game_sets_response);
|
||||
}
|
||||
|
||||
static rc_client_game_info_t* rc_client_allocate_game(void)
|
||||
@@ -2438,6 +2498,8 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc_hash_destroy_iterator(&load_state->hash_iterator); /* done with this now */
|
||||
#else
|
||||
load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN;
|
||||
load_state->game->public_.hash = load_state->hash->hash;
|
||||
@@ -2479,9 +2541,6 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
||||
load_state->game->public_.hash = load_state->hash->hash;
|
||||
}
|
||||
|
||||
/* done with the hashing code, release the global pointer */
|
||||
g_hash_client = NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->add_game_hash)
|
||||
@@ -2495,7 +2554,7 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
||||
}
|
||||
#endif
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_begin_fetch_game_sets(load_state);
|
||||
}
|
||||
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* tried_hashes)
|
||||
@@ -2522,9 +2581,9 @@ void rc_client_load_unknown_game(rc_client_t* client, const char* tried_hashes)
|
||||
client->game = game;
|
||||
}
|
||||
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
static void rc_client_begin_fetch_game_sets(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_api_fetch_game_data_request_t fetch_game_data_request;
|
||||
rc_api_fetch_game_sets_request_t fetch_game_sets_request;
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
@@ -2533,6 +2592,8 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
result = client->state.user;
|
||||
if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
|
||||
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN;
|
||||
else
|
||||
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA;
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
switch (result) {
|
||||
@@ -2548,26 +2609,26 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&fetch_game_data_request, 0, sizeof(fetch_game_data_request));
|
||||
fetch_game_data_request.username = client->user.username;
|
||||
fetch_game_data_request.api_token = client->user.token;
|
||||
memset(&fetch_game_sets_request, 0, sizeof(fetch_game_sets_request));
|
||||
fetch_game_sets_request.username = client->user.username;
|
||||
fetch_game_sets_request.api_token = client->user.token;
|
||||
|
||||
if (load_state->hash->is_unknown) /* lookup failed, but client provided a mapping */
|
||||
fetch_game_data_request.game_id = load_state->hash->game_id;
|
||||
fetch_game_sets_request.game_id = load_state->hash->game_id;
|
||||
else
|
||||
fetch_game_data_request.game_hash = load_state->hash->hash;
|
||||
fetch_game_sets_request.game_hash = load_state->hash->hash;
|
||||
|
||||
result = rc_api_init_fetch_game_data_request_hosted(&request, &fetch_game_data_request, &client->state.host);
|
||||
result = rc_api_init_fetch_game_sets_request_hosted(&request, &fetch_game_sets_request, &client->state.host);
|
||||
if (result != RC_OK) {
|
||||
rc_client_load_error(load_state, result, rc_error_str(result));
|
||||
return;
|
||||
}
|
||||
|
||||
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1);
|
||||
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for hash %s", fetch_game_data_request.game_hash);
|
||||
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for hash %s", fetch_game_sets_request.game_hash);
|
||||
|
||||
rc_client_begin_async(client, &load_state->async_handle);
|
||||
client->callbacks.server_call(&request, rc_client_fetch_game_data_callback, load_state, client);
|
||||
client->callbacks.server_call(&request, rc_client_fetch_game_sets_callback, load_state, client);
|
||||
|
||||
rc_api_destroy_request(&request);
|
||||
}
|
||||
@@ -2633,7 +2694,7 @@ rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char*
|
||||
}
|
||||
|
||||
if (!game_hash) {
|
||||
game_hash = rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_game_hash_t));
|
||||
game_hash = (rc_client_game_hash_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_game_hash_t));
|
||||
memset(game_hash, 0, sizeof(*game_hash));
|
||||
snprintf(game_hash->hash, sizeof(game_hash->hash), "%s", hash);
|
||||
game_hash->game_id = RC_CLIENT_UNKNOWN_GAME_ID;
|
||||
@@ -2739,7 +2800,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_begin_fetch_game_sets(load_state);
|
||||
}
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
@@ -2809,6 +2870,30 @@ rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void rc_client_log_hash_message_verbose(const char* message, const rc_hash_iterator_t* iterator)
|
||||
{
|
||||
const rc_client_load_state_t* load_state = (const rc_client_load_state_t*)iterator->userdata;
|
||||
if (load_state->client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO)
|
||||
rc_client_log_message(load_state->client, message);
|
||||
}
|
||||
|
||||
static void rc_client_log_hash_message_error(const char* message, const rc_hash_iterator_t* iterator)
|
||||
{
|
||||
const rc_client_load_state_t* load_state = (const rc_client_load_state_t*)iterator->userdata;
|
||||
if (load_state->client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR)
|
||||
rc_client_log_message(load_state->client, message);
|
||||
}
|
||||
|
||||
void rc_client_set_hash_callbacks(rc_client_t* client, const struct rc_hash_callbacks* callbacks)
|
||||
{
|
||||
memcpy(&client->callbacks.hash, callbacks, sizeof(*callbacks));
|
||||
|
||||
if (!callbacks->verbose_message)
|
||||
client->callbacks.hash.verbose_message = rc_client_log_hash_message_verbose;
|
||||
if (!callbacks->error_message)
|
||||
client->callbacks.hash.error_message = rc_client_log_hash_message_error;
|
||||
}
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client,
|
||||
uint32_t console_id, const char* file_path,
|
||||
const uint8_t* data, size_t data_size,
|
||||
@@ -2849,12 +2934,6 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) {
|
||||
g_hash_client = client;
|
||||
rc_hash_init_error_message_callback(rc_client_log_hash_message);
|
||||
rc_hash_init_verbose_message_callback(rc_client_log_hash_message);
|
||||
}
|
||||
|
||||
if (!file_path)
|
||||
file_path = "?";
|
||||
|
||||
@@ -2867,9 +2946,18 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
|
||||
load_state->callback = callback;
|
||||
load_state->callback_userdata = callback_userdata;
|
||||
|
||||
if (console_id == RC_CONSOLE_UNKNOWN) {
|
||||
rc_hash_initialize_iterator(&load_state->hash_iterator, file_path, data, data_size);
|
||||
/* initialize the iterator */
|
||||
rc_hash_initialize_iterator(&load_state->hash_iterator, file_path, data, data_size);
|
||||
rc_hash_merge_callbacks(&load_state->hash_iterator, &client->callbacks.hash);
|
||||
load_state->hash_iterator.userdata = load_state;
|
||||
|
||||
if (!load_state->hash_iterator.callbacks.verbose_message)
|
||||
load_state->hash_iterator.callbacks.verbose_message = rc_client_log_hash_message_verbose;
|
||||
if (!load_state->hash_iterator.callbacks.error_message)
|
||||
load_state->hash_iterator.callbacks.error_message = rc_client_log_hash_message_error;
|
||||
|
||||
/* calculate the hash */
|
||||
if (console_id == RC_CONSOLE_UNKNOWN) {
|
||||
if (!rc_hash_iterate(hash, &load_state->hash_iterator)) {
|
||||
rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed");
|
||||
return NULL;
|
||||
@@ -2881,17 +2969,12 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
|
||||
/* ASSERT: hash_iterator->index and hash_iterator->consoles[0] will be 0 from calloc */
|
||||
load_state->hash_console_id = console_id;
|
||||
|
||||
if (data != NULL) {
|
||||
if (!rc_hash_generate_from_buffer(hash, console_id, data, data_size)) {
|
||||
rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!rc_hash_generate_from_file(hash, console_id, file_path)) {
|
||||
rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed");
|
||||
return NULL;
|
||||
}
|
||||
/* prevent initializing the iterator so it won't try other consoles in rc_client_process_resolved_hash */
|
||||
load_state->hash_iterator.index = 0;
|
||||
|
||||
if (!rc_hash_generate(hash, console_id, &load_state->hash_iterator)) {
|
||||
rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3106,7 +3189,8 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
||||
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
|
||||
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID || /* previously looked up */
|
||||
game_hash->hash[0] == '[') { /* internal use - don't try to look up */
|
||||
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
@@ -3199,7 +3283,7 @@ static rc_client_game_info_t* rc_client_check_pending_media(rc_client_t* client,
|
||||
}
|
||||
|
||||
/* still waiting for game data - don't call callback - it's queued */
|
||||
if (pending_media)
|
||||
if (pending_media)
|
||||
return NULL;
|
||||
|
||||
return game;
|
||||
@@ -3207,7 +3291,7 @@ static rc_client_game_info_t* rc_client_check_pending_media(rc_client_t* client,
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path,
|
||||
rc_client_async_handle_t* rc_client_begin_identify_and_change_media(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
rc_client_pending_media_t media;
|
||||
@@ -3227,9 +3311,9 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && !client->state.external_client->begin_change_media_from_hash) {
|
||||
if (client->state.external_client->begin_change_media)
|
||||
return client->state.external_client->begin_change_media(client, file_path, data, data_size, callback, callback_userdata);
|
||||
if (client->state.external_client && !client->state.external_client->begin_change_media) {
|
||||
if (client->state.external_client->begin_identify_and_change_media)
|
||||
return client->state.external_client->begin_identify_and_change_media(client, file_path, data, data_size, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3259,19 +3343,11 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
char hash[33];
|
||||
int result;
|
||||
|
||||
if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) {
|
||||
g_hash_client = client;
|
||||
rc_hash_init_error_message_callback(rc_client_log_hash_message);
|
||||
rc_hash_init_verbose_message_callback(rc_client_log_hash_message);
|
||||
}
|
||||
|
||||
if (data != NULL)
|
||||
result = rc_hash_generate_from_buffer(hash, game->public_.console_id, data, data_size);
|
||||
else
|
||||
result = rc_hash_generate_from_file(hash, game->public_.console_id, file_path);
|
||||
|
||||
g_hash_client = NULL;
|
||||
|
||||
if (!result) {
|
||||
/* when changing discs, if the disc is not supported by the system, allow it. this is
|
||||
* primarily for games that support user-provided audio CDs, but does allow using discs
|
||||
@@ -3292,8 +3368,8 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
|
||||
if (!result) {
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media_from_hash)
|
||||
return client->state.external_client->begin_change_media_from_hash(client, game_hash->hash, callback, callback_userdata);
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media)
|
||||
return client->state.external_client->begin_change_media(client, game_hash->hash, callback, callback_userdata);
|
||||
#endif
|
||||
|
||||
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
|
||||
@@ -3305,8 +3381,8 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(game_hash->hash, game_hash->game_id);
|
||||
if (client->state.external_client->begin_change_media_from_hash)
|
||||
return client->state.external_client->begin_change_media_from_hash(client, game_hash->hash, callback, callback_userdata);
|
||||
if (client->state.external_client->begin_change_media)
|
||||
return client->state.external_client->begin_change_media(client, game_hash->hash, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3315,7 +3391,7 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
|
||||
#endif /* RC_CLIENT_SUPPORTS_HASH */
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
|
||||
rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* hash,
|
||||
rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
rc_client_pending_media_t media;
|
||||
@@ -3333,8 +3409,8 @@ rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* cl
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media_from_hash) {
|
||||
return client->state.external_client->begin_change_media_from_hash(client, hash, callback, callback_userdata);
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media) {
|
||||
return client->state.external_client->begin_change_media(client, hash, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3364,7 +3440,7 @@ const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
|
||||
|
||||
if (client->state.external_client->get_game_info)
|
||||
return rc_client_external_convert_v1_game(client, client->state.external_client->get_game_info());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return client->game ? &client->game->public_ : NULL;
|
||||
@@ -3820,9 +3896,9 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
|
||||
bucket_ptr->bucket_type = bucket_type;
|
||||
|
||||
if (bucket_type == RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED)
|
||||
qsort(bucket_ptr->achievements, bucket_ptr->num_achievements, sizeof(rc_client_achievement_t*), rc_client_compare_achievement_unlock_times);
|
||||
qsort((void*)bucket_ptr->achievements, bucket_ptr->num_achievements, sizeof(rc_client_achievement_t*), rc_client_compare_achievement_unlock_times);
|
||||
else if (bucket_type == RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE)
|
||||
qsort(bucket_ptr->achievements, bucket_ptr->num_achievements, sizeof(rc_client_achievement_t*), rc_client_compare_achievement_progress);
|
||||
qsort((void*)bucket_ptr->achievements, bucket_ptr->num_achievements, sizeof(rc_client_achievement_t*), rc_client_compare_achievement_progress);
|
||||
|
||||
++bucket_ptr;
|
||||
}
|
||||
@@ -4144,7 +4220,7 @@ static void rc_client_award_achievement_callback(const rc_api_server_response_t*
|
||||
}
|
||||
|
||||
static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data)
|
||||
{
|
||||
{
|
||||
rc_api_award_achievement_request_t api_params;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
@@ -4298,7 +4374,7 @@ const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t*
|
||||
if (leaderboard != NULL)
|
||||
return &leaderboard->public_;
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -4382,7 +4458,7 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
|
||||
};
|
||||
|
||||
if (!client)
|
||||
return calloc(1, sizeof(rc_client_leaderboard_list_t));
|
||||
return (rc_client_leaderboard_list_t*)calloc(1, list_size);
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->create_leaderboard_list)
|
||||
@@ -4390,7 +4466,7 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
return calloc(1, sizeof(rc_client_leaderboard_list_t));
|
||||
return (rc_client_leaderboard_list_t*)calloc(1, list_size);
|
||||
|
||||
memset(&bucket_counts, 0, sizeof(bucket_counts));
|
||||
|
||||
@@ -5234,6 +5310,19 @@ void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memo
|
||||
client->callbacks.read_memory = handler;
|
||||
}
|
||||
|
||||
void rc_client_set_allow_background_memory_reads(rc_client_t* client, int allowed)
|
||||
{
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->set_allow_background_memory_reads)
|
||||
client->state.external_client->set_allow_background_memory_reads(allowed);
|
||||
#endif
|
||||
|
||||
client->state.allow_background_memory_reads = allowed;
|
||||
}
|
||||
|
||||
static void rc_client_invalidate_processing_memref(rc_client_t* client)
|
||||
{
|
||||
/* if processing_memref is not set, this occurred following a pointer chain. ignore it. */
|
||||
|
||||
@@ -35,6 +35,7 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_subse
|
||||
uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
|
||||
typedef const rc_client_game_t* (RC_CCONV *rc_client_external_get_game_info_func_t)(void);
|
||||
typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_func_t)(uint32_t subset_id);
|
||||
typedef void (RC_CCONV* rc_client_external_get_user_subset_summary_func_t)(uint32_t subset_id, rc_client_user_game_summary_t* summary);
|
||||
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
|
||||
@@ -99,8 +100,8 @@ typedef struct rc_client_external_t
|
||||
rc_client_external_get_subset_info_func_t get_subset_info;
|
||||
rc_client_external_action_func_t unload_game;
|
||||
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
|
||||
rc_client_external_begin_change_media_func_t begin_change_media;
|
||||
rc_client_external_begin_load_game_func_t begin_change_media_from_hash;
|
||||
rc_client_external_begin_change_media_func_t begin_identify_and_change_media;
|
||||
rc_client_external_begin_load_game_func_t begin_change_media;
|
||||
|
||||
rc_client_external_create_achievement_list_func_t create_achievement_list;
|
||||
rc_client_external_get_int_func_t has_achievements;
|
||||
@@ -136,9 +137,16 @@ typedef struct rc_client_external_t
|
||||
rc_client_external_get_achievement_info_func_t get_achievement_info_v3;
|
||||
rc_client_external_create_achievement_list_func_t create_achievement_list_v3;
|
||||
|
||||
/* VERSION 4 */
|
||||
rc_client_external_set_int_func_t set_allow_background_memory_reads;
|
||||
|
||||
/* VERSION 5 */
|
||||
rc_client_external_get_user_game_summary_func_t get_user_game_summary_v5;
|
||||
rc_client_external_get_user_subset_summary_func_t get_user_subset_summary;
|
||||
|
||||
} rc_client_external_t;
|
||||
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 3
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 5
|
||||
|
||||
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);
|
||||
|
||||
@@ -144,6 +144,28 @@ typedef struct v3_rc_client_achievement_list_info_t {
|
||||
rc_client_destroy_achievement_list_func_t destroy_func;
|
||||
} v3_rc_client_achievement_list_info_t;
|
||||
|
||||
/* user_game_summary */
|
||||
|
||||
typedef struct v1_rc_client_user_game_summary_t {
|
||||
uint32_t num_core_achievements;
|
||||
uint32_t num_unofficial_achievements;
|
||||
uint32_t num_unlocked_achievements;
|
||||
uint32_t num_unsupported_achievements;
|
||||
uint32_t points_core;
|
||||
uint32_t points_unlocked;
|
||||
} v1_rc_client_user_game_summary_t;
|
||||
|
||||
typedef struct v5_rc_client_user_game_summary_t {
|
||||
uint32_t num_core_achievements;
|
||||
uint32_t num_unofficial_achievements;
|
||||
uint32_t num_unlocked_achievements;
|
||||
uint32_t num_unsupported_achievements;
|
||||
uint32_t points_core;
|
||||
uint32_t points_unlocked;
|
||||
time_t beaten_time;
|
||||
time_t completed_time;
|
||||
} v5_rc_client_user_game_summary_t;
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_EXTERNAL_CONVERSIONS_H */
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
#include "rc_client_external.h"
|
||||
#endif
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
#include "rhash/rc_hash_internal.h"
|
||||
#endif
|
||||
|
||||
#include "rc_compat.h"
|
||||
#include "rc_runtime.h"
|
||||
@@ -20,9 +23,9 @@ RC_BEGIN_C_DECLS
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
struct rc_api_fetch_game_data_response_t;
|
||||
typedef void (RC_CCONV *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);
|
||||
struct rc_api_fetch_game_sets_response_t;
|
||||
typedef void (RC_CCONV *rc_client_post_process_game_sets_response_t)(const rc_api_server_response_t* server_response,
|
||||
struct rc_api_fetch_game_sets_response_t* game_sets_response, rc_client_t* client, void* userdata);
|
||||
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
|
||||
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
|
||||
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
|
||||
@@ -36,11 +39,15 @@ typedef struct rc_client_callbacks_t {
|
||||
rc_client_message_callback_t log_call;
|
||||
rc_get_time_millisecs_func_t get_time_millisecs;
|
||||
rc_client_identify_hash_func_t identify_unknown_hash;
|
||||
rc_client_post_process_game_data_response_t post_process_game_data_response;
|
||||
rc_client_post_process_game_sets_response_t post_process_game_sets_response;
|
||||
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
|
||||
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
|
||||
rc_client_rich_presence_override_t rich_presence_override;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
rc_hash_callbacks_t hash;
|
||||
#endif
|
||||
|
||||
void* client_data;
|
||||
} rc_client_callbacks_t;
|
||||
|
||||
@@ -320,6 +327,7 @@ typedef struct rc_client_state_t {
|
||||
uint8_t user;
|
||||
uint8_t disconnect;
|
||||
uint8_t allow_leaderboards_in_softcore;
|
||||
uint8_t allow_background_memory_reads;
|
||||
|
||||
struct rc_client_load_state_t* load;
|
||||
struct rc_client_async_handle_t* async_handles[4];
|
||||
|
||||
@@ -289,7 +289,6 @@
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_NONE">{RC_CLIENT_LOAD_GAME_STATE_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME">{RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN">{RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA">{RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION">{RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_DONE">{RC_CLIENT_LOAD_GAME_STATE_DONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_ABORTED">{RC_CLIENT_LOAD_GAME_STATE_ABORTED}</DisplayString>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 11
|
||||
#define RCHEEVOS_VERSION_MINOR 6
|
||||
#define RCHEEVOS_VERSION_MAJOR 12
|
||||
#define RCHEEVOS_VERSION_MINOR 0
|
||||
#define RCHEEVOS_VERSION_PATCH 0
|
||||
|
||||
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
|
||||
|
||||
@@ -101,6 +101,11 @@ static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr, c
|
||||
chain_length = 1;
|
||||
} while (*memaddr++ == '_');
|
||||
|
||||
/* any combining conditions that don't actually feed into a non-combining condition
|
||||
* need to still have space allocated for them. put them in "other" to match the
|
||||
* logic in rc_find_next_classification */
|
||||
self->num_other_conditions += chain_length - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -338,8 +343,12 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
|
||||
if (parse->buffer) {
|
||||
classification = rc_classify_condition(&condition);
|
||||
if (classification == RC_CONDITION_CLASSIFICATION_COMBINING) {
|
||||
if (combining_classification == RC_CONDITION_CLASSIFICATION_COMBINING)
|
||||
combining_classification = rc_find_next_classification(&(*memaddr)[1]); /* skip over '_' */
|
||||
if (combining_classification == RC_CONDITION_CLASSIFICATION_COMBINING) {
|
||||
if (**memaddr == '_')
|
||||
combining_classification = rc_find_next_classification(&(*memaddr)[1]); /* skip over '_' */
|
||||
else
|
||||
combining_classification = RC_CONDITION_CLASSIFICATION_OTHER;
|
||||
}
|
||||
|
||||
classification = combining_classification;
|
||||
}
|
||||
|
||||
@@ -724,10 +724,11 @@ static const rc_memory_regions_t rc_memory_regions_nes = { _rc_memory_regions_ne
|
||||
|
||||
/* ===== Nintendo 64 ===== */
|
||||
/* https://raw.githubusercontent.com/mikeryan/n64dev/master/docs/n64ops/n64ops%23h.txt */
|
||||
/* https://n64brew.dev/wiki/Memory_map#Virtual_Memory_Map */
|
||||
static const rc_memory_region_t _rc_memory_regions_n64[] = {
|
||||
{ 0x000000U, 0x1FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 1 */
|
||||
{ 0x200000U, 0x3FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 2 */
|
||||
{ 0x400000U, 0x7FFFFFU, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } /* expansion pak - cannot find any details for real address */
|
||||
{ 0x000000U, 0x1FFFFFU, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 1 */
|
||||
{ 0x200000U, 0x3FFFFFU, 0x80200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 2 */
|
||||
{ 0x400000U, 0x7FFFFFU, 0x80400000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } /* expansion pak */
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_n64 = { _rc_memory_regions_n64, 3 };
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ static float rc_build_float(uint32_t mantissa_bits, int32_t exponent, int sign)
|
||||
if (mantissa_bits == 0) {
|
||||
/* infinity */
|
||||
#ifdef INFINITY /* INFINITY and NAN #defines require C99 */
|
||||
dbl = INFINITY;
|
||||
dbl = (double)INFINITY;
|
||||
#else
|
||||
dbl = -log(0.0);
|
||||
#endif
|
||||
|
||||
@@ -307,6 +307,9 @@ void rc_operand_set_float_const(rc_operand_t* self, double value) {
|
||||
}
|
||||
|
||||
int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) {
|
||||
if (left == right)
|
||||
return 1;
|
||||
|
||||
if (left->type != right->type)
|
||||
return 0;
|
||||
|
||||
@@ -377,9 +380,6 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
|
||||
if (!rc_operand_is_memref(self))
|
||||
return 0;
|
||||
|
||||
if (self->type == RC_OPERAND_RECALL)
|
||||
return rc_memsize_is_float(self->memref_access_type);
|
||||
|
||||
if (self->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
|
||||
const rc_modified_memref_t* memref = (const rc_modified_memref_t*)self->value.memref;
|
||||
if (memref->modifier_type != RC_OPERATOR_INDIRECT_READ)
|
||||
@@ -431,6 +431,9 @@ int rc_operand_is_float(const rc_operand_t* self) {
|
||||
if (self->type == RC_OPERAND_FP)
|
||||
return 1;
|
||||
|
||||
if (self->type == RC_OPERAND_RECALL)
|
||||
return rc_memsize_is_float(self->size);
|
||||
|
||||
return rc_operand_is_float_memref(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -379,6 +379,8 @@
|
||||
<DisplayString Condition="value==RC_FORMAT_TENS">{RC_FORMAT_TENS}</DisplayString>
|
||||
<DisplayString Condition="value==RC_FORMAT_HUNDREDS">{RC_FORMAT_HUNDREDS}</DisplayString>
|
||||
<DisplayString Condition="value==RC_FORMAT_THOUSANDS">{RC_FORMAT_THOUSANDS}</DisplayString>
|
||||
<DisplayString Condition="value==RC_FORMAT_UNSIGNED_VALUE">{RC_FORMAT_UNSIGNED_VALUE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_FORMAT_UNFORMATTED">{RC_FORMAT_UNFORMATTED}</DisplayString>
|
||||
<DisplayString Condition="value==101">RC_FORMAT_STRING (101)</DisplayString>
|
||||
<DisplayString Condition="value==102">RC_FORMAT_LOOKUP (102)</DisplayString>
|
||||
<DisplayString Condition="value==103">RC_FORMAT_UNKNOWN_MACRO (103)</DisplayString>
|
||||
|
||||
@@ -174,8 +174,8 @@ static uint32_t rc_scale_value(uint32_t value, uint8_t oper, const rc_operand_t*
|
||||
|
||||
case RC_OPERATOR_SUB:
|
||||
{
|
||||
const uint32_t op_max = (operand->type == RC_OPERAND_CONST) ? operand->value.num : rc_max_value(operand);
|
||||
if (value > op_max)
|
||||
const uint32_t op_max = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 0;
|
||||
if (value >= op_max)
|
||||
return value - op_max;
|
||||
|
||||
return 0xFFFFFFFF;
|
||||
@@ -351,11 +351,11 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
|
||||
snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index);
|
||||
return 0;
|
||||
}
|
||||
if (rc_operand_is_float(&cond->operand1) || rc_operand_is_float(&cond->operand2)) {
|
||||
if (rc_operand_is_float(operand1) || rc_operand_is_float(&cond->operand2)) {
|
||||
snprintf(result, result_size, "Condition %d: Using non-integer value in AddAddress calcuation", index);
|
||||
return 0;
|
||||
}
|
||||
if (rc_operand_type_is_transform(cond->operand1.type)) {
|
||||
if (rc_operand_type_is_transform(operand1->type) && cond->oper != RC_OPERATOR_MULT) {
|
||||
snprintf(result, result_size, "Condition %d: Using transformed value in AddAddress calcuation", index);
|
||||
return 0;
|
||||
}
|
||||
@@ -721,7 +721,7 @@ static int rc_validate_comparison_overlap(int comparison1, uint32_t value1, int
|
||||
}
|
||||
|
||||
static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions,
|
||||
const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
|
||||
int has_hits, const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
|
||||
{
|
||||
int comparison1, comparison2;
|
||||
uint32_t value1, value2;
|
||||
@@ -895,7 +895,15 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
|
||||
{
|
||||
/* only ever report the redundancy on the non-ResetIf condition. The ResetIf is allowed to
|
||||
* fire when the non-ResetIf condition is not true. */
|
||||
continue;
|
||||
if (has_hits)
|
||||
continue;
|
||||
}
|
||||
else if (condition->type == RC_CONDITION_RESET_IF && compare_condition->type != RC_CONDITION_RESET_IF)
|
||||
{
|
||||
/* if the ResetIf condition is more restrictive than the non-ResetIf condition,
|
||||
and there aren't any hits to clear, ignore it */
|
||||
if (has_hits)
|
||||
continue;
|
||||
}
|
||||
else if (compare_condition->type == RC_CONDITION_MEASURED_IF || condition->type == RC_CONDITION_MEASURED_IF)
|
||||
{
|
||||
@@ -910,12 +918,11 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (compare_condition->type == RC_CONDITION_TRIGGER || condition->type == RC_CONDITION_TRIGGER)
|
||||
else if (condition->type == RC_CONDITION_TRIGGER && compare_condition->type != RC_CONDITION_TRIGGER)
|
||||
{
|
||||
/* Trigger is allowed to be redundant with non-trigger conditions as there may be limits that start a
|
||||
* challenge that are furhter reduced for the completion of the challenge */
|
||||
if (compare_condition->type != condition->type)
|
||||
continue;
|
||||
* challenge that are further reduced for the completion of the challenge */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -948,12 +955,21 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
|
||||
{
|
||||
const rc_condset_t* alt;
|
||||
int index;
|
||||
int has_hits = (trigger->requirement && trigger->requirement->num_hittarget_conditions > 0);
|
||||
if (!has_hits) {
|
||||
for (alt = trigger->alternative; alt; alt = alt->next) {
|
||||
if (alt->num_hittarget_conditions > 0) {
|
||||
has_hits = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!trigger->alternative) {
|
||||
if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address))
|
||||
return 0;
|
||||
|
||||
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "", "", result, result_size);
|
||||
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, has_hits, "", "", result, result_size);
|
||||
}
|
||||
|
||||
snprintf(result, result_size, "Core ");
|
||||
@@ -961,7 +977,7 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
|
||||
return 0;
|
||||
|
||||
/* compare core to itself */
|
||||
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "Core", "Core", result, result_size))
|
||||
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, has_hits, "Core", "Core", result, result_size))
|
||||
return 0;
|
||||
|
||||
index = 1;
|
||||
@@ -973,15 +989,15 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
|
||||
|
||||
/* compare alt to itself */
|
||||
snprintf(altname, sizeof(altname), "Alt%d", index);
|
||||
if (!rc_validate_conflicting_conditions(alt, alt, altname, altname, result, result_size))
|
||||
if (!rc_validate_conflicting_conditions(alt, alt, has_hits, altname, altname, result, result_size))
|
||||
return 0;
|
||||
|
||||
/* compare alt to core */
|
||||
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, "Core", altname, result, result_size))
|
||||
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, has_hits, "Core", altname, result, result_size))
|
||||
return 0;
|
||||
|
||||
/* compare core to alt */
|
||||
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, altname, "Core", result, result_size))
|
||||
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, has_hits, altname, "Core", result, result_size))
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,9 @@ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t
|
||||
|
||||
condset = value->conditions;
|
||||
if (condset && !condset->next) {
|
||||
/* single value - if it's only "measured" and "indirect" conditions, we can simplify to a memref */
|
||||
if (condset->num_measured_conditions &&
|
||||
/* single value - if it's a single Measured clause (including any AddSource/AddAddress helpers), we can
|
||||
* simplify to a memref. If there are supporting clauses like MeasuredIf or ResetIf, we can't */
|
||||
if (condset->num_measured_conditions == 1 &&
|
||||
!condset->num_pause_conditions && !condset->num_reset_conditions &&
|
||||
!condset->num_other_conditions && !condset->num_hittarget_conditions) {
|
||||
rc_condition_t* condition = condset->conditions;
|
||||
@@ -537,6 +538,13 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
||||
|
||||
} else if (strncmp(line, "Format:", 7) == 0) {
|
||||
line += 7;
|
||||
if (endline - line == 11 && memcmp(line, "Unformatted", 11) == 0) {
|
||||
/* for backwards compatibility with the comma rollout, we allow old scripts
|
||||
* to define an Unformatted type mapped to VALUE, and new versions will ignore
|
||||
* the definition and use the built-in macro. skip the next line (FormatType=) */
|
||||
line = rc_parse_line(nextline, &endline, parse);
|
||||
continue;
|
||||
}
|
||||
|
||||
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
|
||||
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
|
||||
|
||||
@@ -36,7 +36,7 @@ rc_runtime_t* rc_runtime_alloc(void) {
|
||||
rc_runtime_event_handler_t unused = &rc_runtime_natvis_helper;
|
||||
(void)unused;
|
||||
|
||||
self = malloc(sizeof(rc_runtime_t));
|
||||
self = (rc_runtime_t*)malloc(sizeof(rc_runtime_t));
|
||||
|
||||
if (self) {
|
||||
rc_runtime_init(self);
|
||||
@@ -307,7 +307,7 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
|
||||
rc_lboard_t* lboard;
|
||||
rc_preparse_state_t preparse;
|
||||
rc_runtime_lboard_t* runtime_lboard;
|
||||
int size;
|
||||
int32_t size;
|
||||
uint32_t i;
|
||||
|
||||
(void)unused_L;
|
||||
@@ -808,7 +808,7 @@ void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) {
|
||||
} while (memref_list);
|
||||
}
|
||||
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
|
||||
rc_runtime_validate_address_t validate_handler) {
|
||||
int num_invalid = 0;
|
||||
rc_memref_list_t* memref_list = &self->memrefs->memrefs;
|
||||
|
||||
@@ -560,8 +560,17 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
|
||||
}
|
||||
}
|
||||
|
||||
/* VS raises a C6385 warning here because it thinks count can exceed the size of the local_pending_variables array.
|
||||
* When count is larger, pending_variables points to allocated memory, so the warning is wrong. */
|
||||
#if defined (_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:6385)
|
||||
#endif
|
||||
while (count > 0)
|
||||
rc_reset_value(pending_variables[--count].variable);
|
||||
#if defined (_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
if (pending_variables != local_pending_variables)
|
||||
free(pending_variables);
|
||||
@@ -911,7 +920,7 @@ uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, void* unused_L)
|
||||
|
||||
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, void* unused_L)
|
||||
{
|
||||
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, unused_L);
|
||||
return rc_runtime_serialize_progress_sized((uint8_t*)buffer, 0xFFFFFFFF, runtime, unused_L);
|
||||
}
|
||||
|
||||
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, void* unused_L)
|
||||
|
||||
Reference in New Issue
Block a user