|
|
|
|
@ -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);
|
|
|
|
|
return;
|
|
|
|
|
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))
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
|
|
|
|
|
memset(summary, 0, sizeof(*summary));
|
|
|
|
|
if (!client || !subset_id)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
rc_client_subset_get_user_game_summary(client->game->subsets, summary, unlock_bit);
|
|
|
|
|
#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
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
else {
|
|
|
|
|
achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ?
|
|
|
|
|
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
|
|
|
|
|
|
|
|
|
|
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
|
|
|
|
|
if (client->state.encore_mode) {
|
|
|
|
|
++active_count;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ?
|
|
|
|
|
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
|
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
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, set->achievements, set->num_achievements);
|
|
|
|
|
rc_client_copy_leaderboards(load_state, subset, set->leaderboards, set->num_leaderboards);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
if (!first_subset) {
|
|
|
|
|
rc_client_load_error(load_state, RC_NOT_FOUND, "Response contained no sets");
|
|
|
|
|
} else {
|
|
|
|
|
load_state->subset = first_subset;
|
|
|
|
|
|
|
|
|
|
*next_subset = subset;
|
|
|
|
|
next_subset = &subset->next;
|
|
|
|
|
}
|
|
|
|
|
/* 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 (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 (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. */
|
|
|
|
|
|