diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index 28474b13e48..ace1600552f 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -83,21 +83,11 @@ static rcheevos_locals_t rcheevos_locals = { -#ifdef HAVE_RC_CLIENT NULL, /* client */ -#else - {0}, /* runtime */ - {0}, /* game */ -#endif {{0}},/* memory */ #ifdef HAVE_THREADS CMD_EVENT_NONE, /* queued_command */ false, /* game_placard_requested */ -#endif -#ifndef HAVE_RC_CLIENT - "", /* displayname */ - "", /* username */ - "", /* token */ #endif "", /* user_agent_prefix */ "", /* user_agent_core */ @@ -106,23 +96,8 @@ static rcheevos_locals_t rcheevos_locals = 0, /* menuitem_capacity */ 0, /* menuitem_count */ #endif -#ifdef HAVE_RC_CLIENT true, /* hardcore_allowed */ false,/* hardcore_being_enabled */ -#else - #ifdef HAVE_GFX_WIDGETS - 0, /* active_lboard_trackers */ - NULL, /* tracker_achievement */ - 0.0, /* tracker_progress */ - #endif - {RCHEEVOS_LOAD_STATE_NONE, 0, 0 }, /* load_info */ - 0, /* unpaused_frames */ - false,/* hardcore_active */ - false,/* loaded */ - #ifdef HAVE_GFX_WIDGETS - false,/* assign_new_trackers */ - #endif -#endif true /* core_supports */ }; @@ -147,37 +122,6 @@ void rcheevos_log(const char* fmt, ...) } #endif -#ifndef HAVE_RC_CLIENT - -static void rcheevos_achievement_disabled( - rcheevos_racheevo_t* cheevo, unsigned address) -{ - if (!cheevo) - return; - - CHEEVOS_ERR(RCHEEVOS_TAG - "Achievement %u disabled (invalid address %06X): %s\n", - cheevo->id, address, cheevo->title); - CHEEVOS_FREE(cheevo->memaddr); - cheevo->memaddr = NULL; - cheevo->active |= RCHEEVOS_ACTIVE_UNSUPPORTED; -} - -static void rcheevos_lboard_disabled( - rcheevos_ralboard_t* lboard, unsigned address) -{ - if (!lboard) - return; - - CHEEVOS_ERR(RCHEEVOS_TAG - "Leaderboard %u disabled (invalid address %06X): %s\n", - lboard->id, address, lboard->title); - CHEEVOS_FREE(lboard->mem); - lboard->mem = NULL; -} - -#endif /* HAVE_RC_CLIENT */ - static void rcheevos_handle_log_message(const char* message) { CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", message); @@ -208,21 +152,15 @@ static int rcheevos_init_memory(rcheevos_locals_t* locals) unsigned i; int result; struct retro_memory_map mmap; -#ifdef HAVE_RC_CLIENT const rc_client_game_t* game; -#endif rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system; rarch_memory_map_t *mmaps = &sys_info->mmaps; struct retro_memory_descriptor* descriptors; unsigned console_id; -#ifdef HAVE_RC_CLIENT /* if no game is loaded, fallback to a default mapping (SYSTEM RAM followed by SAVE RAM) */ game = rc_client_get_game_info(locals->client); console_id = game ? game->console_id : 0; -#else - console_id = locals->game.console_id; -#endif descriptors = (struct retro_memory_descriptor*)malloc(mmaps->num_descriptors * sizeof(*descriptors)); if (!descriptors) @@ -254,96 +192,9 @@ uint8_t* rcheevos_patch_address(unsigned address) return rc_libretro_memory_find(&rcheevos_locals.memory, address); } -#ifndef HAVE_RC_CLIENT - -static uint32_t rcheevos_peek(uint32_t address, - uint32_t num_bytes, void* ud) -{ - uint32_t avail; - uint8_t* data = rc_libretro_memory_find_avail( - &rcheevos_locals.memory, address, &avail); - - if (data && avail >= num_bytes) - { - switch (num_bytes) - { - case 4: - return (data[3] << 24) | (data[2] << 16) | - (data[1] << 8) | (data[0]); - case 3: - return (data[2] << 16) | (data[1] << 8) | (data[0]); - case 2: - return (data[1] << 8) | (data[0]); - case 1: - return data[0]; - } - } - - return 0; -} - -static void rcheevos_activate_achievements(void) -{ - unsigned i; - int result; - rcheevos_racheevo_t* achievement = rcheevos_locals.game.achievements; - settings_t* settings = config_get_ptr(); - const uint8_t active_flag = rcheevos_locals.hardcore_active ? RCHEEVOS_ACTIVE_HARDCORE : RCHEEVOS_ACTIVE_SOFTCORE; - - for (i = 0; i < rcheevos_locals.game.achievement_count; - i++, achievement++) - { - if ((achievement->active & active_flag) != 0) - { - result = rc_runtime_activate_achievement(&rcheevos_locals.runtime, achievement->id, achievement->memaddr, NULL, 0); - if (result != RC_OK) - { - char buffer[256]; - buffer[0] = '\0'; - /* TODO/FIXME - localize */ - snprintf(buffer, sizeof(buffer), - "Could not activate achievement %u \"%s\": %s", - achievement->id, achievement->title, rc_error_str(result)); - - if (settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - - CHEEVOS_ERR(RCHEEVOS_TAG "%s: mem %s\n", buffer, achievement->memaddr); - achievement->active &= ~(RCHEEVOS_ACTIVE_HARDCORE | RCHEEVOS_ACTIVE_SOFTCORE); - achievement->active |= RCHEEVOS_ACTIVE_UNSUPPORTED; - - CHEEVOS_FREE(achievement->memaddr); - achievement->memaddr = NULL; - } - } - } -} - -static rcheevos_racheevo_t* rcheevos_find_cheevo(unsigned id) -{ - rcheevos_racheevo_t* cheevo = rcheevos_locals.game.achievements; - rcheevos_racheevo_t* stop = cheevo - + rcheevos_locals.game.achievement_count; - - for(; cheevo < stop; ++cheevo) - { - if (cheevo->id == id) - return cheevo; - } - - return NULL; -} - -#endif - static bool rcheevos_is_game_loaded(void) { -#ifdef HAVE_RC_CLIENT return rc_client_is_game_loaded(rcheevos_locals.client); -#else - return rcheevos_locals.loaded; -#endif } static bool rcheevos_is_player_active(void) @@ -358,7 +209,6 @@ static bool rcheevos_is_player_active(void) void rcheevos_spectating_changed(void) { -#ifdef HAVE_RC_CLIENT /* don't update spectator mode while a game is loading - it prevents being able to change it later */ if (rcheevos_is_game_loaded()) { @@ -366,20 +216,13 @@ void rcheevos_spectating_changed(void) if (spectating != rc_client_get_spectator_mode_enabled(rcheevos_locals.client)) rc_client_set_spectator_mode_enabled(rcheevos_locals.client, !rcheevos_is_player_active()); } -#endif } bool rcheevos_is_pause_allowed(void) { -#ifdef HAVE_RC_CLIENT return rc_client_can_pause(rcheevos_locals.client, NULL); -#else - return (rcheevos_locals.unpaused_frames == 0); -#endif } -#ifdef HAVE_RC_CLIENT - static void rcheevos_show_mastery_placard(void) { const settings_t* settings = config_get_ptr(); @@ -772,2423 +615,890 @@ int rcheevos_get_game_badge_url(char* s, size_t len) return (rc_client_game_get_image_url(game, s, len) == RC_OK); } -#else /* !HAVE_RC_CLIENT */ +#ifdef HAVE_GFX_WIDGETS -void rcheevos_award_achievement(rcheevos_locals_t* locals, - rcheevos_racheevo_t* cheevo, bool widgets_ready) +static void rcheevos_hide_widgets(bool widgets_ready) { - const settings_t *settings = config_get_ptr(); - - if (!cheevo) - return; - - /* Deactivates the achievement. */ - rc_runtime_deactivate_achievement(&locals->runtime, cheevo->id); - - cheevo->active &= ~RCHEEVOS_ACTIVE_SOFTCORE; - if (locals->hardcore_active) - cheevo->active &= ~RCHEEVOS_ACTIVE_HARDCORE; - - cheevo->unlock_time = cpu_features_get_time_usec(); - - if (!rcheevos_is_player_active()) + /* Hide any visible trackers */ + if (widgets_ready) { - CHEEVOS_LOG(RCHEEVOS_TAG "Not awarding achievement %u, player not active\n", - cheevo->id); - return; + gfx_widgets_clear_leaderboard_displays(); + gfx_widgets_clear_challenge_displays(); + gfx_widget_set_achievement_progress(NULL, NULL); } +} - CHEEVOS_LOG(RCHEEVOS_TAG "Awarding achievement %u: %s (%s)\n", - cheevo->id, cheevo->title, cheevo->description); +#endif - /* Show the on screen message. */ - if (settings->bools.cheevos_visibility_unlock) - { +void rcheevos_reset_game(bool widgets_ready) +{ #if defined(HAVE_GFX_WIDGETS) - if (widgets_ready) - gfx_widgets_push_achievement(msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), cheevo->title, cheevo->badge); - else + /* Hide any visible trackers */ + rcheevos_hide_widgets(widgets_ready); #endif - { - char buffer[256]; - size_t _len = strlcpy(buffer, msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), - sizeof(buffer)); - _len += strlcpy(buffer + _len, ": ", sizeof(buffer) - _len); - _len += strlcpy(buffer + _len, cheevo->title, sizeof(buffer) - _len); - runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - runloop_msg_queue_push(cheevo->description, 0, 3 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - } - /* Start the award task (unofficial achievement - * unlocks are not submitted). */ - if (!(cheevo->active & RCHEEVOS_ACTIVE_UNOFFICIAL)) - rcheevos_client_award_achievement(cheevo->id); - -#ifdef HAVE_AUDIOMIXER - /* Play the unlock sound */ - if (settings->bools.cheevos_unlock_sound_enable) - audio_driver_mixer_play_menu_sound( - AUDIO_MIXER_SYSTEM_SLOT_ACHIEVEMENT_UNLOCK); -#endif + rc_client_reset(rcheevos_locals.client); -#ifdef HAVE_SCREENSHOTS - /* Take a screenshot of the achievement. */ - if (settings->bools.cheevos_auto_screenshot) - { - size_t shotname_len = sizeof(char) * 8192; - char *shotname = (char*)malloc(shotname_len); + /* Some cores reallocate memory on reset, + * make sure we update our pointers */ + if (rcheevos_locals.memory.total_size > 0) + rcheevos_init_memory(&rcheevos_locals); +} - if (shotname) - { - video_driver_state_t *video_st = video_state_get_ptr();; - snprintf(shotname, shotname_len, "%s/%s-cheevo-%u", - settings->paths.directory_screenshot, - path_basename(path_get(RARCH_PATH_BASENAME)), - cheevo->id); - shotname[shotname_len - 1] = '\0'; +void rcheevos_refresh_memory(void) +{ + if (rcheevos_locals.memory.total_size > 0) + rcheevos_init_memory(&rcheevos_locals); +} - if (take_screenshot(settings->paths.directory_screenshot, - shotname, - true, - video_st->frame_cache_data && (video_st->frame_cache_data == RETRO_HW_FRAME_BUFFER_VALID), - false, - true)) - CHEEVOS_LOG(RCHEEVOS_TAG - "Captured screenshot for achievement %u\n", - cheevo->id); - else - CHEEVOS_LOG(RCHEEVOS_TAG - "Failed to capture screenshot for achievement %u\n", - cheevo->id); +bool rcheevos_hardcore_active(void) +{ + /* normal hardcore check */ + if (rcheevos_locals.client && rc_client_get_hardcore_enabled(rcheevos_locals.client)) + return true; - free(shotname); - } - } -#endif + /* if we're trying to enable hardcore, pretend it's on so the caller can decide to disable + * it (by calling rcheevos_pause_hardcore) before we actually turn it on. */ + return rcheevos_locals.hardcore_being_enabled; } -static rcheevos_ralboard_t* rcheevos_find_lboard(unsigned id) +void rcheevos_pause_hardcore(void) { - rcheevos_ralboard_t* lboard = rcheevos_locals.game.leaderboards; - rcheevos_ralboard_t* stop = lboard - + rcheevos_locals.game.leaderboard_count; - - for (; lboard < stop; ++lboard) - { - if (lboard->id == id) - return lboard; - } + rcheevos_locals.hardcore_allowed = false; - return NULL; + if (rcheevos_hardcore_active()) + rcheevos_toggle_hardcore_paused(); } -#if defined(HAVE_GFX_WIDGETS) -static void rcheevos_assign_leaderboard_tracker_ids(rcheevos_locals_t* locals) +bool rcheevos_unload(void) { - rcheevos_ralboard_t* lboard = locals->game.leaderboards; - rcheevos_ralboard_t* scan; - rcheevos_ralboard_t* end = lboard + locals->game.leaderboard_count; - unsigned tracker_id; - char buffer[32]; - - for (; lboard < end; ++lboard) - { - if (lboard->active_tracker_id != 0xFF) - continue; + settings_t* settings = config_get_ptr(); + const bool was_loaded = rcheevos_is_game_loaded(); - tracker_id = 0; - if (locals->active_lboard_trackers != 0 && lboard->value_hash != 0) - { - scan = locals->game.leaderboards; - for (; scan < end; ++scan) - { - if (scan->active_tracker_id == 0 || scan->active_tracker_id == 0xFF) - continue; +#ifdef HAVE_GFX_WIDGETS + rcheevos_hide_widgets(gfx_widgets_ready()); + gfx_widget_set_cheevos_set_loading(false); +#endif - /* value_hash match indicates the values have the same definition, but if the leaderboard - * is tracking hits, it could have a different value depending on when it was started. - * also require the current value to match. */ - if (scan->value_hash == lboard->value_hash && scan->value == lboard->value) - { - tracker_id = scan->active_tracker_id; - break; - } - } - } + rc_client_unload_game(rcheevos_locals.client); - if (!tracker_id) - { - unsigned active_trackers = locals->active_lboard_trackers >> 1; - tracker_id++; +#ifdef HAVE_THREADS + rcheevos_locals.queued_command = CMD_EVENT_NONE; + rcheevos_locals.game_placard_requested = false; +#endif - while ((active_trackers & 1) != 0) - { - tracker_id++; - active_trackers >>= 1; - } + if (rcheevos_locals.memory.count > 0) + rc_libretro_memory_destroy(&rcheevos_locals.memory); - if (tracker_id <= 31) - { - locals->active_lboard_trackers |= (1 << tracker_id); + if (was_loaded) + { +#ifdef HAVE_MENU + rcheevos_menu_reset_badges(); - rc_runtime_format_lboard_value(buffer, - sizeof(buffer), lboard->value, lboard->format); - gfx_widgets_set_leaderboard_display(tracker_id, buffer); - } + if (rcheevos_locals.menuitems) + { + CHEEVOS_FREE(rcheevos_locals.menuitems); + rcheevos_locals.menuitems = NULL; + rcheevos_locals.menuitem_capacity = + rcheevos_locals.menuitem_count = 0; } - - lboard->active_tracker_id = tracker_id; +#endif } -} - -static void rcheevos_hide_leaderboard_tracker(rcheevos_locals_t* locals, - rcheevos_ralboard_t* lboard) -{ - unsigned i; - uint8_t tracker_id = lboard->active_tracker_id; - if (!tracker_id) - return; +#ifdef HAVE_THREADS + rcheevos_locals.queued_command = CMD_EVENT_NONE; +#endif - lboard->active_tracker_id = 0; - for (i = 0; i < locals->game.leaderboard_count; ++i) + if (!settings->arrays.cheevos_token[0]) { - if (locals->game.leaderboards[i].active_tracker_id == tracker_id) - return; - } + /* If the config-level token has been cleared, we need to re-login on + * loading the next game. Easiest way to do that is to destroy the client */ + rc_client_t* client = rcheevos_locals.client; + rcheevos_locals.client = NULL; - if (tracker_id <= 31) - { - locals->active_lboard_trackers &= ~(1 << tracker_id); - gfx_widgets_set_leaderboard_display(tracker_id, NULL); + rc_client_destroy(client); } + + return true; } -#endif -static void rcheevos_lboard_submit(rcheevos_locals_t* locals, - rcheevos_ralboard_t* lboard, int value, bool widgets_ready) +void rcheevos_leaderboard_trackers_visibility_changed(void) { - char buffer[256]; - char formatted_value[16]; - const settings_t *settings = config_get_ptr(); - #if defined(HAVE_GFX_WIDGETS) - /* Hide the tracker */ - if (gfx_widgets_ready()) - rcheevos_hide_leaderboard_tracker(locals, lboard); -#endif - - rc_runtime_format_lboard_value(formatted_value, - sizeof(formatted_value), - value, lboard->format); - - if (!rcheevos_is_player_active()) + const settings_t* settings = config_get_ptr(); + if (!settings->bools.cheevos_visibility_lboard_trackers) { - CHEEVOS_LOG(RCHEEVOS_TAG "Not submitting %s for leaderboard %u, player not active\n", - formatted_value, lboard->id); - return; + /* Hide any visible trackers */ + gfx_widgets_clear_leaderboard_displays(); } - - CHEEVOS_LOG(RCHEEVOS_TAG "Submitting %s for leaderboard %u\n", - formatted_value, lboard->id); - - /* Show the on-screen message. */ - if (settings->bools.cheevos_visibility_lboard_submit) + else { - snprintf(buffer, sizeof(buffer), msg_hash_to_str(MSG_LEADERBOARD_SUBMISSION), - formatted_value, lboard->title); - runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + /* No way to immediately request trackers be reshown, but they + * will reappear the next time they're updated */ } - - /* Start the submit task */ - rcheevos_client_submit_lboard_entry(lboard->id, value); +#endif } -static void rcheevos_lboard_canceled(rcheevos_ralboard_t * lboard, - bool widgets_ready) +static void rcheevos_enforce_hardcore_settings(void) { - const settings_t *settings = config_get_ptr(); - if (!lboard) - return; - - CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard %u canceled: %s\n", - lboard->id, lboard->title); - -#if defined(HAVE_GFX_WIDGETS) - if (widgets_ready) - rcheevos_hide_leaderboard_tracker(&rcheevos_locals, lboard); -#endif - - if (settings->bools.cheevos_visibility_lboard_cancel) - { - char buffer[256]; - size_t _len = strlcpy(buffer, msg_hash_to_str(MSG_LEADERBOARD_FAILED), - sizeof(buffer)); - _len += strlcpy(buffer + _len, ": ", sizeof(buffer) - _len); - strlcpy(buffer + _len, lboard->title, sizeof(buffer) - _len); - runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } + /* disable slowdown */ + runloop_state_get_ptr()->flags &= ~RUNLOOP_FLAG_SLOWMOTION; } -static void rcheevos_lboard_started( - rcheevos_ralboard_t * lboard, int value, - bool widgets_ready) +static void rcheevos_toggle_hardcore_active(rcheevos_locals_t* locals) { - const settings_t *settings = config_get_ptr(); - char buffer[256]; - if (!lboard) - return; + settings_t* settings = config_get_ptr(); + bool rewind_enable = settings->bools.rewind_enable; + const bool was_enabled = rcheevos_hardcore_active(); - CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard %u started: %s\n", - lboard->id, lboard->title); - - if (!rcheevos_is_player_active()) - return; + if (!was_enabled) + { + locals->hardcore_being_enabled = true; + locals->hardcore_allowed = true; -#if defined(HAVE_GFX_WIDGETS) - lboard->value = value; + /* If one or more invalid settings is enabled, abort*/ + rcheevos_validate_config_settings(); + if (!locals->hardcore_allowed) + { + locals->hardcore_being_enabled = false; + return; + } - if (settings->bools.cheevos_visibility_lboard_trackers) - { - /* mark the leaderboard as needing a tracker assigned so we can check for merging later */ - lboard->active_tracker_id = 0xFF; - rcheevos_locals.assign_new_trackers = true; - } +#ifdef HAVE_CHEATS + /* If one or more emulator managed cheats is active, abort */ + cheat_manager_apply_cheats(); + if (!locals->hardcore_allowed) + { + locals->hardcore_being_enabled = false; + return; + } #endif - if (settings->bools.cheevos_visibility_lboard_start) - { - size_t _len = strlcpy(buffer, msg_hash_to_str(MSG_LEADERBOARD_STARTED), - sizeof(buffer)); - _len += strlcpy(buffer + _len, ": ", sizeof(buffer) - _len); - _len += strlcpy(buffer + _len, lboard->title, sizeof(buffer) - _len); - if (lboard->description && *lboard->description) - snprintf(buffer + _len, sizeof(buffer) - _len, "- %s", - lboard->description); - - runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL, + if (rcheevos_is_game_loaded()) + { + const char* msg = msg_hash_to_str( + MSG_CHEEVOS_HARDCORE_MODE_ENABLE); + CHEEVOS_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } -} -#if defined(HAVE_GFX_WIDGETS) -static void rcheevos_lboard_updated( - rcheevos_ralboard_t* lboard, int value, - bool widgets_ready) -{ - const settings_t *settings = config_get_ptr(); - if (!lboard) - return; + rcheevos_enforce_hardcore_settings(); + } - lboard->value = value; + /* deinit rewind */ + if (rewind_enable) + { +#ifdef HAVE_THREADS + if (!task_is_on_main_thread()) + { + /* have to "schedule" this. + * CMD_EVENT_REWIND_DEINIT should + * only be called on the main thread */ + rcheevos_locals.queued_command = CMD_EVENT_REWIND_DEINIT; + } + else +#endif + command_event(CMD_EVENT_REWIND_DEINIT, NULL); + } - if (widgets_ready && settings->bools.cheevos_visibility_lboard_trackers && - lboard->active_tracker_id && lboard->active_tracker_id <= 31) + locals->hardcore_being_enabled = false; + rc_client_set_hardcore_enabled(locals->client, 1); + } + else { - char buffer[32]; - rc_runtime_format_lboard_value(buffer, - sizeof(buffer), value, lboard->format); - gfx_widgets_set_leaderboard_display(lboard->active_tracker_id, - rcheevos_is_player_active() ? buffer : NULL); + /* pause hardcore */ + rc_client_set_hardcore_enabled(locals->client, 0); + + /* re-init rewind */ + if (rewind_enable) + { +#ifdef HAVE_THREADS + if (!task_is_on_main_thread()) + { + /* have to "schedule" this. + * CMD_EVENT_REWIND_INIT should + * only be called on the main thread */ + rcheevos_locals.queued_command = CMD_EVENT_REWIND_INIT; + } + else +#endif + command_event(CMD_EVENT_REWIND_INIT, NULL); + } } } -static void rcheevos_challenge_started( - rcheevos_racheevo_t* cheevo, int value, - bool widgets_ready) +void rcheevos_toggle_hardcore_paused(void) { settings_t* settings = config_get_ptr(); - if ( cheevo - && widgets_ready - && settings->bools.cheevos_challenge_indicators - && rcheevos_is_player_active()) - gfx_widgets_set_challenge_display(cheevo->id, cheevo->badge); + /* if hardcore mode is not enabled, we can't toggle whether its active */ + if (settings->bools.cheevos_hardcore_mode_enable) + rcheevos_toggle_hardcore_active(&rcheevos_locals); } -static void rcheevos_challenge_ended( - rcheevos_racheevo_t* cheevo, int value, - bool widgets_ready) +void rcheevos_hardcore_enabled_changed(void) { - if (cheevo && widgets_ready) - gfx_widgets_set_challenge_display(cheevo->id, NULL); + /* called whenever a setting that could potentially affect hardcore enabledness changes + * (i.e. cheevos_enable, hardcore_mode_enable) to synchronize the internal state to the configs. + * also called when a game is first loaded to synchronize the internal state to the configs. */ + const settings_t* settings = config_get_ptr(); + const bool enabled = settings + && settings->bools.cheevos_enable + && settings->bools.cheevos_hardcore_mode_enable; + const bool was_enabled = rcheevos_hardcore_active(); + + if (enabled != was_enabled) + { + rcheevos_toggle_hardcore_active(&rcheevos_locals); + } + else if (was_enabled && rcheevos_is_game_loaded()) + { + /* hardcore enabledness didn't change, but hardcore is active, so make + * sure to enforce the restrictions. */ + rcheevos_enforce_hardcore_settings(); + } } -static void rcheevos_progress_updated(rcheevos_locals_t* locals, - rcheevos_racheevo_t* cheevo, int value, - bool widgets_ready) +void rcheevos_validate_config_settings(void) { - settings_t* settings = config_get_ptr(); + int i; + const rc_disallowed_setting_t + *disallowed_settings = NULL; + core_option_manager_t* coreopts = NULL; + struct retro_system_info *sysinfo = + &runloop_state_get_ptr()->system.info; + const settings_t* settings = config_get_ptr(); + unsigned console_id; + + if (!rcheevos_hardcore_active()) + return; + + /* this adds a sleep to every frame. if the value is high enough that a + * single frame takes more than 1/60th of a second to evaluate, render, + * and sleep, then the real framerate is less than 60fps. with vsync on, + * it'll wait for the next vsync event, effectively halfing the fps. the + * auto setting should achieve the most accurate frame rate anyway, so + * disallow any manual values */ + if (!settings->bools.video_frame_delay_auto && settings->uints.video_frame_delay != 0) { + const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_MANUAL_FRAME_DELAY); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_MANUAL_FRAME_DELAY)); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; + } + + /* this specifies how many vsync events should occur for each rendered + * frame. if vsync is on for a 60Hz monitor and swap_interval is 2 (only + * update every other vsync), only 30fps will be generated. for a 144Hz + * monitor, a value of 2 will generate 72fps, which is still faster than + * the expected 60fps, so the user should really be using auto (0). + * allow 1 even though that could be potentially abused on monitors + * running at less than 60Hz because 1 is the default value - many users + * wouldn't know how to change it to auto. */ + if (settings->uints.video_swap_interval > 1) { + const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_VSYNC_SWAP_INTERVAL); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_VSYNC_SWAP_INTERVAL)); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; + } + + /* this causes N blank frames to be rendered between real frames, thus + * can slow down the actual number of rendered frames per second. */ + if (settings->uints.video_black_frame_insertion > 0) { + const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_BLACK_FRAME_INSERTION); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_BLACK_FRAME_INSERTION)); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; + } + + /* this causes N dupe frames to be rendered between real frames, for + the purposes of shaders that update faster than content. Thus + * can slow down the actual number of rendered frames per second. */ + if (settings->uints.video_shader_subframes > 1) { + const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SHADER_SUBFRAMES); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_SHADER_SUBFRAMES)); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; + } + + if (!sysinfo->library_name) + return; - if ( cheevo - && widgets_ready - && settings->bools.cheevos_visibility_progress_tracker - && rcheevos_is_player_active()) + disallowed_settings = rc_libretro_get_disallowed_settings(sysinfo->library_name); + if (disallowed_settings && retroarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) { - unsigned measured_value, measured_target; - if (rc_runtime_get_achievement_measured(&locals->runtime, cheevo->id, &measured_value, &measured_target)) + for (i = 0; i < (int)coreopts->size; i++) { - const float progress = ((float)measured_value / (float)measured_target); - if (progress > locals->tracker_progress) + const char* key = coreopts->opts[i].key; + const char* val = core_option_manager_get_val(coreopts, i); + if (!rc_libretro_is_setting_allowed(disallowed_settings, key, val)) { - locals->tracker_progress = progress; - locals->tracker_achievement = cheevo; + char buffer[128]; + snprintf(buffer, sizeof(buffer), + msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_SETTING_NOT_ALLOWED), key, val); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer); + snprintf(buffer, sizeof(buffer), + msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SETTING_NOT_ALLOWED), key, val); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; } } } -} -#endif - -int rcheevos_get_richpresence(char *s, size_t len) -{ - if (rcheevos_is_player_active()) { - int ret = rc_runtime_get_richpresence( - &rcheevos_locals.runtime, s, (unsigned)len, - &rcheevos_peek, NULL, NULL); - - if (ret <= 0 && rcheevos_locals.game.title) - { - /* TODO/FIXME - localize */ - size_t _len = strlcpy(s, "Playing ", len); - strlcpy(s + _len, rcheevos_locals.game.title, len - _len); - } - return ret; + const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); + console_id = game ? game->console_id : 0; } - if (rcheevos_locals.game.title) + + if (console_id && !rc_libretro_is_system_allowed(sysinfo->library_name, console_id)) { - /* TODO/FIXME - localize */ - size_t _len = strlcpy(s, "Spectating ", len); - return (int)strlcpy(s + _len, rcheevos_locals.game.title, len - _len); + char buffer[256]; + snprintf(buffer, sizeof(buffer), + msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SYSTEM_NOT_FOR_CORE), + rc_console_name(console_id), sysinfo->library_name); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; } - return 0; } -#if defined(HAVE_GFX_WIDGETS) -static void rcheevos_hide_leaderboard_trackers(void) +/***************************************************************************** +Test all the achievements (call once per frame). +*****************************************************************************/ +void rcheevos_test(void) { - unsigned i = 0; - unsigned trackers = rcheevos_locals.active_lboard_trackers; - if (trackers == 0) - return; - - do +#ifdef HAVE_THREADS + if (rcheevos_locals.queued_command != CMD_EVENT_NONE) { - if ((trackers & 1) != 0) - gfx_widgets_set_leaderboard_display(i, NULL); + if ((int)rcheevos_locals.queued_command != CMD_CHEEVOS_NON_COMMAND) + command_event(rcheevos_locals.queued_command, NULL); - i++; - trackers >>= 1; - } while (trackers != 0); + rcheevos_locals.queued_command = CMD_EVENT_NONE; - for (i = 0; i < rcheevos_locals.game.leaderboard_count; i++) - rcheevos_locals.game.leaderboards[i].active_tracker_id = 0; -} + if (rcheevos_locals.game_placard_requested) + { + rcheevos_locals.game_placard_requested = false; + rcheevos_show_game_placard(); + } + } #endif -#endif /* HAVE_RC_CLIENT */ + if (rcheevos_locals.memory.count != 0) + rc_client_do_frame(rcheevos_locals.client); + else + rc_client_idle(rcheevos_locals.client); +} -#ifdef HAVE_GFX_WIDGETS +void rcheevos_idle(void) +{ + rc_client_idle(rcheevos_locals.client); +} -static void rcheevos_hide_widgets(bool widgets_ready) +size_t rcheevos_get_serialize_size(void) { - /* Hide any visible trackers */ - if (widgets_ready) - { - gfx_widgets_clear_leaderboard_displays(); - gfx_widgets_clear_challenge_displays(); - gfx_widget_set_achievement_progress(NULL, NULL); - } + return rc_client_progress_size(rcheevos_locals.client); } -#endif +bool rcheevos_get_serialized_data(void* buffer) +{ + return (rc_client_serialize_progress(rcheevos_locals.client, (uint8_t*)buffer) == RC_OK); +} -void rcheevos_reset_game(bool widgets_ready) +bool rcheevos_set_serialized_data(void* buffer) { -#if defined(HAVE_GFX_WIDGETS) - /* Hide any visible trackers */ - rcheevos_hide_widgets(widgets_ready); -#endif + if (rcheevos_is_game_loaded() && buffer) + { + const int result = rc_client_deserialize_progress( + rcheevos_locals.client, (const uint8_t*)buffer); -#ifdef HAVE_RC_CLIENT - rc_client_reset(rcheevos_locals.client); -#else - rc_runtime_reset(&rcheevos_locals.runtime); -#endif + return (result == RC_OK); + } - /* Some cores reallocate memory on reset, - * make sure we update our pointers */ - if (rcheevos_locals.memory.total_size > 0) - rcheevos_init_memory(&rcheevos_locals); + return false; } -void rcheevos_refresh_memory(void) +void rcheevos_set_support_cheevos(bool state) { - if (rcheevos_locals.memory.total_size > 0) - rcheevos_init_memory(&rcheevos_locals); + rcheevos_locals.core_supports = state; } -bool rcheevos_hardcore_active(void) +bool rcheevos_get_support_cheevos(void) { -#ifdef HAVE_RC_CLIENT - /* normal hardcore check */ - if (rcheevos_locals.client && rc_client_get_hardcore_enabled(rcheevos_locals.client)) - return true; - - /* if we're trying to enable hardcore, pretend it's on so the caller can decide to disable - * it (by calling rcheevos_pause_hardcore) before we actually turn it on. */ - return rcheevos_locals.hardcore_being_enabled; -#else - return rcheevos_locals.hardcore_active; -#endif + return rcheevos_locals.core_supports; } -void rcheevos_pause_hardcore(void) +const char* rcheevos_get_hash(void) { -#ifdef HAVE_RC_CLIENT - rcheevos_locals.hardcore_allowed = false; -#endif - - if (rcheevos_hardcore_active()) - rcheevos_toggle_hardcore_paused(); + const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); + return game ? game->hash : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE); } -#if defined(HAVE_THREADS) && !defined(HAVE_RC_CLIENT) -static bool rcheevos_timer_check(void* userdata) +/* hooks for rc_hash library */ + +static void* rc_hash_handle_file_open(const char* path) { - retro_time_t stop_time = *(retro_time_t*)userdata; - retro_time_t now = cpu_features_get_time_usec(); - return (now < stop_time); + return intfstream_open_file(path, + RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); } -#endif -bool rcheevos_unload(void) +static void rc_hash_handle_file_seek( + void* file_handle, int64_t offset, int origin) { - settings_t* settings = config_get_ptr(); - const bool was_loaded = rcheevos_is_game_loaded(); - -#ifdef HAVE_GFX_WIDGETS - rcheevos_hide_widgets(gfx_widgets_ready()); - gfx_widget_set_cheevos_set_loading(false); -#endif - -#ifdef HAVE_RC_CLIENT - rc_client_unload_game(rcheevos_locals.client); -#else - /* Immediately mark the game as unloaded - so the ping thread will terminate normally */ - rcheevos_locals.game.id = -1; - rcheevos_locals.game.console_id = 0; - rcheevos_locals.game.hash = NULL; - - #ifdef HAVE_THREADS - if (rcheevos_locals.load_info.state < RCHEEVOS_LOAD_STATE_DONE && - rcheevos_locals.load_info.state != RCHEEVOS_LOAD_STATE_NONE) - { - /* allow up to 5 seconds for pending tasks to run */ - retro_time_t stop_time = cpu_features_get_time_usec() + 5000000; + intfstream_seek((intfstream_t*)file_handle, offset, origin); +} - rcheevos_locals.load_info.state = RCHEEVOS_LOAD_STATE_ABORTED; - CHEEVOS_LOG(RCHEEVOS_TAG "Asked the load tasks to terminate\n"); +static int64_t rc_hash_handle_file_tell(void* file_handle) +{ + return intfstream_tell((intfstream_t*)file_handle); +} - /* Wait for pending tasks to run */ - task_queue_wait(rcheevos_timer_check, &stop_time); - /* Clean up after completed tasks */ - task_queue_check(); - } - #endif -#endif +static size_t rc_hash_handle_file_read( + void* file_handle, void* buffer, size_t requested_bytes) +{ + return intfstream_read((intfstream_t*)file_handle, + buffer, requested_bytes); +} -#ifdef HAVE_THREADS - rcheevos_locals.queued_command = CMD_EVENT_NONE; - rcheevos_locals.game_placard_requested = false; -#endif +static void rc_hash_handle_file_close(void* file_handle) +{ + intfstream_close((intfstream_t*)file_handle); + CHEEVOS_FREE(file_handle); +} - if (rcheevos_locals.memory.count > 0) - rc_libretro_memory_destroy(&rcheevos_locals.memory); +#ifdef HAVE_CHD +static void* rc_hash_handle_chd_open_track( + const char* path, uint32_t track) +{ + cdfs_track_t* cdfs_track; - if (was_loaded) + switch (track) { -#ifndef HAVE_RC_CLIENT - unsigned count = 0; -#endif - -#ifdef HAVE_MENU - rcheevos_menu_reset_badges(); - - if (rcheevos_locals.menuitems) - { - CHEEVOS_FREE(rcheevos_locals.menuitems); - rcheevos_locals.menuitems = NULL; - rcheevos_locals.menuitem_capacity = - rcheevos_locals.menuitem_count = 0; - } -#endif - -#ifndef HAVE_RC_CLIENT - count = rcheevos_locals.game.achievement_count; - rcheevos_locals.game.achievement_count = 0; - if (rcheevos_locals.game.achievements) - { - rcheevos_racheevo_t* achievement = rcheevos_locals.game.achievements; - rcheevos_racheevo_t* end = achievement + count; - while (achievement < end) - { - CHEEVOS_FREE(achievement->title); - CHEEVOS_FREE(achievement->description); - CHEEVOS_FREE(achievement->badge); - CHEEVOS_FREE(achievement->memaddr); - - ++achievement; - } - - CHEEVOS_FREE(rcheevos_locals.game.achievements); - rcheevos_locals.game.achievements = NULL; - } - - count = rcheevos_locals.game.leaderboard_count; - rcheevos_locals.game.leaderboard_count = 0; - if (rcheevos_locals.game.leaderboards) - { - rcheevos_ralboard_t* lboard = rcheevos_locals.game.leaderboards; - rcheevos_ralboard_t* end = lboard + count; - while (lboard < end) - { - CHEEVOS_FREE(lboard->title); - CHEEVOS_FREE(lboard->description); - CHEEVOS_FREE(lboard->mem); - - ++lboard; - } - - CHEEVOS_FREE(rcheevos_locals.game.leaderboards); - rcheevos_locals.game.leaderboards = NULL; - } + case RC_HASH_CDTRACK_FIRST_DATA: + cdfs_track = cdfs_open_data_track(path); + break; - if (rcheevos_locals.game.title) - { - CHEEVOS_FREE(rcheevos_locals.game.title); - rcheevos_locals.game.title = NULL; - } + case RC_HASH_CDTRACK_LAST: + cdfs_track = cdfs_open_track(path, CHDSTREAM_TRACK_LAST); + break; - rcheevos_locals.loaded = false; - rcheevos_locals.hardcore_active = false; + case RC_HASH_CDTRACK_LARGEST: + cdfs_track = cdfs_open_track(path, CHDSTREAM_TRACK_PRIMARY); + break; - rc_libretro_hash_set_destroy(&rcheevos_locals.game.hashes); -#endif + default: + cdfs_track = cdfs_open_track(path, track); + break; } -#ifdef HAVE_THREADS - rcheevos_locals.queued_command = CMD_EVENT_NONE; -#endif - - if (!settings->arrays.cheevos_token[0]) + if (cdfs_track) { -#ifdef HAVE_RC_CLIENT - /* If the config-level token has been cleared, we need to re-login on - * loading the next game. Easiest way to do that is to destroy the client */ - rc_client_t* client = rcheevos_locals.client; - rcheevos_locals.client = NULL; + cdfs_file_t* file = (cdfs_file_t*)malloc(sizeof(cdfs_file_t)); + if (cdfs_open_file(file, cdfs_track, NULL)) + return file; /* ASSERT: file owns cdfs_track now */ - rc_client_destroy(client); -#else - /* If the config-level token has been cleared, - * we need to re-login on loading the next game */ - rcheevos_locals.token[0] = '\0'; -#endif + CHEEVOS_FREE(file); + cdfs_close_track(cdfs_track); /* ASSERT: this free()s cdfs_track */ } -#ifndef HAVE_RC_CLIENT - rc_runtime_destroy(&rcheevos_locals.runtime); - rcheevos_locals.load_info.state = RCHEEVOS_LOAD_STATE_NONE; -#endif - - return true; + return NULL; } -#ifndef HAVE_RC_CLIENT - -static void rcheevos_toggle_hardcore_achievements( - rcheevos_locals_t *locals) +static size_t rc_hash_handle_chd_read_sector( + void* track_handle, uint32_t sector, + void* buffer, size_t requested_bytes) { - const unsigned active_mask = - RCHEEVOS_ACTIVE_SOFTCORE | RCHEEVOS_ACTIVE_HARDCORE | RCHEEVOS_ACTIVE_UNSUPPORTED; - rcheevos_racheevo_t* cheevo = locals->game.achievements; - rcheevos_racheevo_t* stop = cheevo + locals->game.achievement_count; + cdfs_file_t* file = (cdfs_file_t*)track_handle; + uint32_t track_sectors = cdfs_get_num_sectors(file); - while (cheevo < stop) - { - if ((cheevo->active & active_mask) == RCHEEVOS_ACTIVE_HARDCORE) - { - /* player has unlocked achievement in non-hardcore, - * but has not unlocked in hardcore. Toggle state */ - if (locals->hardcore_active) - { - rc_runtime_activate_achievement(&locals->runtime, cheevo->id, cheevo->memaddr, NULL, 0); - CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u activated: %s\n", cheevo->id, cheevo->title); - } - else - { - rc_runtime_deactivate_achievement(&locals->runtime, cheevo->id); - CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u deactivated: %s\n", cheevo->id, cheevo->title); - } - } + sector -= cdfs_get_first_sector(file); + if (sector >= track_sectors) + return 0; - ++cheevo; - } + cdfs_seek_sector(file, sector); + return cdfs_read_file(file, buffer, requested_bytes); } -static void rcheevos_activate_leaderboards(void) +static uint32_t rc_hash_handle_chd_first_track_sector( + void* track_handle) { - unsigned i; - int result; - rcheevos_ralboard_t* leaderboard = rcheevos_locals.game.leaderboards; - const settings_t *settings = config_get_ptr(); + cdfs_file_t* file = (cdfs_file_t*)track_handle; + return cdfs_get_first_sector(file); +} - for (i = 0; i < rcheevos_locals.game.leaderboard_count; - ++i, ++leaderboard) +static void rc_hash_handle_chd_close_track(void* track_handle) +{ + cdfs_file_t* file = (cdfs_file_t*)track_handle; + if (file) { - if (!leaderboard->mem) - continue; - - result = rc_runtime_activate_lboard( - &rcheevos_locals.runtime, leaderboard->id, - leaderboard->mem, NULL, 0); - if (result != RC_OK) - { - char buffer[256]; - buffer[0] = '\0'; - /* TODO/FIXME - localize */ - snprintf(buffer, sizeof(buffer), - "Could not activate leaderboard %u \"%s\": %s", - leaderboard->id, leaderboard->title, rc_error_str(result)); - - if (settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - - CHEEVOS_ERR(RCHEEVOS_TAG "%s: mem %s\n", buffer, leaderboard->mem); - - CHEEVOS_FREE(leaderboard->mem); - leaderboard->mem = NULL; - } + cdfs_close_track(file->track); + cdfs_close_file(file); /* ASSERT: this does not free() file */ + CHEEVOS_FREE(file); } } -static void rcheevos_deactivate_leaderboards(void) -{ - rcheevos_ralboard_t* lboard = rcheevos_locals.game.leaderboards; - rcheevos_ralboard_t* stop = lboard + - rcheevos_locals.game.leaderboard_count; - -#if defined(HAVE_GFX_WIDGETS) - /* Hide any visible trackers */ - rcheevos_hide_leaderboard_trackers(); #endif - for (; lboard < stop; ++lboard) - { - if (lboard->mem) - { - rc_runtime_deactivate_lboard(&rcheevos_locals.runtime, - lboard->id); - } - } -} +static void rc_hash_reset_cdreader_hooks(void); -void rcheevos_leaderboard_trackers_visibility_changed(void) +static void* rc_hash_handle_cd_open_track( + const char* path, uint32_t track) { - const settings_t* settings = config_get_ptr(); + struct rc_hash_filereader filereader; + struct rc_hash_cdreader cdreader; + + memset(&filereader, 0, sizeof(filereader)); + filereader.open = rc_hash_handle_file_open; + filereader.seek = rc_hash_handle_file_seek; + filereader.tell = rc_hash_handle_file_tell; + filereader.read = rc_hash_handle_file_read; + filereader.close = rc_hash_handle_file_close; + rc_hash_init_custom_filereader(&filereader); - if (rcheevos_locals.loaded) + if (string_is_equal_noncase(path_get_extension(path), "chd")) { -#if defined(HAVE_GFX_WIDGETS) - if (!settings->bools.cheevos_visibility_lboard_trackers) - { - /* Hide any visible trackers */ - rcheevos_hide_leaderboard_trackers(); - } - else - { - unsigned i; - rc_runtime_lboard_t* lboard = rcheevos_locals.runtime.lboards; - for (i = 0; i < rcheevos_locals.runtime.lboard_count; ++i, ++lboard) - { - if (!lboard->lboard) - continue; +#ifdef HAVE_CHD + /* special handlers for CHD file */ + memset(&cdreader, 0, sizeof(cdreader)); + cdreader.open_track = rc_hash_handle_cd_open_track; + cdreader.read_sector = rc_hash_handle_chd_read_sector; + cdreader.close_track = rc_hash_handle_chd_close_track; + cdreader.first_track_sector = rc_hash_handle_chd_first_track_sector; + rc_hash_init_custom_cdreader(&cdreader); - if (lboard->lboard->state == RC_LBOARD_STATE_STARTED) - { - rcheevos_ralboard_t* ralboard = rcheevos_find_lboard(lboard->id); - if (ralboard && !ralboard->active_tracker_id) - { - /* mark the leaderboard as needing a tracker assigned so we can check for merging later */ - ralboard->active_tracker_id = 0xFF; - rcheevos_locals.assign_new_trackers = true; - } - } - else - { - rcheevos_ralboard_t* ralboard = rcheevos_find_lboard(lboard->id); - if (ralboard && ralboard->active_tracker_id) - rcheevos_hide_leaderboard_tracker(&rcheevos_locals, ralboard); - } - } - } -#endif - } -} - -#else /* HAVE_RC_CLIENT */ - -void rcheevos_leaderboard_trackers_visibility_changed(void) -{ -#if defined(HAVE_GFX_WIDGETS) - const settings_t* settings = config_get_ptr(); - if (!settings->bools.cheevos_visibility_lboard_trackers) - { - /* Hide any visible trackers */ - gfx_widgets_clear_leaderboard_displays(); - } - else - { - /* No way to immediately request trackers be reshown, but they - * will reappear the next time they're updated */ - } -#endif -} - -#endif /* HAVE_RC_CLIENT */ - -static void rcheevos_enforce_hardcore_settings(void) -{ - /* disable slowdown */ - runloop_state_get_ptr()->flags &= ~RUNLOOP_FLAG_SLOWMOTION; -} - -static void rcheevos_toggle_hardcore_active(rcheevos_locals_t* locals) -{ - settings_t* settings = config_get_ptr(); - bool rewind_enable = settings->bools.rewind_enable; - const bool was_enabled = rcheevos_hardcore_active(); - - if (!was_enabled) - { -#ifdef HAVE_RC_CLIENT - locals->hardcore_being_enabled = true; - locals->hardcore_allowed = true; -#else - /* Activate hardcore */ - locals->hardcore_active = true; -#endif - - /* If one or more invalid settings is enabled, abort*/ - rcheevos_validate_config_settings(); -#ifdef HAVE_RC_CLIENT - if (!locals->hardcore_allowed) - { - locals->hardcore_being_enabled = false; - return; - } -#else - if (!locals->hardcore_active) - return; -#endif - -#ifdef HAVE_CHEATS - /* If one or more emulator managed cheats is active, abort */ - cheat_manager_apply_cheats(); - #ifdef HAVE_RC_CLIENT - if (!locals->hardcore_allowed) - { - locals->hardcore_being_enabled = false; - return; - } -#else - if (!locals->hardcore_active) - return; - #endif -#endif - - if (rcheevos_is_game_loaded()) - { - const char* msg = msg_hash_to_str( - MSG_CHEEVOS_HARDCORE_MODE_ENABLE); - CHEEVOS_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 0, 3 * 60, true, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - - rcheevos_enforce_hardcore_settings(); - -#ifndef HAVE_RC_CLIENT - /* Reactivate leaderboards */ - rcheevos_activate_leaderboards(); - - /* reset the game */ - command_event(CMD_EVENT_RESET, NULL); -#endif - } - - /* deinit rewind */ - if (rewind_enable) - { -#ifdef HAVE_THREADS - if (!task_is_on_main_thread()) - { - /* have to "schedule" this. - * CMD_EVENT_REWIND_DEINIT should - * only be called on the main thread */ - rcheevos_locals.queued_command = CMD_EVENT_REWIND_DEINIT; - } - else -#endif - command_event(CMD_EVENT_REWIND_DEINIT, NULL); - } - -#ifdef HAVE_RC_CLIENT - locals->hardcore_being_enabled = false; - rc_client_set_hardcore_enabled(locals->client, 1); -#endif - } - else - { - /* pause hardcore */ -#ifdef HAVE_RC_CLIENT - rc_client_set_hardcore_enabled(locals->client, 0); -#else - locals->hardcore_active = false; - - if (locals->loaded) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Hardcore paused\n"); - - /* deactivate leaderboards */ - rcheevos_deactivate_leaderboards(); - } -#endif - - /* re-init rewind */ - if (rewind_enable) - { -#ifdef HAVE_THREADS - if (!task_is_on_main_thread()) - { - /* have to "schedule" this. - * CMD_EVENT_REWIND_INIT should - * only be called on the main thread */ - rcheevos_locals.queued_command = CMD_EVENT_REWIND_INIT; - } - else -#endif - command_event(CMD_EVENT_REWIND_INIT, NULL); - } - } - -#ifndef HAVE_RC_CLIENT - if (locals->loaded) - rcheevos_toggle_hardcore_achievements(locals); -#endif -} - -void rcheevos_toggle_hardcore_paused(void) -{ - settings_t* settings = config_get_ptr(); - /* if hardcore mode is not enabled, we can't toggle whether its active */ - if (settings->bools.cheevos_hardcore_mode_enable) - rcheevos_toggle_hardcore_active(&rcheevos_locals); -} - -void rcheevos_hardcore_enabled_changed(void) -{ - /* called whenever a setting that could potentially affect hardcore enabledness changes - * (i.e. cheevos_enable, hardcore_mode_enable) to synchronize the internal state to the configs. - * also called when a game is first loaded to synchronize the internal state to the configs. */ - const settings_t* settings = config_get_ptr(); - const bool enabled = settings - && settings->bools.cheevos_enable - && settings->bools.cheevos_hardcore_mode_enable; - const bool was_enabled = rcheevos_hardcore_active(); - - if (enabled != was_enabled) - { - rcheevos_toggle_hardcore_active(&rcheevos_locals); - } - else if (was_enabled && rcheevos_is_game_loaded()) - { - /* hardcore enabledness didn't change, but hardcore is active, so make - * sure to enforce the restrictions. */ - rcheevos_enforce_hardcore_settings(); - } -} - -void rcheevos_validate_config_settings(void) -{ - int i; - const rc_disallowed_setting_t - *disallowed_settings = NULL; - core_option_manager_t* coreopts = NULL; - struct retro_system_info *sysinfo = - &runloop_state_get_ptr()->system.info; - const settings_t* settings = config_get_ptr(); - unsigned console_id; - - if (!rcheevos_hardcore_active()) - return; - - /* this adds a sleep to every frame. if the value is high enough that a - * single frame takes more than 1/60th of a second to evaluate, render, - * and sleep, then the real framerate is less than 60fps. with vsync on, - * it'll wait for the next vsync event, effectively halfing the fps. the - * auto setting should achieve the most accurate frame rate anyway, so - * disallow any manual values */ - if (!settings->bools.video_frame_delay_auto && settings->uints.video_frame_delay != 0) { - const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_MANUAL_FRAME_DELAY); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_MANUAL_FRAME_DELAY)); - rcheevos_pause_hardcore(); - - runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - return; - } - - /* this specifies how many vsync events should occur for each rendered - * frame. if vsync is on for a 60Hz monitor and swap_interval is 2 (only - * update every other vsync), only 30fps will be generated. for a 144Hz - * monitor, a value of 2 will generate 72fps, which is still faster than - * the expected 60fps, so the user should really be using auto (0). - * allow 1 even though that could be potentially abused on monitors - * running at less than 60Hz because 1 is the default value - many users - * wouldn't know how to change it to auto. */ - if (settings->uints.video_swap_interval > 1) { - const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_VSYNC_SWAP_INTERVAL); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_VSYNC_SWAP_INTERVAL)); - rcheevos_pause_hardcore(); - - runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - return; - } - - /* this causes N blank frames to be rendered between real frames, thus - * can slow down the actual number of rendered frames per second. */ - if (settings->uints.video_black_frame_insertion > 0) { - const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_BLACK_FRAME_INSERTION); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_BLACK_FRAME_INSERTION)); - rcheevos_pause_hardcore(); - - runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - return; - } - - /* this causes N dupe frames to be rendered between real frames, for - the purposes of shaders that update faster than content. Thus - * can slow down the actual number of rendered frames per second. */ - if (settings->uints.video_shader_subframes > 1) { - const char* error = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SHADER_SUBFRAMES); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_SHADER_SUBFRAMES)); - rcheevos_pause_hardcore(); - - runloop_msg_queue_push(error, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - return; - } - - if (!sysinfo->library_name) - return; - - disallowed_settings = rc_libretro_get_disallowed_settings(sysinfo->library_name); - if (disallowed_settings && retroarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) - { - for (i = 0; i < (int)coreopts->size; i++) - { - const char* key = coreopts->opts[i].key; - const char* val = core_option_manager_get_val(coreopts, i); - if (!rc_libretro_is_setting_allowed(disallowed_settings, key, val)) - { - char buffer[128]; - snprintf(buffer, sizeof(buffer), - msg_hash_to_str_us(MSG_CHEEVOS_HARDCORE_PAUSED_SETTING_NOT_ALLOWED), key, val); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer); - snprintf(buffer, sizeof(buffer), - msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SETTING_NOT_ALLOWED), key, val); - rcheevos_pause_hardcore(); - - runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - return; - } - } - } - -#ifdef HAVE_RC_CLIENT - { - const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); - console_id = game ? game->console_id : 0; - } -#else - console_id = rcheevos_locals.game.console_id; -#endif - - if (console_id && !rc_libretro_is_system_allowed(sysinfo->library_name, console_id)) - { - char buffer[256]; - snprintf(buffer, sizeof(buffer), - msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SYSTEM_NOT_FOR_CORE), - rc_console_name(console_id), sysinfo->library_name); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer); - rcheevos_pause_hardcore(); - - runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - return; - } -} - -#ifndef HAVE_RC_CLIENT - -static void rcheevos_runtime_event_handler( - const rc_runtime_event_t* runtime_event) -{ -#if defined(HAVE_GFX_WIDGETS) - bool widgets_ready = gfx_widgets_ready(); -#else - bool widgets_ready = false; -#endif - - switch (runtime_event->type) - { -#if defined(HAVE_GFX_WIDGETS) - case RC_RUNTIME_EVENT_LBOARD_UPDATED: - rcheevos_lboard_updated( - rcheevos_find_lboard(runtime_event->id), - runtime_event->value, widgets_ready); - break; - - case RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED: - rcheevos_challenge_started( - rcheevos_find_cheevo(runtime_event->id), - runtime_event->value, widgets_ready); - break; - - case RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED: - rcheevos_challenge_ended( - rcheevos_find_cheevo(runtime_event->id), - runtime_event->value, widgets_ready); - break; - - case RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED: - rcheevos_progress_updated(&rcheevos_locals, - rcheevos_find_cheevo(runtime_event->id), - runtime_event->value, widgets_ready); - break; -#endif - - case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED: - rcheevos_award_achievement( - &rcheevos_locals, - rcheevos_find_cheevo(runtime_event->id), widgets_ready); - break; - - case RC_RUNTIME_EVENT_LBOARD_STARTED: - rcheevos_lboard_started( - rcheevos_find_lboard(runtime_event->id), - runtime_event->value, widgets_ready); - break; - - case RC_RUNTIME_EVENT_LBOARD_CANCELED: - rcheevos_lboard_canceled( - rcheevos_find_lboard(runtime_event->id), - widgets_ready); - break; - - case RC_RUNTIME_EVENT_LBOARD_TRIGGERED: - rcheevos_lboard_submit( - &rcheevos_locals, - rcheevos_find_lboard(runtime_event->id), - runtime_event->value, widgets_ready); - break; - - case RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED: - rcheevos_achievement_disabled( - rcheevos_find_cheevo(runtime_event->id), - runtime_event->value); - break; - - case RC_RUNTIME_EVENT_LBOARD_DISABLED: - rcheevos_lboard_disabled( - rcheevos_find_lboard(runtime_event->id), - runtime_event->value); - break; - - default: - break; - } -} - -static int rcheevos_runtime_address_validator(uint32_t address) -{ - return rc_libretro_memory_find( - &rcheevos_locals.memory, address) != NULL; -} - -static void rcheevos_validate_memrefs(rcheevos_locals_t* locals) -{ - if (!rcheevos_init_memory(locals)) - { - const settings_t* settings = config_get_ptr(); - /* some cores (like Mupen64-Plus) don't expose the memory until the - * first call to retro_run. in that case, there will be a total_size - * of memory reported by the core, but init will return false, as - * all of the pointers were null. if we're still loading the game, - * just reset the memory count and we'll re-evaluate in - * rcheevos_test() - */ - if (!locals->loaded) - { - /* If no memory was exposed, report the error now - * instead of waiting */ - if (locals->memory.total_size != 0) - { - locals->memory.count = 0; - return; - } - } - - rcheevos_locals.core_supports = false; - - CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n"); - - if (settings && settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), - 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - - rcheevos_unload(); - rcheevos_pause_hardcore(); - return; - } - - rc_runtime_validate_addresses(&locals->runtime, - rcheevos_runtime_event_handler, - rcheevos_runtime_address_validator); -} - -#endif /* HAVE_RC_CLIENT */ - -/***************************************************************************** -Test all the achievements (call once per frame). -*****************************************************************************/ -void rcheevos_test(void) -{ -#ifdef HAVE_THREADS - if (rcheevos_locals.queued_command != CMD_EVENT_NONE) - { - if ((int)rcheevos_locals.queued_command != CMD_CHEEVOS_NON_COMMAND) - command_event(rcheevos_locals.queued_command, NULL); - - rcheevos_locals.queued_command = CMD_EVENT_NONE; - - if (rcheevos_locals.game_placard_requested) - { - rcheevos_locals.game_placard_requested = false; - rcheevos_show_game_placard(); - } - } -#endif - -#ifdef HAVE_RC_CLIENT - if (rcheevos_locals.memory.count != 0) - rc_client_do_frame(rcheevos_locals.client); - else - rc_client_idle(rcheevos_locals.client); -#else - if (!rcheevos_locals.loaded) - return; - - /* We were unable to initialize memory earlier, try now */ - if (rcheevos_locals.memory.count == 0) - { - rcheevos_validate_memrefs(&rcheevos_locals); - - /* rcheevos_validate_memrefs may decide the core doesn't support achievements and - * disable them. if so, bail. */ - if (!rcheevos_locals.loaded) - return; - } - - rc_runtime_do_frame(&rcheevos_locals.runtime, - &rcheevos_runtime_event_handler, rcheevos_peek, NULL, 0); - - #ifdef HAVE_GFX_WIDGETS - if (rcheevos_locals.assign_new_trackers) - { - if (gfx_widgets_ready()) - rcheevos_assign_leaderboard_tracker_ids(&rcheevos_locals); - - rcheevos_locals.assign_new_trackers = false; - } - - if (rcheevos_locals.tracker_achievement != NULL) - { - char buffer[32] = ""; - if (rc_runtime_format_achievement_measured(&rcheevos_locals.runtime, - rcheevos_locals.tracker_achievement->id, buffer, sizeof(buffer))) - { - gfx_widget_set_achievement_progress(rcheevos_locals.tracker_achievement->badge, buffer); - } - - rcheevos_locals.tracker_achievement = NULL; - rcheevos_locals.tracker_progress = 0.0; - } - #endif - - /* We processed a frame - if there's a pause delay in effect, process it */ - if (rcheevos_locals.unpaused_frames > 0) - rcheevos_locals.unpaused_frames--; - -#endif /* HAVE_RC_CLIENT */ -} - -void rcheevos_idle(void) -{ -#ifdef HAVE_RC_CLIENT - rc_client_idle(rcheevos_locals.client); -#endif -} - -size_t rcheevos_get_serialize_size(void) -{ -#ifdef HAVE_RC_CLIENT - return rc_client_progress_size(rcheevos_locals.client); -#else - if (!rcheevos_locals.loaded) - return 0; - return rc_runtime_progress_size(&rcheevos_locals.runtime, NULL); -#endif -} - -bool rcheevos_get_serialized_data(void* buffer) -{ -#ifdef HAVE_RC_CLIENT - return (rc_client_serialize_progress(rcheevos_locals.client, (uint8_t*)buffer) == RC_OK); -#else - if (!rcheevos_locals.loaded) - return false; - return (rc_runtime_serialize_progress( - buffer, &rcheevos_locals.runtime, NULL) == RC_OK); -#endif -} - -bool rcheevos_set_serialized_data(void* buffer) -{ - if (rcheevos_is_game_loaded() && buffer) - { -#ifdef HAVE_RC_CLIENT - const int result = rc_client_deserialize_progress( - rcheevos_locals.client, (const uint8_t*)buffer); -#else - const int result = rc_runtime_deserialize_progress( - &rcheevos_locals.runtime, (const unsigned char*)buffer, NULL); - - #if defined(HAVE_GFX_WIDGETS) - if (gfx_widgets_ready() && rcheevos_is_player_active()) - { - settings_t* settings = config_get_ptr(); - - if (settings->bools.cheevos_visibility_lboard_trackers) - rcheevos_leaderboard_trackers_visibility_changed(); - - if (settings->bools.cheevos_challenge_indicators) - { - unsigned i; - rc_runtime_trigger_t* cheevo = rcheevos_locals.runtime.triggers; - for (i = 0; i < rcheevos_locals.runtime.trigger_count; ++i, ++cheevo) - { - if (!cheevo->trigger) - continue; - - if (cheevo->trigger->state == RC_TRIGGER_STATE_PRIMED) - { - rcheevos_racheevo_t* racheevo = rcheevos_find_cheevo(cheevo->id); - if (racheevo != NULL) - gfx_widgets_set_challenge_display(racheevo->id, racheevo->badge); - } - else - { - gfx_widgets_set_challenge_display(cheevo->id, NULL); - } - } - } - - if (settings->bools.cheevos_visibility_progress_tracker) - gfx_widget_set_achievement_progress(NULL, NULL); - } - #endif -#endif /* HAVE_RC_CLIENT */ - - return (result == RC_OK); - } - - return false; -} - -void rcheevos_set_support_cheevos(bool state) -{ - rcheevos_locals.core_supports = state; -} - -bool rcheevos_get_support_cheevos(void) -{ - return rcheevos_locals.core_supports; -} - -const char* rcheevos_get_hash(void) -{ -#ifdef HAVE_RC_CLIENT - const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); - return game ? game->hash : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE); -#else - return (rcheevos_locals.game.hash != NULL) ? - rcheevos_locals.game.hash : - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE); -#endif -} - -/* hooks for rc_hash library */ - -static void* rc_hash_handle_file_open(const char* path) -{ - return intfstream_open_file(path, - RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); -} - -static void rc_hash_handle_file_seek( - void* file_handle, int64_t offset, int origin) -{ - intfstream_seek((intfstream_t*)file_handle, offset, origin); -} - -static int64_t rc_hash_handle_file_tell(void* file_handle) -{ - return intfstream_tell((intfstream_t*)file_handle); -} - -static size_t rc_hash_handle_file_read( - void* file_handle, void* buffer, size_t requested_bytes) -{ - return intfstream_read((intfstream_t*)file_handle, - buffer, requested_bytes); -} - -static void rc_hash_handle_file_close(void* file_handle) -{ - intfstream_close((intfstream_t*)file_handle); - CHEEVOS_FREE(file_handle); -} - -#ifdef HAVE_CHD -static void* rc_hash_handle_chd_open_track( - const char* path, uint32_t track) -{ - cdfs_track_t* cdfs_track; - - switch (track) - { - case RC_HASH_CDTRACK_FIRST_DATA: - cdfs_track = cdfs_open_data_track(path); - break; - - case RC_HASH_CDTRACK_LAST: - cdfs_track = cdfs_open_track(path, CHDSTREAM_TRACK_LAST); - break; - - case RC_HASH_CDTRACK_LARGEST: - cdfs_track = cdfs_open_track(path, CHDSTREAM_TRACK_PRIMARY); - break; - - default: - cdfs_track = cdfs_open_track(path, track); - break; - } - - if (cdfs_track) - { - cdfs_file_t* file = (cdfs_file_t*)malloc(sizeof(cdfs_file_t)); - if (cdfs_open_file(file, cdfs_track, NULL)) - return file; /* ASSERT: file owns cdfs_track now */ - - CHEEVOS_FREE(file); - cdfs_close_track(cdfs_track); /* ASSERT: this free()s cdfs_track */ - } - - return NULL; -} - -static size_t rc_hash_handle_chd_read_sector( - void* track_handle, uint32_t sector, - void* buffer, size_t requested_bytes) -{ - cdfs_file_t* file = (cdfs_file_t*)track_handle; - uint32_t track_sectors = cdfs_get_num_sectors(file); - - sector -= cdfs_get_first_sector(file); - if (sector >= track_sectors) - return 0; - - cdfs_seek_sector(file, sector); - return cdfs_read_file(file, buffer, requested_bytes); -} - -static uint32_t rc_hash_handle_chd_first_track_sector( - void* track_handle) -{ - cdfs_file_t* file = (cdfs_file_t*)track_handle; - return cdfs_get_first_sector(file); -} - -static void rc_hash_handle_chd_close_track(void* track_handle) -{ - cdfs_file_t* file = (cdfs_file_t*)track_handle; - if (file) - { - cdfs_close_track(file->track); - cdfs_close_file(file); /* ASSERT: this does not free() file */ - CHEEVOS_FREE(file); - } -} - -#endif - -static void rc_hash_reset_cdreader_hooks(void); - -static void* rc_hash_handle_cd_open_track( - const char* path, uint32_t track) -{ - struct rc_hash_filereader filereader; - struct rc_hash_cdreader cdreader; - - memset(&filereader, 0, sizeof(filereader)); - filereader.open = rc_hash_handle_file_open; - filereader.seek = rc_hash_handle_file_seek; - filereader.tell = rc_hash_handle_file_tell; - filereader.read = rc_hash_handle_file_read; - filereader.close = rc_hash_handle_file_close; - rc_hash_init_custom_filereader(&filereader); - - if (string_is_equal_noncase(path_get_extension(path), "chd")) - { -#ifdef HAVE_CHD - /* special handlers for CHD file */ - memset(&cdreader, 0, sizeof(cdreader)); - cdreader.open_track = rc_hash_handle_cd_open_track; - cdreader.read_sector = rc_hash_handle_chd_read_sector; - cdreader.close_track = rc_hash_handle_chd_close_track; - cdreader.first_track_sector = rc_hash_handle_chd_first_track_sector; - rc_hash_init_custom_cdreader(&cdreader); - - return rc_hash_handle_chd_open_track(path, track); -#else - CHEEVOS_LOG(RCHEEVOS_TAG "Cannot generate hash from CHD without HAVE_CHD compile flag\n"); - return NULL; -#endif - } - else - { - /* not a CHD file, use the default handlers */ - rc_hash_get_default_cdreader(&cdreader); - rc_hash_reset_cdreader_hooks(); - return cdreader.open_track(path, track); - } -} - -static void rc_hash_reset_cdreader_hooks(void) -{ - struct rc_hash_cdreader cdreader; - rc_hash_get_default_cdreader(&cdreader); - cdreader.open_track = rc_hash_handle_cd_open_track; - rc_hash_init_custom_cdreader(&cdreader); -} - -/* end hooks */ - -#ifdef HAVE_RC_CLIENT - -static void rcheevos_show_game_placard(void) -{ - size_t len; - char msg[256]; - rc_client_user_game_summary_t summary; - const settings_t* settings = config_get_ptr(); - const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); - if (!game) /* Make sure there's actually a game loaded */ - return; - - rc_client_get_user_game_summary(rcheevos_locals.client, &summary); - - if (summary.num_core_achievements == 0) - { - if (summary.num_unofficial_achievements == 0) - len = snprintf(msg, sizeof(msg), "%s", msg_hash_to_str(MSG_CHEEVOS_GAME_HAS_NO_ACHIEVEMENTS)); - else - len = snprintf(msg, sizeof(msg), - msg_hash_to_str(MSG_CHEEVOS_UNOFFICIAL_ACHIEVEMENTS_ACTIVATED), - (int)summary.num_unofficial_achievements); - } - else if (rc_client_get_encore_mode_enabled(rcheevos_locals.client)) - len = snprintf(msg, sizeof(msg), - msg_hash_to_str(MSG_CHEEVOS_ALL_ACHIEVEMENTS_ACTIVATED), - (int)summary.num_core_achievements); - else - len = snprintf(msg, sizeof(msg), - msg_hash_to_str(MSG_CHEEVOS_NUMBER_ACHIEVEMENTS_UNLOCKED), - (int)summary.num_unlocked_achievements, - (int)summary.num_core_achievements); - - if (summary.num_unsupported_achievements) - { - if (len < sizeof(msg) - 4) - { - msg[len++] = ' '; - msg[len++] = '('; - - len += snprintf(&msg[len], sizeof(msg) - len, - msg_hash_to_str(MSG_CHEEVOS_UNSUPPORTED_COUNT), - (int)summary.num_unsupported_achievements); - - if (len < sizeof(msg) - 1) - { - msg[len++] = ')'; - msg[len] = '\0'; - } - } - } - - msg[sizeof(msg) - 1] = 0; - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg); - - if ( settings->uints.cheevos_visibility_summary == RCHEEVOS_SUMMARY_ALLGAMES - || (settings->uints.cheevos_visibility_summary == RCHEEVOS_SUMMARY_HASCHEEVOS - && (summary.num_core_achievements || summary.num_unofficial_achievements))) - { -#if defined (HAVE_GFX_WIDGETS) - if (gfx_widgets_ready()) - { - char badge_name[32]; - size_t _len = strlcpy(badge_name, "i", sizeof(badge_name)); - _len += strlcpy(badge_name + _len, game->badge_name, - sizeof(badge_name) - _len); - gfx_widgets_push_achievement(game->title, msg, badge_name); - } - else -#endif - runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } -} - -static uint32_t rcheevos_client_read_memory(uint32_t address, - uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) -{ - return rc_libretro_memory_read(&rcheevos_locals.memory, address, buffer, num_bytes); -} - -static uint32_t rcheevos_client_read_memory_dummy(uint32_t address, - uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) -{ - /* pretend the memory exists */ - memset(buffer, 0, num_bytes); - return num_bytes; -} - -static uint32_t rcheevos_client_read_memory_unavailable(uint32_t address, - uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) -{ - return 0; -} - -static uint32_t rcheevos_client_read_memory_uninitialized(uint32_t address, - uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) -{ - /* we can't initialize the memory until we know which console the game - * is associated to. This happens internally to the load game sequence, - * so we have to intercept the first attempt to read memory. - */ - if (rcheevos_init_memory(&rcheevos_locals)) - { - rc_client_set_read_memory_function(client, rcheevos_client_read_memory); - return rcheevos_client_read_memory(address, buffer, num_bytes, client); - } - - /* some cores (like Mupen64-Plus) don't expose the memory until the - * first call to retro_run. in that case, there will be a total_size - * of memory reported by the core, but init will return false, as all - * of the pointers were null. if we're still loading the game, return - * dummy memory and we'll re-evaluate in rcheevos_client_load_game_callback(). - */ - if (!rcheevos_is_game_loaded()) - { - rc_client_set_read_memory_function(client, rcheevos_client_read_memory_dummy); - return rcheevos_client_read_memory_dummy(address, buffer, num_bytes, client); - } - - /* game loaded, but no memory available */ - rc_client_set_read_memory_function(client, rcheevos_client_read_memory_unavailable); - return rcheevos_client_read_memory_unavailable(address, buffer, num_bytes, client); -} - -static void rcheevos_client_login_callback(int result, - const char* error_message, rc_client_t* client, void* userdata) -{ - const rc_client_user_t* user; - - if (result != RC_OK) - { - char msg[256]; - size_t _len = strlcpy(msg, "RetroAchievements login failed: ", - sizeof(msg)); - _len += strlcpy(msg + _len, error_message, sizeof(msg) - _len); - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg); - runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - return; - } - - user = rc_client_get_user_info(client); - if (!user) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Login failed without error\n"); - } - else - { - settings_t* settings = config_get_ptr(); - - if (user->token[0]) - { - /* store the token, clear the password */ - strlcpy(settings->arrays.cheevos_token, user->token, - sizeof(settings->arrays.cheevos_token)); - settings->arrays.cheevos_password[0] = '\0'; - } - else - { - CHEEVOS_LOG(RCHEEVOS_TAG "Login did not return token\n"); - } - - /* show notification (if enabled) */ - if (settings->bools.cheevos_visibility_account) - { - char msg[128]; - snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_CHEEVOS_LOGGED_IN_AS_USER), - user->display_name); - runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - } -} - -static void rcheevos_finalize_game_load(rc_client_t* client) -{ - rcheevos_client_download_achievement_badges(client); - - if (!rc_client_is_processing_required(client)) - { - CHEEVOS_LOG(RCHEEVOS_TAG "No runtime logic for game, pausing hardcore\n"); - rcheevos_pause_hardcore(); - } -} - -static void rcheevos_client_load_game_callback(int result, - const char* error_message, rc_client_t* client, void* userdata) -{ - const settings_t* settings = config_get_ptr(); - const rc_client_game_t* game = rc_client_get_game_info(client); - char msg[256]; - -#if defined(HAVE_GFX_WIDGETS) - gfx_widget_set_cheevos_set_loading(false); -#endif - - if (result != RC_OK || !game) - { - if (result == RC_NO_GAME_LOADED) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Game not recognized, pausing hardcore\n"); - rcheevos_pause_hardcore(); - - if (!settings->bools.cheevos_verbose_enable) - return; - - snprintf(msg, sizeof(msg), "%s", msg_hash_to_str(MSG_CHEEVOS_GAME_NOT_IDENTIFIED)); - } - else - { - if (!error_message) - error_message = "Unknown error"; - - snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_CHEEVOS_GAME_LOAD_FAILED), error_message); - CHEEVOS_LOG(RCHEEVOS_TAG "Game load failed: %s\n", error_message); - } - - runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - return; - } - - if (rcheevos_locals.memory.total_size == 0) - { - /* make one last attempt to initialize memory */ - if (!rcheevos_init_memory(&rcheevos_locals)) - { - rcheevos_locals.core_supports = false; - - CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n"); - - if (settings && settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), - 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); - - rcheevos_unload(); - rcheevos_pause_hardcore(); - return; - } - - /* have valid memory now. use the real read function */ - rc_client_set_read_memory_function(client, rcheevos_client_read_memory); - } - -#ifdef HAVE_THREADS - if (!video_driver_is_threaded() && !task_is_on_main_thread()) - { - /* have to "schedule" this. game image should not be loaded on background thread */ - rcheevos_locals.queued_command = CMD_CHEEVOS_NON_COMMAND; - rcheevos_locals.game_placard_requested = true; - } - else -#endif - rcheevos_show_game_placard(); - - rcheevos_finalize_game_load(client); - - if (rcheevos_hardcore_active()) - { - /* hardcore is active. we're going to start processing - * achievements. make sure restrictions are enforced */ - rcheevos_validate_config_settings(); - rcheevos_enforce_hardcore_settings(); - } - else - { -#if HAVE_REWIND - /* Re-enable rewind. Additional space will be allocated for the achievement state data */ - if (settings->bools.rewind_enable) - { -#ifdef HAVE_THREADS - if (!task_is_on_main_thread()) - { - /* Have to "schedule" this. CMD_EVENT_REWIND_REINIT should - * only be called on the main thread */ - rcheevos_locals.queued_command = CMD_EVENT_REWIND_REINIT; - } - else -#endif - command_event(CMD_EVENT_REWIND_REINIT, NULL); - } -#endif - } - - rcheevos_spectating_changed(); /* synchronize spectating state */ -} - -static rc_clock_t rcheevos_client_get_time_millisecs(const rc_client_t* client) -{ - return cpu_features_get_time_usec() / 1000; -} - -#else /* !HAVE_RC_CLIENT */ - -void rcheevos_show_mastery_placard(void) -{ - char title[256]; - const settings_t* settings = config_get_ptr(); - - if (rcheevos_locals.game.mastery_placard_shown) - return; - - rcheevos_locals.game.mastery_placard_shown = true; - - snprintf(title, sizeof(title), - msg_hash_to_str(rcheevos_locals.hardcore_active - ? MSG_CHEEVOS_MASTERED_GAME - : MSG_CHEEVOS_COMPLETED_GAME), - rcheevos_locals.game.title); - title[sizeof(title) - 1] = '\0'; - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", title); - - if (settings->bools.cheevos_visibility_mastery) - { -#if defined (HAVE_GFX_WIDGETS) - if (gfx_widgets_ready()) - { - char msg[128]; - const bool content_runtime_log = settings->bools.content_runtime_log; - const bool content_runtime_log_aggr = settings->bools.content_runtime_log_aggregate; - size_t len = strlcpy(msg, rcheevos_locals.displayname, sizeof(msg)); - - if (len < sizeof(msg) - 12 && - (content_runtime_log || content_runtime_log_aggr)) - { - const char* content_path = path_get(RARCH_PATH_CONTENT); - const char* core_path = path_get(RARCH_PATH_CORE); - runtime_log_t* runtime_log = runtime_log_init( - content_path, core_path, - settings->paths.directory_runtime_log, - settings->paths.directory_playlist, - !content_runtime_log_aggr); - - if (runtime_log) - { - const runloop_state_t* runloop_state = runloop_state_get_ptr(); - runtime_log_add_runtime_usec(runtime_log, - runloop_state->core_runtime_usec); - - len += snprintf(msg + len, sizeof(msg) - len, " | "); - runtime_log_get_runtime_str(runtime_log, msg + len, sizeof(msg) - len); - msg[sizeof(msg) - 1] = '\0'; - - free(runtime_log); - } - } - - gfx_widgets_push_achievement(title, msg, rcheevos_locals.game.badge_name); - } - else + return rc_hash_handle_chd_open_track(path, track); +#else + CHEEVOS_LOG(RCHEEVOS_TAG "Cannot generate hash from CHD without HAVE_CHD compile flag\n"); + return NULL; #endif - runloop_msg_queue_push(title, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } + else + { + /* not a CHD file, use the default handlers */ + rc_hash_get_default_cdreader(&cdreader); + rc_hash_reset_cdreader_hooks(); + return cdreader.open_track(path, track); + } +} + +static void rc_hash_reset_cdreader_hooks(void) +{ + struct rc_hash_cdreader cdreader; + rc_hash_get_default_cdreader(&cdreader); + cdreader.open_track = rc_hash_handle_cd_open_track; + rc_hash_init_custom_cdreader(&cdreader); } +/* end hooks */ + static void rcheevos_show_game_placard(void) { + size_t len; char msg[256]; - const settings_t* settings = config_get_ptr(); - const rcheevos_racheevo_t* cheevo = rcheevos_locals.game.achievements; - const rcheevos_racheevo_t* end = cheevo - + rcheevos_locals.game.achievement_count; - int number_of_active = 0; - int number_of_unsupported = 0; - int number_of_core = 0; - int mode = RCHEEVOS_ACTIVE_SOFTCORE; - - if (rcheevos_locals.game.id < 0) /* make sure there's actually a game loaded */ + rc_client_user_game_summary_t summary; + const settings_t* settings = config_get_ptr(); + const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); + if (!game) /* Make sure there's actually a game loaded */ return; - if (rcheevos_locals.hardcore_active) - mode = RCHEEVOS_ACTIVE_HARDCORE; - - for (; cheevo < end; cheevo++) - { - if (cheevo->active & RCHEEVOS_ACTIVE_UNOFFICIAL) - continue; - - number_of_core++; - if (cheevo->active & RCHEEVOS_ACTIVE_UNSUPPORTED) - number_of_unsupported++; - else if (cheevo->active & mode) - number_of_active++; - } + rc_client_get_user_game_summary(rcheevos_locals.client, &summary); - /* TODO/FIXME - localize strings */ - if (number_of_core == 0) - strlcpy(msg, "This game has no achievements.", sizeof(msg)); - else if (!number_of_unsupported) + if (summary.num_core_achievements == 0) { - if (settings->bools.cheevos_start_active) - snprintf(msg, sizeof(msg), - "All %d achievements activated for this session.", - number_of_core); + if (summary.num_unofficial_achievements == 0) + len = snprintf(msg, sizeof(msg), "%s", msg_hash_to_str(MSG_CHEEVOS_GAME_HAS_NO_ACHIEVEMENTS)); else - snprintf(msg, sizeof(msg), - "You have %d of %d achievements unlocked.", - number_of_core - number_of_active, number_of_core); + len = snprintf(msg, sizeof(msg), + msg_hash_to_str(MSG_CHEEVOS_UNOFFICIAL_ACHIEVEMENTS_ACTIVATED), + (int)summary.num_unofficial_achievements); } + else if (rc_client_get_encore_mode_enabled(rcheevos_locals.client)) + len = snprintf(msg, sizeof(msg), + msg_hash_to_str(MSG_CHEEVOS_ALL_ACHIEVEMENTS_ACTIVATED), + (int)summary.num_core_achievements); else + len = snprintf(msg, sizeof(msg), + msg_hash_to_str(MSG_CHEEVOS_NUMBER_ACHIEVEMENTS_UNLOCKED), + (int)summary.num_unlocked_achievements, + (int)summary.num_core_achievements); + + if (summary.num_unsupported_achievements) { - if (settings->bools.cheevos_start_active) - snprintf(msg, sizeof(msg), - "All %d achievements activated for this session (%d unsupported).", - number_of_core, number_of_unsupported); - else - snprintf(msg, sizeof(msg), - "You have %d of %d achievements unlocked (%d unsupported).", - number_of_core - number_of_active - number_of_unsupported, - number_of_core, number_of_unsupported); + if (len < sizeof(msg) - 4) + { + msg[len++] = ' '; + msg[len++] = '('; + + len += snprintf(&msg[len], sizeof(msg) - len, + msg_hash_to_str(MSG_CHEEVOS_UNSUPPORTED_COUNT), + (int)summary.num_unsupported_achievements); + + if (len < sizeof(msg) - 1) + { + msg[len++] = ')'; + msg[len] = '\0'; + } + } } msg[sizeof(msg) - 1] = 0; CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg); - if (settings->uints.cheevos_visibility_summary == RCHEEVOS_SUMMARY_ALLGAMES || - (number_of_core > 0 && settings->uints.cheevos_visibility_summary == RCHEEVOS_SUMMARY_HASCHEEVOS)) + if ( settings->uints.cheevos_visibility_summary == RCHEEVOS_SUMMARY_ALLGAMES + || (settings->uints.cheevos_visibility_summary == RCHEEVOS_SUMMARY_HASCHEEVOS + && (summary.num_core_achievements || summary.num_unofficial_achievements))) { #if defined (HAVE_GFX_WIDGETS) if (gfx_widgets_ready()) - gfx_widgets_push_achievement(rcheevos_locals.game.title, msg, rcheevos_locals.game.badge_name); + { + char badge_name[32]; + size_t _len = strlcpy(badge_name, "i", sizeof(badge_name)); + _len += strlcpy(badge_name + _len, game->badge_name, + sizeof(badge_name) - _len); + gfx_widgets_push_achievement(game->title, msg, badge_name); + } else #endif - runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } } -static void rcheevos_end_load(void) +static uint32_t rcheevos_client_read_memory(uint32_t address, + uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) { -#ifdef HAVE_GFX_WIDGETS - rcheevos_ralboard_t* lboard = rcheevos_locals.game.leaderboards; - rcheevos_ralboard_t* stop = lboard + rcheevos_locals.game.leaderboard_count; - const char* ptr; - unsigned hash; - - for (; lboard < stop; ++lboard) - { - lboard->value_hash = 0; - lboard->active_tracker_id = 0; - - ptr = lboard->mem; - if (!ptr) - continue; - - ptr = strstr(ptr, "VAL:"); - if (!ptr) - continue; - ptr += 4; - - /* calculate the DJB2 hash of the VAL portion of the string*/ - hash = 5381; - while (*ptr && (ptr[0] != ':' || ptr[1] != ':')) - hash = (hash << 5) + hash + *ptr++; - - lboard->value_hash = hash; - } -#endif - - CHEEVOS_LOG(RCHEEVOS_TAG "Load finished\n"); - rcheevos_locals.load_info.state = RCHEEVOS_LOAD_STATE_DONE; + return rc_libretro_memory_read(&rcheevos_locals.memory, address, buffer, num_bytes); } -static void rcheevos_fetch_badges_callback(void* userdata) +static uint32_t rcheevos_client_read_memory_dummy(uint32_t address, + uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) { - rcheevos_end_load(); + /* pretend the memory exists */ + memset(buffer, 0, num_bytes); + return num_bytes; } -static void rcheevos_fetch_badges(void) +static uint32_t rcheevos_client_read_memory_unavailable(uint32_t address, + uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) { - /* this function manages the - * RCHEEVOS_LOAD_STATE_FETCHING_BADGES state */ - rcheevos_client_fetch_badges(rcheevos_fetch_badges_callback, NULL); + return 0; } -static void rcheevos_start_session_async(retro_task_t* task) +static uint32_t rcheevos_client_read_memory_uninitialized(uint32_t address, + uint8_t* buffer, uint32_t num_bytes, rc_client_t* client) { - const bool needs_runtime = - ( rcheevos_locals.game.achievement_count > 0 - || rcheevos_locals.game.leaderboard_count > 0 - || rcheevos_locals.runtime.richpresence); - - if (rcheevos_load_aborted()) - return; - - /* We don't have to wait for this to complete - * to proceed to the next loading state */ - rcheevos_client_start_session(rcheevos_locals.game.id); - - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_STARTING_SESSION); - - if (needs_runtime) - { - /* activate the achievements and leaderboards - * (rich presence has already been activated) */ - rcheevos_activate_achievements(); - - if (rcheevos_locals.hardcore_active) - rcheevos_activate_leaderboards(); - - /* disable any unsupported achievements */ - rcheevos_validate_memrefs(&rcheevos_locals); - - /* Let the runtime start processing the achievements */ - rcheevos_locals.loaded = true; - } - -#if HAVE_REWIND - if (!rcheevos_locals.hardcore_active) + /* we can't initialize the memory until we know which console the game + * is associated to. This happens internally to the load game sequence, + * so we have to intercept the first attempt to read memory. + */ + if (rcheevos_init_memory(&rcheevos_locals)) { - /* Re-enable rewind. If rcheevos_locals.loaded is true, - * additional space will be allocated for the achievement - * state data */ - const settings_t* settings = config_get_ptr(); - if (settings->bools.rewind_enable) - { -#ifdef HAVE_THREADS - if (!task_is_on_main_thread()) - { - /* Have to "schedule" this. CMD_EVENT_REWIND_INIT should - * only be called on the main thread */ - rcheevos_locals.queued_command = CMD_EVENT_REWIND_INIT; - } - else -#endif - command_event(CMD_EVENT_REWIND_INIT, NULL); - } + rc_client_set_read_memory_function(client, rcheevos_client_read_memory); + return rcheevos_client_read_memory(address, buffer, num_bytes, client); } -#endif - - /* If there's nothing for the runtime to process, - * disable hardcore. */ - if (!needs_runtime) - rcheevos_pause_hardcore(); - /* hardcore is active. we're going to start processing - * achievements. make sure restrictions are enforced */ - else if (rcheevos_locals.hardcore_active) - runloop_state_get_ptr()->flags &= ~RUNLOOP_FLAG_SLOWMOTION; - - task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); - - if (rcheevos_end_load_state() == 0) - rcheevos_fetch_badges(); -} - -static void rcheevos_start_session_finish(retro_task_t* task, void* data, void* userdata, const char* error) -{ - (void)task; - (void)data; - (void)userdata; - (void)error; - /* this must be called on the main thread */ - rcheevos_show_game_placard(); -} - -static void rcheevos_start_session(void) -{ - retro_task_t* task; - - if (rcheevos_load_aborted()) + /* some cores (like Mupen64-Plus) don't expose the memory until the + * first call to retro_run. in that case, there will be a total_size + * of memory reported by the core, but init will return false, as all + * of the pointers were null. if we're still loading the game, return + * dummy memory and we'll re-evaluate in rcheevos_client_load_game_callback(). + */ + if (!rcheevos_is_game_loaded()) { - CHEEVOS_LOG(RCHEEVOS_TAG "Load aborted before starting session\n"); - return; + rc_client_set_read_memory_function(client, rcheevos_client_read_memory_dummy); + return rcheevos_client_read_memory_dummy(address, buffer, num_bytes, client); } - /* re-validate the config settings now that we know - * which console_id is active */ - rcheevos_validate_config_settings(); - - task = task_init(); - task->handler = rcheevos_start_session_async; - task->callback = rcheevos_start_session_finish; - task_queue_push(task); -} - -static void rcheevos_initialize_runtime_callback(void* userdata) -{ - rcheevos_start_session(); + /* game loaded, but no memory available */ + rc_client_set_read_memory_function(client, rcheevos_client_read_memory_unavailable); + return rcheevos_client_read_memory_unavailable(address, buffer, num_bytes, client); } -static void rcheevos_fetch_game_data(void) +static void rcheevos_client_login_callback(int result, + const char* error_message, rc_client_t* client, void* userdata) { - if (rcheevos_load_aborted()) - { - rcheevos_locals.game.hash = NULL; - rcheevos_pause_hardcore(); - return; - } + const rc_client_user_t* user; - if (rcheevos_locals.game.id <= 0) + if (result != RC_OK) { - const settings_t* settings = config_get_ptr(); - if (settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push( - "RetroAchievements: Game could not be identified.", - 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - - CHEEVOS_LOG(RCHEEVOS_TAG "Game could not be identified\n"); - if (rcheevos_locals.load_info.hashes_tried > 1) - rcheevos_locals.game.hash = NULL; - - rcheevos_locals.load_info.state = RCHEEVOS_LOAD_STATE_UNKNOWN_GAME; - rcheevos_pause_hardcore(); + char msg[256]; + size_t _len = strlcpy(msg, "RetroAchievements login failed: ", + sizeof(msg)); + _len += strlcpy(msg + _len, error_message, sizeof(msg) - _len); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg); + runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); return; } - if (!rcheevos_locals.token[0]) + user = rc_client_get_user_info(client); + if (!user) { - rcheevos_locals.load_info.state = RCHEEVOS_LOAD_STATE_LOGIN_FAILED; - rcheevos_pause_hardcore(); - return; + CHEEVOS_LOG(RCHEEVOS_TAG "Login failed without error\n"); } - - /* fetch the game data and the user unlocks */ - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA); - -#if HAVE_REWIND - if (!rcheevos_locals.hardcore_active) + else { - /* deactivate rewind while we activate the achievements */ - const settings_t* settings = config_get_ptr(); - if (settings->bools.rewind_enable) - { -#ifdef HAVE_THREADS - if (!task_is_on_main_thread()) - { - /* have to "schedule" this. CMD_EVENT_REWIND_DEINIT should only be called on the main thread */ - rcheevos_locals.queued_command = CMD_EVENT_REWIND_DEINIT; - - /* wait for rewind to be disabled */ - while (rcheevos_locals.queued_command != CMD_EVENT_NONE) - retro_sleep(1); - } - else -#endif - command_event(CMD_EVENT_REWIND_DEINIT, NULL); - } - } -#endif - - rcheevos_client_initialize_runtime(rcheevos_locals.game.id, rcheevos_initialize_runtime_callback, NULL); - - if (rcheevos_end_load_state() == 0) - rcheevos_start_session(); -} - -struct rcheevos_identify_game_data -{ - struct rc_hash_iterator iterator; - char* path; - uint8_t* datacopy; - char hash[33]; -}; - -static void rcheevos_identify_game_callback(void* userdata) -{ - struct rcheevos_identify_game_data* data = - (struct rcheevos_identify_game_data*)userdata; - - rcheevos_locals.load_info.hashes_tried++; + settings_t* settings = config_get_ptr(); - if (rcheevos_locals.game.id == 0) - { - /* previous hash didn't match, try the next one */ - char new_hash[33]; - int found_new_hash; - while ((found_new_hash = rc_hash_iterate(new_hash, &data->iterator)) != 0) + if (user->token[0]) { - if (!rc_libretro_hash_set_get_game_id(&rcheevos_locals.game.hashes, new_hash)) - break; - - CHEEVOS_LOG(RCHEEVOS_TAG "Ignoring [%s]. Already tried.\n", new_hash); + /* store the token, clear the password */ + strlcpy(settings->arrays.cheevos_token, user->token, + sizeof(settings->arrays.cheevos_token)); + settings->arrays.cheevos_password[0] = '\0'; } - - if (found_new_hash) + else { - memcpy(data->hash, new_hash, sizeof(data->hash)); - rcheevos_client_identify_game(data->hash, - rcheevos_identify_game_callback, data); - return; + CHEEVOS_LOG(RCHEEVOS_TAG "Login did not return token\n"); } - } - - rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, - data->path, rcheevos_locals.game.id, data->hash); - rcheevos_locals.game.hash = - rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, data->path); - - if (data->iterator.path && strcmp(data->iterator.path, data->path) != 0) - { - rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, - data->iterator.path, rcheevos_locals.game.id, data->hash); - rcheevos_locals.game.hash = - rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, data->iterator.path); - } - - /* no more hashes generated, free the iterator data */ - rc_hash_destroy_iterator(&data->iterator); - if (data->datacopy) - free(data->datacopy); - if (data->path) - free(data->path); - free(data); - - /* hash resolution complete, proceed to fetching game data */ - if (rcheevos_end_load_state() == 0) - rcheevos_fetch_game_data(); -} - -static int rcheevos_get_image_path(uint32_t index, char* buffer, size_t buffer_size) -{ - rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system; - if (!sys_info->disk_control.cb.get_image_path) - return 0; - return sys_info->disk_control.cb.get_image_path(index, buffer, buffer_size); -} - -static bool rcheevos_identify_game(const struct retro_game_info* info) -{ - struct rcheevos_identify_game_data* data; - struct rc_hash_filereader filereader; - size_t len; -#ifndef DEBUG - settings_t* settings = config_get_ptr(); -#endif - - data = (struct rcheevos_identify_game_data*) - calloc(1, sizeof(struct rcheevos_identify_game_data)); - if (!data) - { - CHEEVOS_LOG(RCHEEVOS_TAG "allocation failed\n"); - return false; - } - - /* provide hooks for reading files */ - memset(&filereader, 0, sizeof(filereader)); - filereader.open = rc_hash_handle_file_open; - filereader.seek = rc_hash_handle_file_seek; - filereader.tell = rc_hash_handle_file_tell; - filereader.read = rc_hash_handle_file_read; - filereader.close = rc_hash_handle_file_close; - rc_hash_init_custom_filereader(&filereader); - - rc_hash_init_error_message_callback(rcheevos_handle_log_message); -#ifndef DEBUG - /* in DEBUG mode, always initialize the verbose message handler */ - if (settings->bools.cheevos_verbose_enable) -#endif - { - rc_hash_init_verbose_message_callback(rcheevos_handle_log_message); + /* show notification (if enabled) */ + if (settings->bools.cheevos_visibility_account) + { + char msg[128]; + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_CHEEVOS_LOGGED_IN_AS_USER), + user->display_name); + runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } } +} - rc_hash_reset_cdreader_hooks(); +static void rcheevos_finalize_game_load(rc_client_t* client) +{ + rcheevos_client_download_achievement_badges(client); - /* fetch the first hash */ - rc_hash_initialize_iterator(&data->iterator, - info->path, (uint8_t*)info->data, info->size); - if (!rc_hash_iterate(data->hash, &data->iterator)) + if (!rc_client_is_processing_required(client)) { - CHEEVOS_LOG(RCHEEVOS_TAG "no hashes generated\n"); - rc_hash_destroy_iterator(&data->iterator); - free(data); - return false; + CHEEVOS_LOG(RCHEEVOS_TAG "No runtime logic for game, pausing hardcore\n"); + rcheevos_pause_hardcore(); } +} + +static void rcheevos_client_load_game_callback(int result, + const char* error_message, rc_client_t* client, void* userdata) +{ + const settings_t* settings = config_get_ptr(); + const rc_client_game_t* game = rc_client_get_game_info(client); + char msg[256]; - rc_libretro_hash_set_init(&rcheevos_locals.game.hashes, info->path, rcheevos_get_image_path); - data->path = strdup(info->path); +#if defined(HAVE_GFX_WIDGETS) + gfx_widget_set_cheevos_set_loading(false); +#endif - if (data->iterator.consoles[data->iterator.index] != 0) + if (result != RC_OK || !game) { - /* multiple potential matches, clone the data for the next attempt */ - if (info->data) + if (result == RC_NO_GAME_LOADED) { - len = info->size; - if (len > CHEEVOS_MB(64)) - len = CHEEVOS_MB(64); + CHEEVOS_LOG(RCHEEVOS_TAG "Game not recognized, pausing hardcore\n"); + rcheevos_pause_hardcore(); - data->datacopy = (uint8_t*)malloc(len); - if (!data->datacopy) - { - CHEEVOS_LOG(RCHEEVOS_TAG "allocation failed\n"); - rc_hash_destroy_iterator(&data->iterator); - free(data); - return false; - } + if (!settings->bools.cheevos_verbose_enable) + return; - memcpy(data->datacopy, info->data, len); - data->iterator.buffer = data->datacopy; + snprintf(msg, sizeof(msg), "%s", msg_hash_to_str(MSG_CHEEVOS_GAME_NOT_IDENTIFIED)); } - } + else + { + if (!error_message) + error_message = "Unknown error"; - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME); - rcheevos_client_identify_game(data->hash, - rcheevos_identify_game_callback, data); - return true; -} + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_CHEEVOS_GAME_LOAD_FAILED), error_message); + CHEEVOS_LOG(RCHEEVOS_TAG "Game load failed: %s\n", error_message); + } -static void rcheevos_login_callback(void* userdata) -{ - if (rcheevos_locals.token[0]) + runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + return; + } + + if (rcheevos_locals.memory.total_size == 0) { - const settings_t* settings = config_get_ptr(); - if (settings->bools.cheevos_visibility_account) + /* make one last attempt to initialize memory */ + if (!rcheevos_init_memory(&rcheevos_locals)) { - char msg[256]; - msg[0] = '\0'; - /* TODO/FIXME - localize */ - snprintf(msg, sizeof(msg), - "RetroAchievements: Logged in as \"%s\".", - rcheevos_locals.displayname); - runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + rcheevos_locals.core_supports = false; + + CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n"); + + if (settings && settings->bools.cheevos_verbose_enable) + runloop_msg_queue_push(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), + 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + + rcheevos_unload(); + rcheevos_pause_hardcore(); + return; } - } - if (rcheevos_end_load_state() == 0) - rcheevos_fetch_game_data(); -} + /* have valid memory now. use the real read function */ + rc_client_set_read_memory_function(client, rcheevos_client_read_memory); + } -/* Increment the outstanding requests counter and set the load state */ -void rcheevos_begin_load_state(enum rcheevos_load_state state) -{ -#ifdef HAVE_THREADS - slock_lock(rcheevos_locals.load_info.request_lock); -#endif - ++rcheevos_locals.load_info.outstanding_requests; - rcheevos_locals.load_info.state = state; #ifdef HAVE_THREADS - slock_unlock(rcheevos_locals.load_info.request_lock); + if (!video_driver_is_threaded() && !task_is_on_main_thread()) + { + /* have to "schedule" this. game image should not be loaded on background thread */ + rcheevos_locals.queued_command = CMD_CHEEVOS_NON_COMMAND; + rcheevos_locals.game_placard_requested = true; + } + else #endif -} + rcheevos_show_game_placard(); -/* Decrement and return the outstanding requests counter. - * If non-zero, requests are still outstanding */ -int rcheevos_end_load_state(void) -{ - int requests = 0; + rcheevos_finalize_game_load(client); + if (rcheevos_hardcore_active()) + { + /* hardcore is active. we're going to start processing + * achievements. make sure restrictions are enforced */ + rcheevos_validate_config_settings(); + rcheevos_enforce_hardcore_settings(); + } + else + { +#if HAVE_REWIND + /* Re-enable rewind. Additional space will be allocated for the achievement state data */ + if (settings->bools.rewind_enable) + { #ifdef HAVE_THREADS - slock_lock(rcheevos_locals.load_info.request_lock); + if (!task_is_on_main_thread()) + { + /* Have to "schedule" this. CMD_EVENT_REWIND_REINIT should + * only be called on the main thread */ + rcheevos_locals.queued_command = CMD_EVENT_REWIND_REINIT; + } + else #endif - if (rcheevos_locals.load_info.outstanding_requests > 0) - --rcheevos_locals.load_info.outstanding_requests; - requests = rcheevos_locals.load_info.outstanding_requests; -#ifdef HAVE_THREADS - slock_unlock(rcheevos_locals.load_info.request_lock); + command_event(CMD_EVENT_REWIND_REINIT, NULL); + } #endif + } - return requests; + rcheevos_spectating_changed(); /* synchronize spectating state */ } -bool rcheevos_load_aborted(void) +static rc_clock_t rcheevos_client_get_time_millisecs(const rc_client_t* client) { - switch (rcheevos_locals.load_info.state) - { - /* Unload has been called */ - case RCHEEVOS_LOAD_STATE_ABORTED: - /* Unload quit waiting and ran to completion */ - case RCHEEVOS_LOAD_STATE_NONE: - /* Login/resolve hash failed after several attempts */ - case RCHEEVOS_LOAD_STATE_NETWORK_ERROR: - return true; - default: - break; - } - return false; + return cpu_features_get_time_usec() / 1000; } -#endif /* HAVE_RC_CLIENT */ + bool rcheevos_load(const void *data) { @@ -3197,20 +1507,6 @@ bool rcheevos_load(const void *data) bool cheevos_enable = settings && settings->bools.cheevos_enable; -#ifndef HAVE_RC_CLIENT - memset(&rcheevos_locals.load_info, 0, - sizeof(rcheevos_locals.load_info)); - - rcheevos_locals.loaded = false; - rcheevos_locals.game.id = -1; - rcheevos_locals.game.console_id = 0; - rcheevos_locals.game.mastery_placard_shown = false; - #ifdef HAVE_GFX_WIDGETS - rcheevos_locals.tracker_progress = 0.0; - #endif - rc_runtime_init(&rcheevos_locals.runtime); -#endif /* HAVE_RC_CLIENT */ - #ifdef HAVE_THREADS rcheevos_locals.queued_command = CMD_EVENT_NONE; #endif @@ -3219,9 +1515,6 @@ bool rcheevos_load(const void *data) * support achievements, disable hardcore and bail */ if (!cheevos_enable || !rcheevos_locals.core_supports || !data) { -#ifndef HAVE_RC_CLIENT - rcheevos_locals.game.id = 0; -#endif rcheevos_pause_hardcore(); return false; } @@ -3231,14 +1524,10 @@ bool rcheevos_load(const void *data) CHEEVOS_LOG(RCHEEVOS_TAG "Cannot login (no username)\n"); runloop_msg_queue_push("Missing RetroAchievements account information.", 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); -#ifndef HAVE_RC_CLIENT - rcheevos_locals.game.id = 0; -#endif rcheevos_pause_hardcore(); return false; } -#ifdef HAVE_RC_CLIENT /* Refresh the user agent in case it's not set or has changed */ rcheevos_get_user_agent(&rcheevos_locals, rcheevos_locals.user_agent_core, @@ -3303,31 +1592,6 @@ bool rcheevos_load(const void *data) { rcheevos_enforce_hardcore_settings(); } -#ifndef HAVE_RC_CLIENT - else - { -#if HAVE_REWIND - /* deactivate rewind while we activate the achievements */ - const settings_t* settings = config_get_ptr(); - if (settings->bools.rewind_enable) - { -#ifdef HAVE_THREADS - if (!task_is_on_main_thread()) - { - /* have to "schedule" this. CMD_EVENT_REWIND_DEINIT should only be called on the main thread */ - rcheevos_locals.queued_command = CMD_EVENT_REWIND_DEINIT; - - /* wait for rewind to be disabled */ - while (rcheevos_locals.queued_command != CMD_EVENT_NONE) - retro_sleep(1); - } - else -#endif - command_event(CMD_EVENT_REWIND_DEINIT, NULL); - } -#endif - } -#endif /* provide hooks for reading files */ rc_hash_reset_cdreader_hooks(); @@ -3340,104 +1604,11 @@ bool rcheevos_load(const void *data) rc_client_begin_identify_and_load_game(rcheevos_locals.client, RC_CONSOLE_UNKNOWN, info->path, (const uint8_t*)info->data, info->size, rcheevos_client_load_game_callback, NULL); -#else /* !HAVE_RC_CLIENT */ - #ifdef HAVE_THREADS - if (!rcheevos_locals.load_info.request_lock) - rcheevos_locals.load_info.request_lock = slock_new(); - #endif - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME); - - /* reset hardcore mode and leaderboard settings based on configs */ - rcheevos_hardcore_enabled_changed(); - CHEEVOS_LOG(RCHEEVOS_TAG "Load started, hardcore %sactive\n", rcheevos_hardcore_active() ? "" : "not "); - - rcheevos_validate_config_settings(); - - /* Refresh the user agent in case it's not set or has changed */ - rcheevos_client_initialize(); - rcheevos_get_user_agent(&rcheevos_locals, - rcheevos_locals.user_agent_core, - sizeof(rcheevos_locals.user_agent_core)); - - /* === ACHIEVEMENT INITIALIZATION PROCESS === - - 1. RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME - a. iterate possible hashes to identify game [rcheevos_identify_game] - i. if game not found, display "no achievements for this game" and abort [rcheevos_identify_game_callback] - b. Login - i. if already logged in, skip this step - ii. start login request [rcheevos_client_login_with_password/rcheevos_client_login_with_token] - iii. complete login, store user/token [rcheevos_login_callback] - 2. RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA [rcheevos_client_initialize_runtime] - a. begin game data request [rc_api_init_fetch_game_data_request] - b. fetch user unlocks - i. if encore mode, skip this step - ii. begin user unlocks hardcore request [rc_api_init_fetch_user_unlocks_request] - iii. begin user unlocks softcore request [rc_api_init_fetch_user_unlocks_request] - 3. RCHEEVOS_LOAD_STATE_STARTING_SESSION [rcheevos_initialize_runtime_callback] - a. activate achievements [rcheevos_activate_achievements] - b. schedule rich presence periodic update [rcheevos_client_start_session] - c. start session on server [rcheevos_client_start_session] - d. show title card [rcheevos_show_game_placard] - 4. RCHEEVOS_LOAD_STATE_FETCHING_BADGES - a. download from server [rcheevos_client_fetch_badges] - 5. RCHEEVOS_LOAD_STATE_DONE - - */ - - /* Identify the game and log the user in. - * These will run asynchronously. */ - if (!rcheevos_identify_game(info)) - { - /* No hashes could be generated for the game, - * disable hardcore and bail */ - rcheevos_locals.game.id = 0; - rcheevos_end_load_state(); - rcheevos_pause_hardcore(); - return false; - } - - if (!rcheevos_locals.token[0]) - { - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME); - if (!string_is_empty(settings->arrays.cheevos_token)) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Attempting to login %s (with token)\n", - settings->arrays.cheevos_username); - rcheevos_client_login_with_token( - settings->arrays.cheevos_username, - settings->arrays.cheevos_token, - rcheevos_login_callback, NULL); - } - else if (!string_is_empty(settings->arrays.cheevos_password)) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Attempting to login %s (with password)\n", - settings->arrays.cheevos_username); - rcheevos_client_login_with_password( - settings->arrays.cheevos_username, - settings->arrays.cheevos_password, - rcheevos_login_callback, NULL); - } - else - { - CHEEVOS_LOG(RCHEEVOS_TAG "Cannot login %s (no password or token)\n", - settings->arrays.cheevos_username); - runloop_msg_queue_push("No password provided for RetroAchievements account", 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - rcheevos_unload(); - return false; - } - } - if (rcheevos_end_load_state() == 0) - rcheevos_fetch_game_data(); -#endif /* HAVE_RC_CLIENT */ return true; } -#ifdef HAVE_RC_CLIENT - static void rcheevos_client_change_media_callback(int result, const char* error_message, rc_client_t* client, void* userdata) { @@ -3472,197 +1643,3 @@ void rcheevos_change_disc(const char* new_disc_path, bool initial_disc) NULL, 0, rcheevos_client_change_media_callback, NULL); } } - -#else /* !HAVE_RC_CLIENT */ - -struct rcheevos_identify_changed_disc_data -{ - int real_game_id; - char* path; - char hash[33]; -}; - -static void rcheevos_identify_game_disc_callback(void* userdata) -{ - struct rcheevos_identify_changed_disc_data* changed_disc_data = - (struct rcheevos_identify_changed_disc_data*)userdata; - - /* rcheevos_locals.game.id has the game id for the new hash, swap it with the old game id */ - const int hash_game_id = rcheevos_locals.game.id; - rcheevos_locals.game.id = changed_disc_data->real_game_id; - - /* rcheevos_client_identify_game will update rcheevos_locals.game.id */ - if (rcheevos_locals.game.id == hash_game_id) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); - } - else if (hash_game_id != 0) - { - /* when changing discs, if the disc is recognized but belongs to another game, allow it. - * this allows loading known game discs for games that leverage user-provided discs. */ - CHEEVOS_LOG(RCHEEVOS_TAG "Hash identified for game %d\n", hash_game_id); - } - else - { - CHEEVOS_LOG(RCHEEVOS_TAG "Disc not recognized\n"); - if (rcheevos_hardcore_active()) - { - /* don't allow unknown game discs in hardcore. - * assume it's a modified version of the base game. */ - runloop_msg_queue_push("Hardcore paused. Game disc unrecognized.", 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - rcheevos_pause_hardcore(); - } - } - - /* disc is valid, add it to the known disk list */ - rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, - changed_disc_data->path, hash_game_id, changed_disc_data->hash); - - rcheevos_locals.game.hash = - rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, changed_disc_data->hash); - - free(changed_disc_data->path); - free(changed_disc_data); -} - -static void rcheevos_identify_initial_disc_callback(void* userdata) -{ - struct rcheevos_identify_changed_disc_data* changed_disc_data = - (struct rcheevos_identify_changed_disc_data*)userdata; - - /* rcheevos_client_identify_game will update rcheevos_locals.game.id */ - if (rcheevos_locals.game.id != changed_disc_data->real_game_id) - { - if (rcheevos_locals.game.id == 0) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Disc not recognized\n"); - runloop_msg_queue_push("Disabling achievements. Game disc unrecognized.", 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - } - else - { - CHEEVOS_LOG(RCHEEVOS_TAG "Initial disc for game %d\n", rcheevos_locals.game.id); - runloop_msg_queue_push("Disabling achievements. Not for loaded game.", 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - } - - rcheevos_locals.game.hash = NULL; - rcheevos_unload(); - } - else - { - /* disc is valid, add it to the known disk list */ - CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); - rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, - changed_disc_data->path, rcheevos_locals.game.id, changed_disc_data->hash); - - rcheevos_locals.game.hash = - rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, changed_disc_data->hash); - } - - free(changed_disc_data->path); - free(changed_disc_data); -} - -static void rcheevos_validate_initial_disc_handler(retro_task_t* task) -{ - char* new_disc_path = (char*)task->user_data; - - if (rcheevos_locals.game.id == 0) - { - /* could not identify game. don't bother identifying initial disc */ - } - else - { - if (rcheevos_locals.game.console_id == 0) - { - /* not ready yet. try again in another 500ms */ - task->when = cpu_features_get_time_usec() + 500 * 1000; - return; - } - - /* game ready. attempt to validate the initial disc */ - rcheevos_change_disc(new_disc_path, true); - } - - free(new_disc_path); - task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); -} - -void rcheevos_change_disc(const char* new_disc_path, bool initial_disc) -{ - struct rcheevos_identify_changed_disc_data* data; - char hash[33]; - int hash_game_id; - - /* no game loaded */ - if (rcheevos_locals.game.id == 0) - return; - - /* see if we've already identified this file */ - rcheevos_locals.game.hash = - rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, new_disc_path); - if (rcheevos_locals.game.hash) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Switched to known hash: %s\n", rcheevos_locals.game.hash); - return; - } - - /* don't check the disc until the game is done loading */ - if (rcheevos_locals.game.console_id == 0) - { - retro_task_t* task = task_init(); - task->handler = rcheevos_validate_initial_disc_handler; - task->user_data = strdup(new_disc_path); - task->progress = -1; - task->when = cpu_features_get_time_usec() + 500 * 1000; /* 500ms */ - task_queue_push(task); - return; - } - - /* attempt to identify the file */ - if (rc_hash_generate_from_file(hash, rcheevos_locals.game.console_id, new_disc_path)) - { - /* check to see if the hash is already known */ - hash_game_id = rc_libretro_hash_set_get_game_id(&rcheevos_locals.game.hashes, hash); - if (hash_game_id) - { - /* hash identical to some other file - probably the first disc matching the m3u. */ - CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); - } - } - else - { - /* 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 - * from other systems for games that leverage user-provided discs. */ - CHEEVOS_LOG(RCHEEVOS_TAG "No hash generated\n"); - hash_game_id = -1; - strlcpy(hash, "[NO HASH]", sizeof(hash)); - } - - if (hash_game_id) - { - /* we know how to handle this disc. no need to call the server */ - rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, new_disc_path, hash_game_id, hash); - rcheevos_locals.game.hash = - rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, new_disc_path); - return; - } - - /* call the server to make sure the hash is valid for the loaded game */ - data = (struct rcheevos_identify_changed_disc_data*) - calloc(1, sizeof(struct rcheevos_identify_changed_disc_data)); - if (data) { - data->real_game_id = rcheevos_locals.game.id; - data->path = strdup(new_disc_path); - memcpy(data->hash, hash, sizeof(data->hash)); - - rcheevos_client_identify_game(data->hash, - initial_disc ? rcheevos_identify_initial_disc_callback : - rcheevos_identify_game_disc_callback, data); - } -} - -#endif /* HAVE_RC_CLIENT */ diff --git a/cheevos/cheevos_client.c b/cheevos/cheevos_client.c index 6e85710ee06..294baad3197 100644 --- a/cheevos/cheevos_client.c +++ b/cheevos/cheevos_client.c @@ -57,98 +57,12 @@ /* Define this macro to log downloaded badge images. */ #undef CHEEVOS_LOG_BADGES -#ifndef HAVE_RC_CLIENT - -/* Number of usecs to wait between posting rich presence to the site. */ -/* Keep consistent with SERVER_PING_FREQUENCY from RAIntegration. */ -#define CHEEVOS_PING_FREQUENCY 2 * 60 * 1000000 - - -/**************************** - * data types * - ****************************/ - -enum rcheevos_async_io_type -{ - CHEEVOS_ASYNC_RICHPRESENCE = 0, - CHEEVOS_ASYNC_AWARD_ACHIEVEMENT, - CHEEVOS_ASYNC_SUBMIT_LBOARD, - CHEEVOS_ASYNC_LOGIN, - CHEEVOS_ASYNC_RESOLVE_HASH, - CHEEVOS_ASYNC_FETCH_GAME_DATA, - CHEEVOS_ASYNC_FETCH_USER_UNLOCKS, - CHEEVOS_ASYNC_FETCH_HARDCORE_USER_UNLOCKS, - CHEEVOS_ASYNC_START_SESSION, - CHEEVOS_ASYNC_FETCH_BADGE -}; - -struct rcheevos_async_io_request; - -typedef void (*rcheevos_async_handler)(struct rcheevos_async_io_request *request, - http_transfer_data_t *data, char buffer[], size_t buffer_size); - -typedef struct rcheevos_async_io_request -{ - rc_api_request_t request; - rcheevos_async_handler handler; - int id; - rcheevos_client_callback callback; - void* callback_data; - int attempt_count; - const char* success_message; - const char* failure_message; - const char* user_agent; - char type; -} rcheevos_async_io_request; - -#endif /* HAVE_RC_CLIENT */ - #ifdef HAVE_THREADS #define RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS 2 #else #define RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS 1 #endif -#ifndef HAVE_RC_CLIENT - -typedef struct rcheevos_fetch_badge_state -{ - unsigned badge_fetch_index; - unsigned locked_badge_fetch_index; - const char* badge_directory; - rcheevos_client_callback callback; - void* callback_data; - char requested_badges[ - RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS][32]; -} rcheevos_fetch_badge_state; - -typedef struct rcheevos_fetch_badge_data -{ - rcheevos_fetch_badge_state* state; - int request_index; - void* data; - size_t data_len; - rcheevos_client_callback callback; -} rcheevos_fetch_badge_data; - - -/**************************** - * forward declarations * - ****************************/ - -static retro_time_t rcheevos_client_prepare_ping(rcheevos_async_io_request* request); - -static void rcheevos_async_http_task_callback( - retro_task_t* task, void* task_data, void* user_data, const char* error); - -static void rcheevos_async_end_request(rcheevos_async_io_request* request); - -static void rcheevos_async_fetch_badge_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size); - -#endif /* HAVE_RC_CLIENT */ - /**************************** * user agent construction * ****************************/ @@ -320,8 +234,6 @@ static void rcheevos_log_post_url(const char* url, const char* post) * dispatch * ****************************/ -#ifdef HAVE_RC_CLIENT - typedef struct rc_client_http_task_data_t { rc_client_server_callback_t callback; @@ -742,1555 +654,4 @@ void rcheevos_client_download_achievement_badge(const char* badge_name, bool loc rc_api_destroy_request(&request); } -#else /* !HAVE_RC_CLIENT */ - -static void rcheevos_async_begin_http_request(rcheevos_async_io_request* request) -{ - if (request->request.post_data) - task_push_http_post_transfer_with_user_agent(request->request.url, - request->request.post_data, true, "POST", request->user_agent, - rcheevos_async_http_task_callback, request); - else - task_push_http_transfer_with_user_agent(request->request.url, - true, "GET", request->user_agent, - rcheevos_async_http_task_callback, request); -} - -static void rcheevos_async_retry_request(retro_task_t* task) -{ - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - task->user_data; - - /* the timer task has done its job. let it dispose itself */ - task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); - - /* start a new task for the HTTP call */ - rcheevos_async_begin_http_request(request); -} - -static void rcheevos_async_retry_request_after_delay(rcheevos_async_io_request* request, const char* error) -{ - retro_task_t* task = task_init(); - /* Double the wait between each attempt until we hit - * a maximum delay of two minutes. - * 250ms -> 500ms -> 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s... */ - retro_time_t retry_delay = (request->attempt_count > 8) - ? (120 * 1000 * 1000) - : ((250 * 1000) << request->attempt_count); - - CHEEVOS_ERR(RCHEEVOS_TAG "%s %u: %s (automatic retry in %dms)\n", request->failure_message, - request->id, error, (int)retry_delay / 1000); - - task->when = cpu_features_get_time_usec() + retry_delay; - task->handler = rcheevos_async_retry_request; - task->user_data = request; - task->progress = -1; - - ++request->attempt_count; - task_queue_push(task); -} - -static bool rcheevos_async_request_failed(rcheevos_async_io_request* request, const char* error) -{ - /* always retry any request once (attempt_count==0) in case of network hiccup */ - if (request->attempt_count > 0) - { - /* retry failed, don't retry these requests */ - switch (request->type) - { - /* timer will ping again */ - case CHEEVOS_ASYNC_RICHPRESENCE: - /* fallback to the placeholder image */ - case CHEEVOS_ASYNC_FETCH_BADGE: - return false; - - case CHEEVOS_ASYNC_RESOLVE_HASH: - case CHEEVOS_ASYNC_LOGIN: - /* make a maximum of four attempts - (0ms -> 250ms -> 500ms -> 1s) */ - if (request->attempt_count == 3) - return false; - break; - - default: - break; - } - } - - /* automatically retry the request */ - rcheevos_async_retry_request_after_delay(request, error); - return true; -} - -static void rcheevos_async_http_task_callback( - retro_task_t* task, void* task_data, void* user_data, - const char* error) -{ - rcheevos_async_io_request *request = (rcheevos_async_io_request*)user_data; - http_transfer_data_t *data = (http_transfer_data_t*)task_data; - const bool aborted = rcheevos_load_aborted(); - char buffer[224]; - - if (aborted) - { - /* load was aborted. don't process the response */ - strlcpy(buffer, "Load aborted", sizeof(buffer)); - } - else if (error) - { - /* there was a communication error */ - /* if automatically requeued, don't process any further */ - if (rcheevos_async_request_failed(request, error)) - return; - - strlcpy(buffer, error, sizeof(buffer)); - } - else if (!data) - { - /* Server did not return HTTP headers */ - strlcpy(buffer, "Server communication error", sizeof(buffer)); - } - else if (!data->data || !data->len) - { - if (data->status <= 0) - { - /* something occurred which prevented the response from being processed. - * assume the server request hasn't happened and try again. */ - snprintf(buffer, sizeof(buffer), "task status code %d", data->status); - rcheevos_async_request_failed(request, buffer); - return; - } - - if (data->status != 200) /* Server returned error via status code. */ - { - snprintf(buffer, sizeof(buffer), "HTTP error code %d", data->status); - - if (request->type == CHEEVOS_ASYNC_FETCH_BADGE) - { - /* This isn't a JSON request. An empty response with a status code is a valid response. */ - if (request->handler) - request->handler(request, data, buffer, sizeof(buffer)); - } - } - else /* Server sent empty response without error status code */ - strlcpy(buffer, "No response from server", sizeof(buffer)); - } - else - { - /* indicate success unless handler provides error */ - buffer[0] = '\0'; - - /* Call appropriate handler to process the response */ - /* NOTE: data->data is not null-terminated. Most handlers assume the - * response is properly formatted or will encounter a parse failure - * before reading past the end of the data */ - if (request->handler) - request->handler(request, data, buffer, sizeof(buffer)); - } - - if (!buffer[0]) - { - /* success */ - if (request->success_message) - { - if (request->id) - CHEEVOS_LOG(RCHEEVOS_TAG "%s %u\n", request->success_message, request->id); - else - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", request->success_message); - } - } - else - { - /* encountered an error */ - char errbuf[256]; - if (request->id) - snprintf(errbuf, sizeof(errbuf), "%s %u: %s", - request->failure_message, request->id, buffer); - else - snprintf(errbuf, sizeof(errbuf), "%s: %s", - request->failure_message, buffer); - - if (!aborted) - { - switch (request->type) - { - case CHEEVOS_ASYNC_RICHPRESENCE: - case CHEEVOS_ASYNC_FETCH_BADGE: - /* Don't bother informing user when these fail */ - break; - - case CHEEVOS_ASYNC_LOGIN: - case CHEEVOS_ASYNC_RESOLVE_HASH: - if (error) - { - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - size_t len = 0; - char* ptr; - - if (rcheevos_locals->load_info.state - == RCHEEVOS_LOAD_STATE_NETWORK_ERROR) - break; - - rcheevos_locals->load_info.state = - RCHEEVOS_LOAD_STATE_NETWORK_ERROR; - - while ( - /* find the first single slash */ - request->request.url[len] != '/' - || request->request.url[len + 1] == '/' - || request->request.url[len - 1] == '/') - ++len; - - ptr = errbuf + snprintf(errbuf, sizeof(errbuf), - "Could not communicate with "); - memcpy(ptr, request->request.url, len); - ptr[len] = '\0'; - } - /* fallthrough to default */ - - default: - runloop_msg_queue_push(errbuf, 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - break; - } - } - - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", errbuf); - } - - rcheevos_async_end_request(request); -} - -static void rcheevos_async_end_request(rcheevos_async_io_request* request) -{ - rc_api_destroy_request(&request->request); - - if (request->callback) - request->callback(request->callback_data); - - /* rich presence request will be reused on next ping - reset the attempt - * counter. for all other request types, free the request object */ - if (request->type == CHEEVOS_ASYNC_RICHPRESENCE) - request->attempt_count = 0; - else - free(request); -} - -static void rcheevos_async_begin_request( - rcheevos_async_io_request* request, int init_result, - rcheevos_async_handler handler, char type, int id, - const char* success_message, const char* failure_message) -{ - if (init_result != RC_OK) - { - char errbuf[256]; - if (id) - snprintf(errbuf, sizeof(errbuf), "%s %u: %s", - failure_message, id, rc_error_str(init_result)); - else - snprintf(errbuf, sizeof(errbuf), "%s: %s", - failure_message, rc_error_str(init_result)); - - CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", errbuf); - runloop_msg_queue_push(errbuf, 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - - rcheevos_async_end_request(request); - return; - } - - request->handler = handler; - request->type = type; - request->id = id; - request->success_message = success_message; - request->failure_message = failure_message; - request->attempt_count = 0; - - if (!request->user_agent) - request->user_agent = get_rcheevos_locals()->user_agent_core; - - rcheevos_log_post_url(request->request.url, request->request.post_data); - rcheevos_async_begin_http_request(request); -} - -static bool rcheevos_async_succeeded(int result, - const rc_api_response_t* response, char buffer[], - size_t buffer_size) -{ - if (result != RC_OK) - { - strlcpy(buffer, rc_error_str(result), buffer_size); - return false; - } - - if (!response->succeeded) - { - strlcpy(buffer, response->error_message, buffer_size); - return false; - } - - return true; -} - -void rcheevos_client_initialize(void) -{ - const settings_t* settings = config_get_ptr(); - const char* host = settings->arrays.cheevos_custom_host; - if (!host[0]) - { -#ifdef HAVE_SSL - host = "https://retroachievements.org"; -#else - host = "http://retroachievements.org"; -#endif - } - - CHEEVOS_LOG(RCHEEVOS_TAG "Using host: %s\n", host); - if (!string_is_equal(host, "https://retroachievements.org")) - { - rc_api_set_host(host); - - if (!string_is_equal(host, "http://retroachievements.org")) - rc_api_set_image_host(host); - } -} - -/**************************** - * login * - ****************************/ - -static void rcheevos_async_login_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - rc_api_login_response_t api_response; - - int result = rc_api_process_login_response(&api_response, data->data); - if (rcheevos_async_succeeded(result, &api_response.response, - buffer, buffer_size)) - { - /* save the token to the config and clear the password on success */ - settings_t* settings = config_get_ptr(); - strlcpy(settings->arrays.cheevos_token, api_response.api_token, - sizeof(settings->arrays.cheevos_token)); - settings->arrays.cheevos_password[0] = '\0'; - - CHEEVOS_LOG(RCHEEVOS_TAG "%s logged in successfully\n", - api_response.display_name); - strlcpy(rcheevos_locals->displayname, api_response.display_name, - sizeof(rcheevos_locals->displayname)); - strlcpy(rcheevos_locals->username, api_response.username, - sizeof(rcheevos_locals->username)); - strlcpy(rcheevos_locals->token, api_response.api_token, - sizeof(rcheevos_locals->token)); - } - else - rcheevos_locals->token[0] = '\0'; - - rc_api_destroy_login_response(&api_response); -} - -static void rcheevos_client_login(const char* username, - const char* password, const char* token, - rcheevos_client_callback callback, void* userdata) -{ - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate login request\n"); - } - else - { - rc_api_login_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = username; - api_params.password = password; - api_params.api_token = token; - - result = rc_api_init_login_request(&request->request, &api_params); - - request->callback = callback; - request->callback_data = userdata; - - rcheevos_async_begin_request(request, result, - rcheevos_async_login_callback, - CHEEVOS_ASYNC_LOGIN, 0, - NULL, - "Error logging in"); - } -} - -void rcheevos_client_login_with_password(const char* username, - const char* password, - rcheevos_client_callback callback, void* userdata) -{ - rcheevos_client_login(username, password, NULL, callback, userdata); -} - -void rcheevos_client_login_with_token(const char* username, - const char* token, - rcheevos_client_callback callback, void* userdata) -{ - rcheevos_client_login(username, NULL, token, callback, userdata); -} - -/**************************** - * identify game * - ****************************/ - -static void rcheevos_async_resolve_hash_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rc_api_resolve_hash_response_t api_response; - int result = rc_api_process_resolve_hash_response(&api_response, - data->data); - - if (rcheevos_async_succeeded(result, &api_response.response, - buffer, buffer_size)) - { - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - rcheevos_locals->game.id = api_response.game_id; - } - - rc_api_destroy_resolve_hash_response(&api_response); -} - -void rcheevos_client_identify_game(const char* hash, - rcheevos_client_callback callback, void* userdata) -{ - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate game identification request\n"); - } - else - { - rc_api_resolve_hash_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.game_hash = hash; - - result = rc_api_init_resolve_hash_request(&request->request, &api_params); - - request->callback = callback; - request->callback_data = userdata; - - rcheevos_async_begin_request(request, result, - rcheevos_async_resolve_hash_callback, - CHEEVOS_ASYNC_RESOLVE_HASH, 0, - NULL, - "Error resolving hash"); - } -} - -/**************************** - * initialize runtime * - ****************************/ - -typedef struct rcheevos_async_initialize_runtime_data_t -{ - rc_api_fetch_game_data_response_t game_data; - rc_api_fetch_user_unlocks_response_t hardcore_unlocks; - rc_api_fetch_user_unlocks_response_t non_hardcore_unlocks; - - rcheevos_client_callback callback; - void* callback_data; -} rcheevos_async_initialize_runtime_data_t; - -static void rcheevos_client_copy_achievements( - rcheevos_async_initialize_runtime_data_t* runtime_data) -{ - unsigned i, j; - const rc_api_achievement_definition_t* definition; - rcheevos_racheevo_t *achievement; - rcheevos_locals_t *rcheevos_locals = get_rcheevos_locals(); - const settings_t *settings = config_get_ptr(); - - rcheevos_locals->game.achievements = (rcheevos_racheevo_t*) - calloc(runtime_data->game_data.num_achievements, - sizeof(rcheevos_racheevo_t)); - - achievement = rcheevos_locals->game.achievements; - if (!achievement) - { - CHEEVOS_ERR(RCHEEVOS_TAG "Could not allocate achievements\n"); - return; - } - - definition = runtime_data->game_data.achievements; - for (i = 0; i < runtime_data->game_data.num_achievements; - ++i, ++definition) - { - /* invalid definition, ignore */ - if ( - definition->category == 0 - || !definition->definition - || !definition->definition[0] - || !definition->title - || !definition->title[0] - || !definition->description - || !definition->description[0]) - continue; - - if (definition->category != 3) - { - if (!settings->bools.cheevos_test_unofficial) - continue; - - achievement->active = RCHEEVOS_ACTIVE_UNOFFICIAL - | RCHEEVOS_ACTIVE_SOFTCORE - | RCHEEVOS_ACTIVE_HARDCORE; - } - else - { - achievement->active = RCHEEVOS_ACTIVE_SOFTCORE - | RCHEEVOS_ACTIVE_HARDCORE; - - for (j = 0; j < - runtime_data->hardcore_unlocks.num_achievement_ids; ++j) - { - if (runtime_data->hardcore_unlocks.achievement_ids[j] - == definition->id) - { - achievement->active &= ~(RCHEEVOS_ACTIVE_HARDCORE - | RCHEEVOS_ACTIVE_SOFTCORE); - break; - } - } - - if ((achievement->active & RCHEEVOS_ACTIVE_SOFTCORE) != 0) - { - for (j = 0; j < - runtime_data->non_hardcore_unlocks.num_achievement_ids; - ++j) - { - if (runtime_data->non_hardcore_unlocks.achievement_ids[j] == definition->id) - { - achievement->active &= ~RCHEEVOS_ACTIVE_SOFTCORE; - break; - } - } - } - } - - achievement->id = definition->id; - achievement->title = strdup(definition->title); - achievement->description = strdup(definition->description); - achievement->badge = strdup(definition->badge_name); - achievement->points = definition->points; - - /* If an achievement has been fully unlocked, - * we don't need to keep the definition around - * as it won't be reactivated. Otherwise, - * we do have to keep a copy of it. */ - if ((achievement->active & ( - RCHEEVOS_ACTIVE_HARDCORE - | RCHEEVOS_ACTIVE_SOFTCORE)) != 0) - achievement->memaddr = strdup(definition->definition); - - ++achievement; - } - - rcheevos_locals->game.achievement_count = (unsigned)(achievement - - rcheevos_locals->game.achievements); -} - -static void rcheevos_client_copy_leaderboards( - rcheevos_async_initialize_runtime_data_t* runtime_data) -{ - unsigned i; - rcheevos_ralboard_t *leaderboard; - const rc_api_leaderboard_definition_t *definition; - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - - rcheevos_locals->game.leaderboards = (rcheevos_ralboard_t*) - calloc(runtime_data->game_data.num_leaderboards, - sizeof(rcheevos_ralboard_t)); - rcheevos_locals->game.leaderboard_count = - runtime_data->game_data.num_leaderboards; - - leaderboard = rcheevos_locals->game.leaderboards; - if (!leaderboard) - { - CHEEVOS_ERR(RCHEEVOS_TAG "Could not allocate leaderboards\n"); - return; - } - - definition = runtime_data->game_data.leaderboards; - for (i = 0; i < runtime_data->game_data.num_leaderboards; ++i, ++definition, ++leaderboard) - { - leaderboard->id = definition->id; - leaderboard->title = strdup(definition->title); - leaderboard->description = strdup(definition->description); - leaderboard->mem = strdup(definition->definition); - leaderboard->format = definition->format; - } -} - -static void rcheevos_client_initialize_runtime_rich_presence( - rcheevos_async_initialize_runtime_data_t* runtime_data) -{ - if (runtime_data->game_data.rich_presence_script && *runtime_data->game_data.rich_presence_script) - { - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - - /* Just activate the rich presence script now. - * It can't be toggled on or off, - * so there's no reason to keep the unparsed version - * around any longer than necessary, and we can avoid - * making a copy in the process. */ - int result = rc_runtime_activate_richpresence( - &rcheevos_locals->runtime, - runtime_data->game_data.rich_presence_script, NULL, 0); - - if (result != RC_OK) - { - const settings_t* settings = config_get_ptr(); - char buffer[256]; - snprintf(buffer, sizeof(buffer), - "Could not activate rich presence: %s", - rc_error_str(result)); - - if (settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - - CHEEVOS_ERR(RCHEEVOS_TAG "%s\n", buffer); - } - } -} - -static void rcheevos_client_initialize_runtime_callback(void* userdata) -{ - rcheevos_async_initialize_runtime_data_t* runtime_data = (rcheevos_async_initialize_runtime_data_t*)userdata; - - if (rcheevos_end_load_state() > 0) - return; - - if (!rcheevos_load_aborted()) - { - rcheevos_client_copy_achievements(runtime_data); - rcheevos_client_copy_leaderboards(runtime_data); - rcheevos_client_initialize_runtime_rich_presence(runtime_data); - } - - rc_api_destroy_fetch_user_unlocks_response(&runtime_data->hardcore_unlocks); - rc_api_destroy_fetch_user_unlocks_response(&runtime_data->non_hardcore_unlocks); - rc_api_destroy_fetch_game_data_response(&runtime_data->game_data); - - if (runtime_data->callback) - runtime_data->callback(runtime_data->callback_data); - - free(runtime_data); -} - -static void rcheevos_client_fetch_game_badge_callback(void* userdata) -{ - rcheevos_fetch_badge_data* data = (rcheevos_fetch_badge_data*)userdata; - rcheevos_async_initialize_runtime_data_t* runtime_data = - (rcheevos_async_initialize_runtime_data_t*)data->state->callback_data; - - free((void*)data->state->badge_directory); - free(data->state); - free(data); - - rcheevos_client_initialize_runtime_callback(runtime_data); -} - -static void rcheevos_client_fetch_game_badge(const char* badge_name, rcheevos_async_initialize_runtime_data_t* runtime_data) -{ -#if defined(HAVE_GFX_WIDGETS) /* don't need game badge unless widgets are enabled */ - char badge_fullpath[PATH_MAX_LENGTH] = ""; - char *badge_fullname = NULL; - size_t badge_fullname_size = 0; - - /* make sure the directory exists */ - fill_pathname_application_special(badge_fullpath, - sizeof(badge_fullpath), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); - - if (!path_is_directory(badge_fullpath)) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Creating %s\n", badge_fullpath); - path_mkdir(badge_fullpath); - } - - fill_pathname_slash(badge_fullpath, sizeof(badge_fullpath)); - badge_fullname = badge_fullpath + strlen(badge_fullpath); - badge_fullname_size = sizeof(badge_fullpath) - - (badge_fullname - badge_fullpath); - - snprintf(badge_fullname, - badge_fullname_size, "i%s" FILE_PATH_PNG_EXTENSION, badge_name); - - /* check if it's already available */ - if (path_is_valid(badge_fullpath)) - return; - -#ifdef CHEEVOS_LOG_BADGES - CHEEVOS_LOG(RCHEEVOS_TAG "Downloading game badge %s\n", badge_name); -#endif - - { - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - rcheevos_fetch_badge_data* data = (rcheevos_fetch_badge_data*) - calloc(1, sizeof(rcheevos_fetch_badge_data)); - rcheevos_fetch_badge_state* state = (rcheevos_fetch_badge_state*) - calloc(1, sizeof(rcheevos_fetch_badge_state)); - - if (!request || !data || !state) - { - CHEEVOS_LOG(RCHEEVOS_TAG - "Failed to allocate fetch badge request\n"); - } - else - { - rc_api_fetch_image_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.image_name = badge_name; - api_params.image_type = RC_IMAGE_TYPE_GAME; - - result = rc_api_init_fetch_image_request(&request->request, &api_params); - - strlcpy(state->requested_badges[0], badge_fullname, sizeof(state->requested_badges[0])); - *badge_fullname = '\0'; - state->badge_directory = strdup(badge_fullpath); - state->callback_data = runtime_data; - - data->state = state; - data->request_index = 0; - data->callback = rcheevos_client_fetch_game_badge_callback; - - request->callback_data = data; - - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA); - rcheevos_async_begin_request(request, result, - rcheevos_async_fetch_badge_callback, - CHEEVOS_ASYNC_FETCH_BADGE, atoi(badge_name), NULL, - "Error fetching game badge"); - } - } -#endif -} - -static void rcheevos_async_fetch_user_unlocks_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rcheevos_async_initialize_runtime_data_t* runtime_data = - (rcheevos_async_initialize_runtime_data_t*)request->callback_data; - int result; - - if (request->type == CHEEVOS_ASYNC_FETCH_HARDCORE_USER_UNLOCKS) - { - result = rc_api_process_fetch_user_unlocks_response( - &runtime_data->hardcore_unlocks, data->data); - rcheevos_async_succeeded(result, - &runtime_data->hardcore_unlocks.response, - buffer, buffer_size); - } - else - { - result = rc_api_process_fetch_user_unlocks_response( - &runtime_data->non_hardcore_unlocks, data->data); - rcheevos_async_succeeded(result, - &runtime_data->non_hardcore_unlocks.response, - buffer, buffer_size); - } -} - -static void rcheevos_async_fetch_game_data_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rcheevos_async_initialize_runtime_data_t* runtime_data = - (rcheevos_async_initialize_runtime_data_t*)request->callback_data; - -#ifdef CHEEVOS_SAVE_JSON - filestream_write_file(CHEEVOS_SAVE_JSON, data->data, data->len); -#endif - - int result = rc_api_process_fetch_game_data_response( - &runtime_data->game_data, data->data); - if (rcheevos_async_succeeded(result, &runtime_data->game_data.response, buffer, buffer_size)) - { - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - rcheevos_locals->game.title = strdup( - runtime_data->game_data.title); - rcheevos_locals->game.console_id = - runtime_data->game_data.console_id; - - snprintf(rcheevos_locals->game.badge_name, sizeof(rcheevos_locals->game.badge_name), - "i%s", runtime_data->game_data.image_name); - rcheevos_client_fetch_game_badge(runtime_data->game_data.image_name, runtime_data); - } - else - { - rcheevos_unload(); - } -} - -void rcheevos_client_initialize_runtime(unsigned game_id, - rcheevos_client_callback callback, void* userdata) -{ - rcheevos_async_io_request* request; - const settings_t *settings = config_get_ptr(); - const rcheevos_locals_t *rcheevos_locals = get_rcheevos_locals(); - rcheevos_async_initialize_runtime_data_t *data = - (rcheevos_async_initialize_runtime_data_t*) - malloc(sizeof(rcheevos_async_initialize_runtime_data_t)); - - if (!data) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate runtime initialization data\n"); - return; - } - - data->callback = callback; - data->callback_data = userdata; - request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate game data fetch request\n"); - } - else - { -#ifdef CHEEVOS_JSON_OVERRIDE - char buffer[128]; - size_t size = 0; - char* contents; - http_transfer_data_t transfer_data; - FILE* file = fopen(CHEEVOS_JSON_OVERRIDE, "rb"); - - fseek(file, 0, SEEK_END); - size = ftell(file); - fseek(file, 0, SEEK_SET); - - contents = (char*)malloc(size + 1); - fread((void*)contents, 1, size, file); - fclose(file); - - contents[size] = 0; - - transfer_data.data = contents; - transfer_data.len = size; - transfer_data.status = 200; - - request->callback_data = data; - rcheevos_async_fetch_game_data_callback(request, &transfer_data, buffer, sizeof(buffer)); - - free(contents); -#else - rc_api_fetch_game_data_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = rcheevos_locals->username; - api_params.api_token = rcheevos_locals->token; - api_params.game_id = rcheevos_locals->game.id; - - result = rc_api_init_fetch_game_data_request(&request->request, &api_params); - - request->callback = rcheevos_client_initialize_runtime_callback; - request->callback_data = data; - - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA); - rcheevos_async_begin_request(request, result, - rcheevos_async_fetch_game_data_callback, - CHEEVOS_ASYNC_FETCH_GAME_DATA, rcheevos_locals->game.id, - "Fetched game data", - "Error fetching game data"); -#endif - } - - if (settings->bools.cheevos_start_active) - { - memset(&data->hardcore_unlocks, 0, sizeof(data->hardcore_unlocks)); - memset(&data->non_hardcore_unlocks, 0, sizeof(data->non_hardcore_unlocks)); - - data->hardcore_unlocks.num_achievement_ids = 0; - data->non_hardcore_unlocks.num_achievement_ids = 0; - } - else - { - int i; - for (i = 0; i < 2; ++i) - { - request = (rcheevos_async_io_request*)calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate user unlock request\n"); - } - else - { - rc_api_fetch_user_unlocks_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = rcheevos_locals->username; - api_params.api_token = rcheevos_locals->token; - api_params.game_id = rcheevos_locals->game.id; - api_params.hardcore = i; - - result = rc_api_init_fetch_user_unlocks_request(&request->request, &api_params); - - request->callback = rcheevos_client_initialize_runtime_callback; - request->callback_data = data; - - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA); - if (i == 0) - { - rcheevos_async_begin_request(request, result, - rcheevos_async_fetch_user_unlocks_callback, - CHEEVOS_ASYNC_FETCH_USER_UNLOCKS, - rcheevos_locals->game.id, - "Fetched user unlocks", - "Error fetching user unlocks"); - } - else - { - rcheevos_async_begin_request(request, result, - rcheevos_async_fetch_user_unlocks_callback, - CHEEVOS_ASYNC_FETCH_HARDCORE_USER_UNLOCKS, - rcheevos_locals->game.id, - "Fetched hardcore user unlocks", - "Error fetching hardcore user unlocks"); - } - } - } - } -} - -/**************************** - * ping * - ****************************/ - -static retro_time_t rcheevos_client_prepare_ping( - rcheevos_async_io_request* request) -{ - rc_api_ping_request_t api_params; - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - const settings_t *settings = config_get_ptr(); - const bool cheevos_richpresence_enable = - rcheevos_hardcore_active() || - settings->bools.cheevos_richpresence_enable; - char buffer[256] = ""; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = rcheevos_locals->username; - api_params.api_token = rcheevos_locals->token; - api_params.game_id = request->id; - - if (cheevos_richpresence_enable) - { - rcheevos_get_richpresence(buffer, sizeof(buffer)); - api_params.rich_presence = buffer; - } - - rc_api_init_ping_request(&request->request, &api_params); - - rcheevos_log_post_url(request->request.url, - request->request.post_data); - -#ifdef HAVE_PRESENCE - presence_update(PRESENCE_RETROACHIEVEMENTS); -#endif - - /* Update rich presence every two minutes */ - if (cheevos_richpresence_enable) - return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY; - - /* Send ping every four minutes */ - return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY * 2; -} - -static void rcheevos_async_ping_handler(retro_task_t* task) -{ - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - task->user_data; - - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - if (request->id != (int)rcheevos_locals->game.id) - { - CHEEVOS_LOG(RCHEEVOS_TAG - "Stopping periodic rich presence update task for game %u\n", request->id); - /* game changed; stop the recurring task - a new one will - * be scheduled if a new game is loaded */ - task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); - /* request->request was destroyed - * in rcheevos_async_http_task_callback */ - free(request); - return; - } - - /* update the request and set the task to fire again in - * two minutes */ - task->when = rcheevos_client_prepare_ping(request); - - /* start the HTTP request */ - rcheevos_async_begin_http_request(request); -} - -/**************************** - * start session * - ****************************/ - -static void rcheevos_async_start_session_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rc_api_start_session_response_t api_response; - - int result = rc_api_process_start_session_response( - &api_response, data->data); - rcheevos_async_succeeded(result, - &api_response.response, buffer, buffer_size); - rc_api_destroy_start_session_response(&api_response); -} - -void rcheevos_client_start_session(unsigned game_id) -{ - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - - /* the core won't change while a session is active, so only - * calculate the user agent once */ - rcheevos_get_user_agent(rcheevos_locals, - rcheevos_locals->user_agent_core, - sizeof(rcheevos_locals->user_agent_core)); - - /* schedule the first rich presence call in 30 seconds */ - { - rcheevos_async_io_request *request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG - "Failed to allocate rich presence request\n"); - } - else - { - retro_task_t* task = task_init(); - - request->id = game_id; - request->type = CHEEVOS_ASYNC_RICHPRESENCE; - request->user_agent = rcheevos_locals->user_agent_core; - request->failure_message = "Error sending ping"; - - task->handler = rcheevos_async_ping_handler; - task->user_data = request; - task->progress = -1; - task->when = cpu_features_get_time_usec() + - CHEEVOS_PING_FREQUENCY / 4; - - CHEEVOS_LOG(RCHEEVOS_TAG - "Starting periodic rich presence update task for game %u\n", game_id); - task_queue_push(task); - } - } - - /* send the new session request */ - { - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG - "Failed to allocate new session request\n"); - } - else - { - rc_api_start_session_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = rcheevos_locals->username; - api_params.api_token = rcheevos_locals->token; - api_params.game_id = game_id; - - result = rc_api_init_start_session_request( - &request->request, &api_params); - - rcheevos_async_begin_request(request, result, - rcheevos_async_start_session_callback, - CHEEVOS_ASYNC_START_SESSION, game_id, - "Started session for game", - "Error starting session for game"); - } - } -} - - -/**************************** - * fetch badge * - ****************************/ - -static bool rcheevos_fetch_next_badge(rcheevos_fetch_badge_state* state); - -static void rcheevos_end_fetch_badges(rcheevos_fetch_badge_state* state) -{ - if (state->callback) - state->callback(state->callback_data); - - free((void*)state->badge_directory); - free(state); -} - -static void rcheevos_async_download_next_badge(void* userdata) -{ - rcheevos_fetch_badge_data* badge_data = - (rcheevos_fetch_badge_data*)userdata; - rcheevos_fetch_next_badge(badge_data->state); - - if (rcheevos_end_load_state() == 0) - rcheevos_end_fetch_badges(badge_data->state); - - free(badge_data); -} - -static void rcheevos_async_fetch_badge_complete(rcheevos_fetch_badge_data* badge_data) -{ -#ifdef HAVE_THREADS - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - - slock_lock(rcheevos_locals->load_info.request_lock); -#endif - badge_data->state->requested_badges[badge_data->request_index][0] = '\0'; -#ifdef HAVE_THREADS - slock_unlock(rcheevos_locals->load_info.request_lock); -#endif - - if (badge_data->callback) - badge_data->callback(badge_data); -} - -static void rcheevos_async_write_badge(retro_task_t* task) -{ - char badge_fullpath[PATH_MAX_LENGTH]; - rcheevos_fetch_badge_data* badge_data = - (rcheevos_fetch_badge_data*)task->user_data; - - fill_pathname_join_special(badge_fullpath, - badge_data->state->badge_directory, - badge_data->state->requested_badges[badge_data->request_index], - sizeof(badge_fullpath)); - - if (!filestream_write_file(badge_fullpath, badge_data->data, badge_data->data_len)) - { - CHEEVOS_ERR(RCHEEVOS_TAG "Error writing badge %s\n", - badge_fullpath); - } - - free(badge_data->data); - badge_data->data = NULL; - badge_data->data_len = 0; - - task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); - - rcheevos_async_fetch_badge_complete(badge_data); -} - -static void rcheevos_async_fetch_badge_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rcheevos_fetch_badge_data* badge_data = - (rcheevos_fetch_badge_data*)request->callback_data; - retro_task_t* task; - - if (!data->data) - { - /* error retrieving badge, nothing to write. just call the callback */ - rcheevos_async_fetch_badge_complete(badge_data); - return; - } - - /* take ownership of the file data */ - badge_data->data = data->data; - badge_data->data_len = data->len; - data->data = NULL; - data->len = 0; - - /* this is called on the primary thread. use a task to write the file from a background thread */ - task = task_init(); - task->handler = rcheevos_async_write_badge; - task->user_data = badge_data; - task_queue_push(task); -} - -static bool rcheevos_client_fetch_badge( - const char* badge_name, int locked, - rcheevos_fetch_badge_state* state) -{ - char badge_fullpath[PATH_MAX_LENGTH]; - char* badge_fullname = NULL; - size_t badge_fullname_size = 0; - int request_index = -1; - - if (!badge_name || !badge_name[0]) - return false; - - strlcpy(badge_fullpath, - state->badge_directory, sizeof(badge_fullpath)); - fill_pathname_slash(badge_fullpath, sizeof(badge_fullpath)); - badge_fullname = badge_fullpath + strlen(badge_fullpath); - badge_fullname_size = sizeof(badge_fullpath) - - (badge_fullname - badge_fullpath); - - snprintf(badge_fullname, - badge_fullname_size, "%s%s" FILE_PATH_PNG_EXTENSION, - badge_name, locked ? "_lock" : ""); - - /* check if it's already available */ - if (path_is_valid(badge_fullpath)) - return false; - - /* check if it's already requested */ - { - int i; - int found_index = -1; -#ifdef HAVE_THREADS - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - slock_lock(rcheevos_locals->load_info.request_lock); -#endif - for (i = RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS - 1; i >= 0; --i) - { - if (!state->requested_badges[i][0]) - request_index = i; - else if (string_is_equal(badge_fullname, - state->requested_badges[i])) - { - found_index = i; - break; - } - } - - if (found_index == -1) - { - /* unexpected - but if it happens, - * the queue is full. Pretend we found - * a match to prevent an exception */ - if (request_index == -1) - found_index = 0; - else - strlcpy(state->requested_badges[request_index], - badge_fullname, - sizeof(state->requested_badges[request_index])); - } -#ifdef HAVE_THREADS - slock_unlock(rcheevos_locals->load_info.request_lock); -#endif - if (found_index != -1) - return false; - } - - /* request the new badge */ -#ifdef CHEEVOS_LOG_BADGES - CHEEVOS_LOG(RCHEEVOS_TAG "Downloading badge %s\n", badge_name); -#endif - - { - rcheevos_async_io_request* request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - rcheevos_fetch_badge_data* data = (rcheevos_fetch_badge_data*) - calloc(1, sizeof(rcheevos_fetch_badge_data)); - - if (!request || !data) - { - CHEEVOS_LOG(RCHEEVOS_TAG - "Failed to allocate fetch badge request\n"); - } - else - { - rc_api_fetch_image_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.image_name = badge_name; - api_params.image_type = locked - ? RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED - : RC_IMAGE_TYPE_ACHIEVEMENT; - - result = rc_api_init_fetch_image_request(&request->request, &api_params); - - data->state = state; - data->request_index = request_index; - data->callback = rcheevos_async_download_next_badge; - - request->callback_data = data; - - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_BADGES); - rcheevos_async_begin_request(request, result, - rcheevos_async_fetch_badge_callback, - CHEEVOS_ASYNC_FETCH_BADGE, atoi(badge_name), NULL, - "Error fetching badge"); - } - } - - return true; -} - -static bool rcheevos_fetch_next_badge(rcheevos_fetch_badge_state* state) -{ - if (rcheevos_load_aborted()) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Load aborted while fetching badges\n"); - } - else - { - int active = 0; - const rcheevos_racheevo_t *cheevo = NULL; - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - - /* fetch badges for current state of achievements first */ - do - { -#ifdef HAVE_THREADS - slock_lock(rcheevos_locals->load_info.request_lock); -#endif - if ( state->locked_badge_fetch_index - < rcheevos_locals->game.achievement_count) - cheevo = &rcheevos_locals->game.achievements[state->locked_badge_fetch_index++]; - else - cheevo = NULL; -#ifdef HAVE_THREADS - slock_unlock(rcheevos_locals->load_info.request_lock); -#endif - - if (!cheevo) - break; - - active = (cheevo->active - & (RCHEEVOS_ACTIVE_HARDCORE | RCHEEVOS_ACTIVE_SOFTCORE)); - if (rcheevos_client_fetch_badge(cheevo->badge, active, state)) - return true; - - } while (true); - - /* then fetch badges for unlocked state so they're ready when the user unlocks something */ - do - { -#ifdef HAVE_THREADS - slock_lock(rcheevos_locals->load_info.request_lock); -#endif - if (state->badge_fetch_index < rcheevos_locals->game.achievement_count) - cheevo = &rcheevos_locals->game.achievements[state->badge_fetch_index++]; - else - cheevo = NULL; - -#ifdef HAVE_THREADS - slock_unlock(rcheevos_locals->load_info.request_lock); -#endif - - if (!cheevo) - break; - - if (rcheevos_client_fetch_badge(cheevo->badge, 0, state)) - return true; - - } while (true); - } - - return false; -} - -void rcheevos_client_fetch_badges(rcheevos_client_callback callback, void* userdata) -{ -#if defined(HAVE_MENU) || defined(HAVE_GFX_WIDGETS) /* don't need badges unless menu or widgets are enabled */ - rcheevos_fetch_badge_state* state = NULL; - char badge_fullpath[PATH_MAX_LENGTH] = ""; -#if !defined(HAVE_GFX_WIDGETS) /* we always want badges if widgets are enabled */ - settings_t* settings = config_get_ptr(); - /* User has explicitly disabled badges */ - if (!settings->bools.cheevos_badges_enable) - return; - - /* badges are only needed for xmb and ozone menus */ - if (!string_is_equal(settings->arrays.menu_driver, "xmb") && - !string_is_equal(settings->arrays.menu_driver, "ozone")) - return; -#endif /* !defined(HAVE_GFX_WIDGETS) */ - - /* make sure the directory exists */ - fill_pathname_application_special(badge_fullpath, - sizeof(badge_fullpath), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); - - if (!path_is_directory(badge_fullpath)) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Creating %s\n", badge_fullpath); - path_mkdir(badge_fullpath); - } - - /* start the download task */ - state = (rcheevos_fetch_badge_state*) - calloc(1, sizeof(rcheevos_fetch_badge_state)); - - if (!state) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate fetch badge state\n"); - } - else - { - int num_concurrent = RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS; - - state->badge_directory = strdup(badge_fullpath); - state->locked_badge_fetch_index = 0; - state->badge_fetch_index = 0; - state->callback = callback; - state->callback_data = userdata; - - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_BADGES); - - /* fetch the placeholder image */ - if (rcheevos_client_fetch_badge("00000", 0, state)) - num_concurrent--; - - /* queue up additional requests so up to {num_concurrent} downloads are queued */ - while (num_concurrent--) - { - if (!rcheevos_fetch_next_badge(state)) - break; - } - - if (rcheevos_end_load_state() == 0) - rcheevos_end_fetch_badges(state); - } -#endif /* defined(HAVE_MENU) || defined(HAVE_GFX_WIDGETS) */ -} - -#undef RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS - - -/**************************** - * award achievement * - ****************************/ - -static void rcheevos_async_award_achievement_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t *data, char buffer[], size_t buffer_size) -{ - rc_api_award_achievement_response_t api_response; - - int result = rc_api_process_award_achievement_response( - &api_response, data->data); - if (rcheevos_async_succeeded(result, &api_response.response, - buffer, buffer_size)) - { - if ((int)api_response.awarded_achievement_id != request->id) - snprintf(buffer, buffer_size, "Achievement %u awarded instead", - (unsigned)api_response.awarded_achievement_id); - else if (api_response.response.error_message) - { - /* previously unlocked achievements are returned as a "successful" error */ - CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u: %s\n", - request->id, api_response.response.error_message); - } - - if (api_response.achievements_remaining == 0) - rcheevos_show_mastery_placard(); - } - - rc_api_destroy_award_achievement_response(&api_response); -} - -void rcheevos_client_award_achievement(unsigned achievement_id) -{ - rcheevos_async_io_request *request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate unlock request for achievement %u\n", - achievement_id); - } - else - { - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - rc_api_award_achievement_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = rcheevos_locals->username; - api_params.api_token = rcheevos_locals->token; - api_params.achievement_id = achievement_id; - api_params.hardcore = rcheevos_locals->hardcore_active ? 1 : 0; - api_params.game_hash = rcheevos_locals->game.hash; - - result = rc_api_init_award_achievement_request(&request->request, - &api_params); - - rcheevos_async_begin_request(request, result, - rcheevos_async_award_achievement_callback, - CHEEVOS_ASYNC_AWARD_ACHIEVEMENT, achievement_id, - "Awarded achievement", - "Error awarding achievement"); - } -} - - -/**************************** - * submit leaderboard * - ****************************/ - -static void rcheevos_async_submit_lboard_entry_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) -{ - rc_api_submit_lboard_entry_response_t api_response; - int result = rc_api_process_submit_lboard_entry_response( - &api_response, data->data); - - /* not currently doing anything with the response */ - if (rcheevos_async_succeeded(result, &api_response.response, buffer, - buffer_size)) { } - - rc_api_destroy_submit_lboard_entry_response(&api_response); -} - -void rcheevos_client_submit_lboard_entry(unsigned leaderboard_id, - int value) -{ - rcheevos_async_io_request *request = (rcheevos_async_io_request*) - calloc(1, sizeof(rcheevos_async_io_request)); - if (!request) - { - CHEEVOS_LOG(RCHEEVOS_TAG - "Failed to allocate request for lboard %u submit\n", - leaderboard_id); - } - else - { - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - rc_api_submit_lboard_entry_request_t api_params; - int result; - - memset(&api_params, 0, sizeof(api_params)); - api_params.username = rcheevos_locals->username; - api_params.api_token = rcheevos_locals->token; - api_params.leaderboard_id = leaderboard_id; - api_params.score = value; - api_params.game_hash = rcheevos_locals->game.hash; - - result = rc_api_init_submit_lboard_entry_request(&request->request, - &api_params); - - rcheevos_async_begin_request(request, result, - rcheevos_async_submit_lboard_entry_callback, - CHEEVOS_ASYNC_SUBMIT_LBOARD, leaderboard_id, - "Submitted leaderboard", - "Error submitting leaderboard"); - } -} -#endif /* HAVE_RC_CLIENT */ diff --git a/cheevos/cheevos_client.h b/cheevos/cheevos_client.h index 96aba4a0028..56dee4c20e0 100644 --- a/cheevos/cheevos_client.h +++ b/cheevos/cheevos_client.h @@ -20,8 +20,6 @@ RETRO_BEGIN_DECLS -#ifdef HAVE_RC_CLIENT - void rcheevos_client_download_placeholder_badge(void); void rcheevos_client_download_game_badge(const rc_client_game_t* game); void rcheevos_client_download_achievement_badges(rc_client_t* client); @@ -31,31 +29,6 @@ void rcheevos_client_download_badge_from_url(const char* url, const char* badge_ void rcheevos_client_server_call(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); -#else - -typedef void (*rcheevos_client_callback)(void* userdata); - -void rcheevos_client_initialize(void); - -void rcheevos_client_login_with_password(const char* username, const char* password, - rcheevos_client_callback callback, void* userdata); -void rcheevos_client_login_with_token(const char* username, const char* token, - rcheevos_client_callback callback, void* userdata); - -void rcheevos_client_identify_game(const char* hash, rcheevos_client_callback callback, void* userdata); - -void rcheevos_client_initialize_runtime(unsigned game_id, rcheevos_client_callback callback, void* userdata); - -void rcheevos_client_start_session(unsigned game_id); -void rcheevos_client_award_achievement(unsigned achievement_id); -void rcheevos_client_submit_lboard_entry(unsigned leaderboard_id, int value); - -void rcheevos_client_fetch_badges(rcheevos_client_callback callback, void* userdata); - -void rcheevos_log_url(const char* url); - -#endif /* HAVE_RC_CLIENT */ - void rcheevos_get_user_agent(rcheevos_locals_t* locals, char* buffer, size_t len); RETRO_END_DECLS diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h index 51ac28cd080..16907a5faca 100644 --- a/cheevos/cheevos_locals.h +++ b/cheevos/cheevos_locals.h @@ -17,8 +17,6 @@ #ifndef __RARCH_CHEEVOS_LOCALS_H #define __RARCH_CHEEVOS_LOCALS_H -#define HAVE_RC_CLIENT 1 - #include "../deps/rcheevos/include/rc_client.h" #include "../deps/rcheevos/include/rc_runtime.h" #include "../deps/rcheevos/src/rc_libretro.h" @@ -60,69 +58,6 @@ RETRO_BEGIN_DECLS * State * ************************************************************************/ -#ifndef HAVE_RC_CLIENT -enum -{ - RCHEEVOS_ACTIVE_SOFTCORE = 1 << 0, - RCHEEVOS_ACTIVE_HARDCORE = 1 << 1, - RCHEEVOS_ACTIVE_UNOFFICIAL = 1 << 2, - RCHEEVOS_ACTIVE_UNSUPPORTED = 1 << 3 -}; - -typedef struct rcheevos_racheevo_t -{ - const char* title; - const char* description; - const char* badge; - const char* memaddr; - unsigned id; - unsigned points; - - retro_time_t unlock_time; - uint8_t active; - -#ifdef HAVE_MENU - uint8_t menu_bucket; - uint8_t menu_progress; - uint8_t menu_badge_grayscale; - uintptr_t menu_badge_texture; -#endif - -} rcheevos_racheevo_t; - -typedef struct rcheevos_ralboard_t -{ - const char* title; - const char* description; - const char* mem; - unsigned id; - unsigned format; - -#ifdef HAVE_GFX_WIDGETS - int value; - unsigned value_hash; - uint8_t active_tracker_id; -#endif - -} rcheevos_ralboard_t; - - -enum rcheevos_load_state -{ - RCHEEVOS_LOAD_STATE_NONE, - RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME, - RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA, - RCHEEVOS_LOAD_STATE_STARTING_SESSION, - RCHEEVOS_LOAD_STATE_FETCHING_BADGES, - RCHEEVOS_LOAD_STATE_DONE, - RCHEEVOS_LOAD_STATE_UNKNOWN_GAME, - RCHEEVOS_LOAD_STATE_NETWORK_ERROR, - RCHEEVOS_LOAD_STATE_LOGIN_FAILED, - RCHEEVOS_LOAD_STATE_ABORTED -}; - -#endif /* HAVE_RC_CLIENT */ - enum rcheevos_summary_notif { RCHEEVOS_SUMMARY_ALLGAMES = 0, @@ -131,49 +66,6 @@ enum rcheevos_summary_notif RCHEEVOS_SUMMARY_LAST }; -#ifndef HAVE_RC_CLIENT - -typedef struct rcheevos_load_info_t -{ - enum rcheevos_load_state state; - int hashes_tried; - int outstanding_requests; -#ifdef HAVE_THREADS - slock_t* request_lock; -#endif -} rcheevos_load_info_t; - -typedef struct rcheevos_game_info_t -{ - int id; - int console_id; - char* title; - char badge_name[16]; - const char* hash; - bool mastery_placard_shown; - - rc_libretro_hash_set_t hashes; - - rcheevos_racheevo_t* achievements; - rcheevos_ralboard_t* leaderboards; - - unsigned achievement_count; - unsigned leaderboard_count; - -} rcheevos_game_info_t; - -#ifdef HAVE_MENU - -typedef struct rcheevos_menuitem_t -{ - rcheevos_racheevo_t* cheevo; - enum msg_hash_enums state_label_idx; -} rcheevos_menuitem_t; - -#endif - -#else /* HAVE_RC_CLIENT */ - #ifdef HAVE_MENU typedef struct rcheevos_menuitem_t @@ -187,16 +79,9 @@ typedef struct rcheevos_menuitem_t #endif -#endif /* HAVE_RC_CLIENT */ - typedef struct rcheevos_locals_t { -#ifdef HAVE_RC_CLIENT rc_client_t* client; /* rcheevos client state */ -#else - rc_runtime_t runtime; /* rcheevos runtime state */ - rcheevos_game_info_t game; /* information about the current game */ -#endif rc_libretro_memory_regions_t memory;/* achievement addresses to core memory mappings */ #ifdef HAVE_THREADS @@ -204,11 +89,6 @@ typedef struct rcheevos_locals_t bool game_placard_requested; /* request to display game placard */ #endif -#ifndef HAVE_RC_CLIENT - char displayname[32]; /* name to display in messages */ - char username[32]; /* case-corrected username */ - char token[32]; /* user's session token */ -#endif char user_agent_prefix[128]; /* RetroArch/OS version information */ char user_agent_core[256]; /* RetroArch/OS/Core version information */ @@ -218,41 +98,14 @@ typedef struct rcheevos_locals_t unsigned menuitem_count; /* current number of items in the menuitems array */ #endif -#ifdef HAVE_RC_CLIENT bool hardcore_allowed; /* prevents enabling hardcore if illegal settings detected */ bool hardcore_being_enabled; /* allows callers to detect hardcore mode while it's being enabled */ -#else - -#ifdef HAVE_GFX_WIDGETS - unsigned active_lboard_trackers; /* bit mask of active leaderboard tracker ids */ - rcheevos_racheevo_t* tracker_achievement; - float tracker_progress; -#endif - - rcheevos_load_info_t load_info; /* load info */ - - uint32_t unpaused_frames; /* number of unpaused frames before next pause is allowed */ - - bool hardcore_active; /* hardcore functionality is active */ - bool loaded; /* load task has completed */ -#ifdef HAVE_GFX_WIDGETS - bool assign_new_trackers; /* a new leaderboard was started and needs a tracker assigned */ -#endif - -#endif bool core_supports; /* false if core explicitly disables achievements */ } rcheevos_locals_t; rcheevos_locals_t* get_rcheevos_locals(void); -#ifndef HAVE_RC_CLIENT -void rcheevos_begin_load_state(enum rcheevos_load_state state); -int rcheevos_end_load_state(void); -bool rcheevos_load_aborted(void); -void rcheevos_show_mastery_placard(void); -#endif - RETRO_END_DECLS #endif /* __RARCH_CHEEVOS_LOCALS_H */ diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c index 843786db8d6..a9cb09423b9 100644 --- a/cheevos/cheevos_menu.c +++ b/cheevos/cheevos_menu.c @@ -43,8 +43,6 @@ * check for the file again. */ #define MENU_BADGE_RETRY_RELOAD_FRAMES 64 -#ifdef HAVE_RC_CLIENT - #if HAVE_MENU bool rcheevos_menu_get_state(unsigned menu_offset, char* buffer, size_t buffer_size) @@ -577,657 +575,4 @@ uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool downlo return tex; } -#else /* !HAVE_RC_CLIENT */ - -#if HAVE_MENU - -enum rcheevos_menuitem_bucket -{ - RCHEEVOS_MENUITEM_BUCKET_UNKNOWN = 0, - RCHEEVOS_MENUITEM_BUCKET_LOCKED, - RCHEEVOS_MENUITEM_BUCKET_UNLOCKED, - RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED, - RCHEEVOS_MENUITEM_BUCKET_UNOFFICIAL, - RCHEEVOS_MENUITEM_BUCKET_RECENTLY_UNLOCKED, - RCHEEVOS_MENUITEM_BUCKET_ACTIVE_CHALLENGE, - RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE -}; - -static void rcheevos_menu_update_bucket(rcheevos_racheevo_t* cheevo) -{ - cheevo->menu_progress = 0; - - /* Non-active unsupported achievement */ - if (cheevo->active & RCHEEVOS_ACTIVE_UNSUPPORTED) - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED; - /* Non-active unlocked in hardcore achievement */ - else if (!(cheevo->active & RCHEEVOS_ACTIVE_HARDCORE)) - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_UNLOCKED; - else - { - rc_trigger_t* trigger; - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - - if (!rcheevos_locals->hardcore_active && !(cheevo->active & RCHEEVOS_ACTIVE_SOFTCORE)) - { - /* Non-active unlocked in softcore achievement in softcore mode */ - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_UNLOCKED; - return; - } - - /* Active achievement */ - if (cheevo->active & RCHEEVOS_ACTIVE_UNOFFICIAL) - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_UNOFFICIAL; - else - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_LOCKED; - - trigger = rc_runtime_get_achievement(&rcheevos_locals->runtime, cheevo->id); - if (trigger) - { - if (trigger->measured_value && trigger->measured_value != 0xFFFFFFFF && trigger->measured_target) - { - const unsigned long clamped_value = (unsigned long) - MIN(trigger->measured_value, trigger->measured_target); - cheevo->menu_progress = - (uint8_t)((clamped_value * 100) / trigger->measured_target); - } - - if (trigger->state == RC_TRIGGER_STATE_PRIMED) - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_ACTIVE_CHALLENGE; - else if (cheevo->menu_progress >= 80) - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE; - } - } -} - -static void rcheevos_menu_update_buckets(void) -{ - const rcheevos_locals_t *rcheevos_locals = get_rcheevos_locals(); - rcheevos_racheevo_t *cheevo = rcheevos_locals->game.achievements; - rcheevos_racheevo_t *stop = cheevo + - rcheevos_locals->game.achievement_count; - - while (cheevo < stop) - { - rcheevos_menu_update_bucket(cheevo); - ++cheevo; - } -} - -bool rcheevos_menu_get_state(unsigned menu_offset, char *buffer, size_t buffer_size) -{ - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - if (menu_offset < rcheevos_locals->menuitem_count) - { - const rcheevos_menuitem_t* menuitem = &rcheevos_locals->menuitems[menu_offset]; - const rcheevos_racheevo_t* cheevo = menuitem->cheevo; - if (cheevo) - { - if (cheevo->menu_progress) - { - const int written = snprintf(buffer, buffer_size, "%s - ", - msg_hash_to_str(menuitem->state_label_idx)); - if (buffer_size - written > 0) - rc_runtime_format_achievement_measured(&rcheevos_locals->runtime, - cheevo->id, buffer + written, buffer_size - written); - } - else - strlcpy(buffer, msg_hash_to_str(menuitem->state_label_idx), buffer_size); - - return true; - } - } - - if (buffer) - buffer[0] = '\0'; - - return false; -} - -bool rcheevos_menu_get_sublabel(unsigned menu_offset, char *buffer, size_t buffer_size) -{ - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - if (menu_offset < rcheevos_locals->menuitem_count) - { - const rcheevos_racheevo_t* cheevo = rcheevos_locals->menuitems[menu_offset].cheevo; - if (cheevo && buffer) - { - strlcpy(buffer, cheevo->description, buffer_size); - return true; - } - } - - if (buffer) - buffer[0] = '\0'; - - return false; -} - -void rcheevos_menu_reset_badges(void) -{ - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - rcheevos_racheevo_t* cheevo = rcheevos_locals->game.achievements; - rcheevos_racheevo_t* stop = cheevo + rcheevos_locals->game.achievement_count; - - while (cheevo < stop) - { - if (cheevo->menu_badge_texture) - { - video_driver_texture_unload(&cheevo->menu_badge_texture); - cheevo->menu_badge_texture = 0; - cheevo->menu_badge_grayscale = MENU_BADGE_RETRY_RELOAD_FRAMES; - } - ++cheevo; - } -} - -static rcheevos_menuitem_t* rcheevos_menu_allocate( - rcheevos_locals_t* rcheevos_locals, rcheevos_racheevo_t* cheevo) -{ - rcheevos_menuitem_t* menuitem; - - if (rcheevos_locals->menuitem_count == rcheevos_locals->menuitem_capacity) - { - if (rcheevos_locals->menuitems) - { - rcheevos_menuitem_t *new_menuitems; - rcheevos_locals->menuitem_capacity += 32; - new_menuitems = (rcheevos_menuitem_t*)realloc(rcheevos_locals->menuitems, - rcheevos_locals->menuitem_capacity * sizeof(rcheevos_menuitem_t)); - - if (new_menuitems) - rcheevos_locals->menuitems = new_menuitems; - else - { - /* realloc failed */ - CHEEVOS_ERR(RCHEEVOS_TAG " could not allocate space for %u menu items\n", - rcheevos_locals->menuitem_capacity); - rcheevos_locals->menuitem_capacity -= 32; - return NULL; - } - } - else - { - rcheevos_locals->menuitem_capacity = 64; - rcheevos_locals->menuitems = (rcheevos_menuitem_t*) - malloc(rcheevos_locals->menuitem_capacity * sizeof(rcheevos_menuitem_t)); - - if (!rcheevos_locals->menuitems) - { - /* malloc failed */ - CHEEVOS_ERR(RCHEEVOS_TAG " could not allocate space for %u menu items\n", - rcheevos_locals->menuitem_capacity); - rcheevos_locals->menuitem_capacity = 0; - return NULL; - } - } - } - - menuitem = &rcheevos_locals->menuitems[rcheevos_locals->menuitem_count++]; - menuitem->cheevo = cheevo; - menuitem->state_label_idx = MSG_UNKNOWN; - return menuitem; -} - -static void rcheevos_menu_append_header(rcheevos_locals_t* rcheevos_locals, - enum msg_hash_enums label) -{ - rcheevos_menuitem_t* menuitem = rcheevos_menu_allocate(rcheevos_locals, NULL); - if (menuitem) - menuitem->state_label_idx = label; -} - -static void rcheevos_menu_update_badge(rcheevos_racheevo_t* cheevo) -{ - bool badge_grayscale = false; - - switch (cheevo->menu_bucket) - { - case RCHEEVOS_MENUITEM_BUCKET_LOCKED: - case RCHEEVOS_MENUITEM_BUCKET_UNOFFICIAL: - case RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED: - case RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE: - case RCHEEVOS_MENUITEM_BUCKET_ACTIVE_CHALLENGE: - badge_grayscale = true; - break; - default: - break; - } - - if (!cheevo->menu_badge_texture || cheevo->menu_badge_grayscale != badge_grayscale) - { - uintptr_t new_badge_texture = - rcheevos_get_badge_texture(cheevo->badge, badge_grayscale, false); - - if (new_badge_texture) - { - if (cheevo->menu_badge_texture) - video_driver_texture_unload(&cheevo->menu_badge_texture); - - cheevo->menu_badge_texture = new_badge_texture; - cheevo->menu_badge_grayscale = badge_grayscale; - } - /* menu_badge_grayscale is overloaded such - * that any value greater than 1 indicates - * the server default image is being used */ - else if (cheevo->menu_badge_grayscale < 2) - { - if (cheevo->menu_badge_texture) - video_driver_texture_unload(&cheevo->menu_badge_texture); - - /* requested badge is not available, check for server default */ - cheevo->menu_badge_texture = - rcheevos_get_badge_texture("00000", false, false); - - if (cheevo->menu_badge_texture) - cheevo->menu_badge_grayscale = 2; - } - } -} - -static void rcheevos_menu_append_items(rcheevos_locals_t* rcheevos_locals, - enum rcheevos_menuitem_bucket bucket) -{ - rcheevos_racheevo_t* cheevo = rcheevos_locals->game.achievements; - rcheevos_racheevo_t* stop = cheevo + rcheevos_locals->game.achievement_count; - const unsigned first_index = rcheevos_locals->menuitem_count; - - while (cheevo < stop) - { - if (cheevo->menu_bucket == bucket) - { - rcheevos_menuitem_t* menuitem = rcheevos_menu_allocate(rcheevos_locals, cheevo); - if (!menuitem) - return; - - switch (cheevo->menu_bucket) - { - case RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED: - menuitem->state_label_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNSUPPORTED_ENTRY; - break; - case RCHEEVOS_MENUITEM_BUCKET_RECENTLY_UNLOCKED: - { - /* insert the item such that the unlock times are descending */ - unsigned entry_index = rcheevos_locals->menuitem_count - 1; - while (entry_index > first_index) - { - rcheevos_menuitem_t* prev_menuitem = menuitem - 1; - if (prev_menuitem->cheevo->unlock_time >= cheevo->unlock_time) - break; - - memcpy(menuitem, prev_menuitem, sizeof(rcheevos_menuitem_t)); - menuitem = prev_menuitem; - --entry_index; - } - - menuitem->cheevo = cheevo; - } - /* fallthrough to RCHEEVOS_MENUITEM_BUCKET_UNLOCKED */ - - case RCHEEVOS_MENUITEM_BUCKET_UNLOCKED: - if (!(cheevo->active & RCHEEVOS_ACTIVE_HARDCORE)) - menuitem->state_label_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY_HARDCORE; - else - menuitem->state_label_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY; - break; - case RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE: - { - /* insert the item such that the progresses are descending */ - unsigned entry_index = rcheevos_locals->menuitem_count - 1; - while (entry_index > first_index) - { - rcheevos_menuitem_t* prev_menuitem = menuitem - 1; - if (prev_menuitem->cheevo->menu_progress >= cheevo->menu_progress) - break; - - memcpy(menuitem, prev_menuitem, sizeof(rcheevos_menuitem_t)); - menuitem = prev_menuitem; - --entry_index; - } - - menuitem->cheevo = cheevo; - } - /* fallthrough to default */ - - default: - if (cheevo->active & RCHEEVOS_ACTIVE_UNOFFICIAL) - menuitem->state_label_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNOFFICIAL_ENTRY; - else if (!(cheevo->active & RCHEEVOS_ACTIVE_SOFTCORE)) - menuitem->state_label_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY; - else - menuitem->state_label_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_LOCKED_ENTRY; - break; - } - - if (cheevo->badge && cheevo->badge[0]) - { -#ifndef HAVE_GFX_WIDGETS - const settings_t* settings = config_get_ptr(); - if (settings && settings->bools.cheevos_badges_enable) -#endif - rcheevos_menu_update_badge(cheevo); - } - } - - ++cheevo; - } -} - -uintptr_t rcheevos_menu_get_badge_texture(unsigned menu_offset) -{ - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - if (menu_offset < rcheevos_locals->menuitem_count) - { - rcheevos_racheevo_t* cheevo = rcheevos_locals->menuitems[menu_offset].cheevo; - if (cheevo) - { - /* if we're using the placeholder badge, check to see if the real badge - * has become available (do this roughly once a second) */ - if (cheevo->menu_badge_grayscale >= 2) - { - if (++cheevo->menu_badge_grayscale >= MENU_BADGE_RETRY_RELOAD_FRAMES) - { - cheevo->menu_badge_grayscale = 2; - rcheevos_menu_update_badge(cheevo); - } - } - - return cheevo->menu_badge_texture; - } - } - - return 0; -} - -void rcheevos_menu_populate_hardcore_pause_submenu(void* data) -{ - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - menu_displaylist_info_t* info = (menu_displaylist_info_t*)data; - const settings_t* settings = config_get_ptr(); - const bool cheevos_hardcore_mode_enable = settings->bools.cheevos_hardcore_mode_enable; - - if (cheevos_hardcore_mode_enable && rcheevos_locals->loaded) - { - if (rcheevos_locals->hardcore_active) - { - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_CANCEL), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_CANCEL), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_CANCEL, - MENU_SETTING_ACTION_CLOSE, 0, 0, NULL); - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE, - MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0, NULL); - } - else - { - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME_CANCEL), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL), - MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL, - MENU_SETTING_ACTION_CLOSE, 0, 0, NULL); - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_RESUME), - MENU_ENUM_LABEL_ACHIEVEMENT_RESUME, - MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0, NULL); - } - } -} - -void rcheevos_menu_populate(void* data) -{ - menu_displaylist_info_t* info = (menu_displaylist_info_t*)data; - rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - const settings_t* settings = config_get_ptr(); - unsigned num_locked = 0; - unsigned num_unlocked = 0; - unsigned num_recently_unlocked = 0; - unsigned num_unsupported = 0; - unsigned num_active_challenges = 0; - unsigned num_almost_there = 0; - - if (rcheevos_locals->loaded) - { - const retro_time_t now = cpu_features_get_time_usec(); - const retro_time_t recent_unlock_time = now - (10 * 60 * 1000000); /* 10 minutes ago */ - rcheevos_racheevo_t* cheevo = NULL; - rcheevos_racheevo_t* stop = NULL; - - /* first menu item is the Pause/Resume Hardcore option (unless hardcore is disabled) */ - if (settings->bools.cheevos_enable && settings->bools.cheevos_hardcore_mode_enable) - { - if (rcheevos_locals->hardcore_active) - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, - MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0, NULL); - else - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, - MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0, NULL); - } - - /* update the bucket for each achievement */ - rcheevos_menu_update_buckets(); - - /* count items in each bucket */ - cheevo = rcheevos_locals->game.achievements; - stop = cheevo + rcheevos_locals->game.achievement_count; - - while (cheevo < stop) - { - switch (cheevo->menu_bucket) - { - case RCHEEVOS_MENUITEM_BUCKET_UNLOCKED: - if (cheevo->unlock_time && cheevo->unlock_time >= recent_unlock_time) - { - cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_RECENTLY_UNLOCKED; - ++num_recently_unlocked; - } - else - ++num_unlocked; - break; - - case RCHEEVOS_MENUITEM_BUCKET_LOCKED: - case RCHEEVOS_MENUITEM_BUCKET_UNOFFICIAL: - ++num_locked; - break; - - case RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED: - ++num_unsupported; - break; - - case RCHEEVOS_MENUITEM_BUCKET_ACTIVE_CHALLENGE: - ++num_active_challenges; - break; - - case RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE: - ++num_almost_there; - break; - } - - ++cheevo; - } - - if (!rcheevos_locals->menuitems) - { - /* reserve space for all achievements and up to 6 headers before we need to realloc */ - rcheevos_locals->menuitem_capacity = rcheevos_locals->game.achievement_count + 6; - rcheevos_locals->menuitems = (rcheevos_menuitem_t*) - malloc(rcheevos_locals->menuitem_capacity * sizeof(rcheevos_menuitem_t)); - if (!rcheevos_locals->menuitems) - rcheevos_locals->menuitem_capacity = 0; - } - } - - /* reset menu */ - rcheevos_locals->menuitem_count = 0; - - /* active challenges */ - if (num_active_challenges) - { - rcheevos_menu_append_header(rcheevos_locals, - MENU_ENUM_LABEL_VALUE_CHEEVOS_ACTIVE_CHALLENGES_ENTRY); - - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_ACTIVE_CHALLENGE); - } - - /* recently unlocked */ - if (num_recently_unlocked) - { - rcheevos_menu_append_header(rcheevos_locals, - MENU_ENUM_LABEL_VALUE_CHEEVOS_RECENTLY_UNLOCKED_ENTRY); - - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_RECENTLY_UNLOCKED); - } - - /* almost there */ - if (num_almost_there) - { - rcheevos_menu_append_header(rcheevos_locals, - MENU_ENUM_LABEL_VALUE_CHEEVOS_ALMOST_THERE_ENTRY); - - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE); - } - - /* locked */ - if (num_locked) - { - if (rcheevos_locals->menuitem_count > 0) - rcheevos_menu_append_header(rcheevos_locals, - MENU_ENUM_LABEL_VALUE_CHEEVOS_LOCKED_ENTRY); - - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_LOCKED); - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_UNOFFICIAL); - } - - /* unsupported */ - if (num_unsupported) - { - if (rcheevos_locals->menuitem_count > 0) - rcheevos_menu_append_header(rcheevos_locals, - MENU_ENUM_LABEL_VALUE_CHEEVOS_UNSUPPORTED_ENTRY); - - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED); - } - - /* unlocked */ - if (num_unlocked) - { - if (rcheevos_locals->menuitem_count > 0) - rcheevos_menu_append_header(rcheevos_locals, - MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY); - - rcheevos_menu_append_items(rcheevos_locals, - RCHEEVOS_MENUITEM_BUCKET_UNLOCKED); - } - - if (rcheevos_locals->menuitem_count > 0) - { - char buffer[128]; - unsigned idx = 0; - /* convert to menu entries */ - rcheevos_menuitem_t* menuitem = rcheevos_locals->menuitems; - rcheevos_menuitem_t* stop = menuitem + - rcheevos_locals->menuitem_count; - - do - { - if (menuitem->cheevo) - menu_entries_append(info->list, menuitem->cheevo->title, - menuitem->cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, - MENU_SETTINGS_CHEEVOS_START + idx, 0, 0, NULL); - else - { - snprintf(buffer, sizeof(buffer), "----- %s -----", - msg_hash_to_str(menuitem->state_label_idx)); - - menu_entries_append(info->list, buffer, "", - MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, - MENU_SETTINGS_CHEEVOS_START + idx, 0, 0, NULL); - } - - ++idx; - ++menuitem; - } while (menuitem != stop); - } - else - { - /* no achievements found */ - if (!rcheevos_locals->core_supports) - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), - msg_hash_to_str(MENU_ENUM_LABEL_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), - MENU_ENUM_LABEL_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE, - FILE_TYPE_NONE, 0, 0, NULL); - else if (rcheevos_locals->load_info.state == RCHEEVOS_LOAD_STATE_NETWORK_ERROR) - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETWORK_ERROR), - msg_hash_to_str(MENU_ENUM_LABEL_NETWORK_ERROR), - MENU_ENUM_LABEL_NETWORK_ERROR, - FILE_TYPE_NONE, 0, 0, NULL); - else if (!rcheevos_locals->game.id) - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME), - msg_hash_to_str(MENU_ENUM_LABEL_UNKNOWN_GAME), - MENU_ENUM_LABEL_UNKNOWN_GAME, - FILE_TYPE_NONE, 0, 0, NULL); - else if (!rcheevos_locals->token[0]) - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN), - msg_hash_to_str(MENU_ENUM_LABEL_NOT_LOGGED_IN), - MENU_ENUM_LABEL_NOT_LOGGED_IN, - FILE_TYPE_NONE, 0, 0, NULL); - else - menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ACHIEVEMENTS_TO_DISPLAY), - msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY), - MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY, - FILE_TYPE_NONE, 0, 0, NULL); - } -} - -#endif /* HAVE_MENU */ - -uintptr_t rcheevos_get_badge_texture(const char *badge, bool locked, bool download_if_missing) -{ - if (badge) - { - size_t _len; - char badge_file[24]; - char fullpath[PATH_MAX_LENGTH]; - uintptr_t tex = 0; - - /* OpenGL driver crashes if gfx_display_reset_textures_list is called on a background thread */ - retro_assert(task_is_on_main_thread()); - - _len = strlcpy(badge_file, badge, sizeof(badge_file)); - _len += strlcpy(badge_file + _len, locked ? "_lock" : "", - sizeof(badge_file) - _len); - strlcpy(badge_file + _len, ".png", - sizeof(badge_file) - _len); - - fill_pathname_application_special(fullpath, sizeof(fullpath), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); - - if (gfx_display_reset_textures_list(badge_file, fullpath, - &tex, TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL)) - return tex; - } - return 0; -} -#endif /* HAVE_RC_CLIENT */