diff --git a/Makefile.common b/Makefile.common index 0bcd0a2e89..a5b3cfc759 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2107,7 +2107,7 @@ ifeq ($(HAVE_NETWORKING), 1) INCLUDE_DIRS += -Ideps/rcheevos/include OBJ += cheevos/cheevos.o \ - cheevos/badges.o \ + cheevos/cheevos_menu.o \ cheevos/cheevos_memory.o \ cheevos/cheevos_parser.o \ $(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \ diff --git a/cheevos/badges.c b/cheevos/badges.c deleted file mode 100644 index 9aa951abc5..0000000000 --- a/cheevos/badges.c +++ /dev/null @@ -1,91 +0,0 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2015-2016 - Andre Leiradella - * - * RetroArch is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with RetroArch. - * If not, see . - */ - -#include - -#include "../file_path_special.h" -#include "../configuration.h" -#include "../gfx/gfx_display.h" - -#include "badges.h" - -#ifdef HAVE_MENU - -#define CHEEVOS_MENU_BADGE_LIMIT 256 -/* TODO/FIXME - public global variables */ -static uintptr_t cheevos_badge_menu_texture_list[CHEEVOS_MENU_BADGE_LIMIT] = { 0 }; - -void cheevos_reset_menu_badges(void) -{ - int index; - for (index = 0; index < CHEEVOS_MENU_BADGE_LIMIT; ++index) - { - if (cheevos_badge_menu_texture_list[index]) - video_driver_texture_unload(&cheevos_badge_menu_texture_list[index]); - } - - memset(&cheevos_badge_menu_texture_list, 0, - sizeof(cheevos_badge_menu_texture_list)); -} - -void cheevos_set_menu_badge(int index, const char *badge, bool locked) -{ - settings_t *settings = config_get_ptr(); - - if (index >= CHEEVOS_MENU_BADGE_LIMIT) - return; - - if (!settings || !settings->bools.cheevos_badges_enable) - cheevos_badge_menu_texture_list[index] = 0; - else - cheevos_badge_menu_texture_list[index] = - cheevos_get_badge_texture(badge, locked); -} - -uintptr_t cheevos_get_menu_badge_texture(int index) -{ - if (index < CHEEVOS_MENU_BADGE_LIMIT) - return cheevos_badge_menu_texture_list[index]; - - return 0; -} - -#endif - -uintptr_t cheevos_get_badge_texture(const char *badge, bool locked) -{ - char badge_file[24]; - char fullpath[PATH_MAX_LENGTH]; - uintptr_t tex = 0; - - if (!badge) - return 0; - - fullpath[0] = badge_file[0] = '\0'; - - strlcpy(badge_file, badge, sizeof(badge_file)); - if (locked) - strlcat(badge_file, "_lock", sizeof(badge_file)); - strlcat(badge_file, FILE_PATH_PNG_EXTENSION, sizeof(badge_file)); - - 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)) - tex = 0; - - return tex; -} diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index c4180fe0ba..ac9de0344e 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -33,11 +33,6 @@ #include "../config.h" #endif -#ifdef HAVE_MENU -#include "../menu/menu_driver.h" -#include "../menu/menu_entries.h" -#endif - #ifdef HAVE_GFX_WIDGETS #include "../gfx/gfx_widgets.h" #endif @@ -58,11 +53,10 @@ #include "streams/chd_stream.h" #endif -#include "badges.h" #include "cheevos.h" +#include "cheevos_locals.h" #include "cheevos_memory.h" #include "cheevos_parser.h" -#include "util.h" #include "../file_path_special.h" #include "../paths.h" @@ -80,7 +74,6 @@ #include "../network/net_http_special.h" #include "../tasks/tasks_internal.h" -#include "../deps/rcheevos/include/rc_runtime.h" #include "../deps/rcheevos/include/rc_runtime_types.h" #include "../deps/rcheevos/include/rc_url.h" #include "../deps/rcheevos/include/rc_hash.h" @@ -132,30 +125,6 @@ typedef struct rcheevos_async_io_request char hardcore; } rcheevos_async_io_request; -typedef struct -{ - rc_runtime_t runtime; - rcheevos_rapatchdata_t patchdata; /* ptr alignment */ - rcheevos_memory_regions_t memory; /* ptr alignment */ - - retro_task_t* task; -#ifdef HAVE_THREADS - slock_t* task_lock; - enum event_command queued_command; -#endif - - char token[32]; - char hash[33]; - char user_agent_prefix[128]; - - bool hardcore_active; - bool loaded; - bool core_supports; - bool leaderboards_enabled; - bool leaderboard_notifications; - bool leaderboard_trackers; -} rcheevos_locals_t; - static rcheevos_locals_t rcheevos_locals = { {0}, /* runtime */ @@ -169,6 +138,11 @@ static rcheevos_locals_t rcheevos_locals = {0}, /* token */ "N/A",/* hash */ "", /* user_agent_prefix */ +#ifdef HAVE_MENU + NULL, /* menuitems */ + 0, /* menuitem_capacity */ + 0, /* menuitem_count */ +#endif false,/* hardcore_active */ false,/* loaded */ true, /* core_supports */ @@ -177,6 +151,11 @@ static rcheevos_locals_t rcheevos_locals = false /* leaderboard_trackers */ }; +rcheevos_locals_t* get_rcheevos_locals() +{ + return &rcheevos_locals; +} + #ifdef HAVE_THREADS #define CHEEVOS_LOCK(l) do { slock_lock(l); } while (0) #define CHEEVOS_UNLOCK(l) do { slock_unlock(l); } while (0) @@ -986,6 +965,8 @@ static void rcheevos_award_achievement(rcheevos_locals_t *locals, if (locals->hardcore_active) cheevo->active &= ~RCHEEVOS_ACTIVE_HARDCORE; + cheevo->unlock_time = cpu_features_get_time_usec(); + /* Show the OSD message. */ { #if defined(HAVE_GFX_WIDGETS) @@ -1235,231 +1216,6 @@ void rcheevos_reset_game(bool widgets_ready) rcheevos_locals.patchdata.console_id); } -#ifdef HAVE_MENU -void rcheevos_get_achievement_state(unsigned index, - char *buffer, size_t len) -{ - enum msg_hash_enums enum_idx; - rcheevos_racheevo_t *cheevo = NULL; - bool check_measured = false; - - if (index < rcheevos_locals.patchdata.core_count) - { - enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_LOCKED_ENTRY; - if (rcheevos_locals.patchdata.core) - cheevo = &rcheevos_locals.patchdata.core[index]; - } - else - { - enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNOFFICIAL_ENTRY; - if (rcheevos_locals.patchdata.unofficial) - cheevo = &rcheevos_locals.patchdata.unofficial[index - - rcheevos_locals.patchdata.core_count]; - } - - if (!cheevo || !cheevo->memaddr) - enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNSUPPORTED_ENTRY; - else if (!(cheevo->active & RCHEEVOS_ACTIVE_HARDCORE)) - enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY_HARDCORE; - else if (!(cheevo->active & RCHEEVOS_ACTIVE_SOFTCORE)) - { - enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY; - /* if in hardcore mode, track progress towards hardcore unlock */ - check_measured = rcheevos_locals.hardcore_active; - } - /* Use either "Locked" for core or "Unofficial" - * for unofficial as set above and track progress */ - else - check_measured = true; - - strlcpy(buffer, msg_hash_to_str(enum_idx), len); - - if (check_measured) - { - const rc_trigger_t* trigger = rc_runtime_get_achievement( - &rcheevos_locals.runtime, cheevo->id); - const unsigned int target = trigger->measured_target; - - if (target > 0 && trigger->measured_value > 0) - { - char measured_buffer[12]; - const unsigned int value = MIN(trigger->measured_value, target); - const int percent = (int)(((unsigned long)value) * 100 / target); - - snprintf(measured_buffer, sizeof(measured_buffer), - " - %d%%", percent); - strlcat(buffer, measured_buffer, len); - } - } -} - -static void rcheevos_append_menu_achievement( - menu_displaylist_info_t* info, size_t idx, - rcheevos_racheevo_t* cheevo) -{ - bool badge_grayscale; - - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, - (unsigned)(MENU_SETTINGS_CHEEVOS_START + idx), 0, 0); - - /* TODO/FIXME - can we refactor this? - * Make badge_grayscale true by default, then - * have one conditional (second one here) that sets it - * to false */ - if (!cheevo->memaddr) - badge_grayscale = true; /* unsupported */ - else if (!(cheevo->active & RCHEEVOS_ACTIVE_HARDCORE) || - !(cheevo->active & RCHEEVOS_ACTIVE_SOFTCORE)) - badge_grayscale = false; /* unlocked */ - else - badge_grayscale = true; /* locked */ - - cheevos_set_menu_badge((int)idx, cheevo->badge, badge_grayscale); -} -#endif - -void rcheevos_populate_hardcore_pause_menu(void* data) -{ -#ifdef HAVE_MENU - menu_displaylist_info_t* info = (menu_displaylist_info_t*)data; - settings_t* settings = config_get_ptr(); - 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_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_CANCEL), - msg_hash_to_str(MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE_CANCEL), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_CANCEL, - MENU_SETTING_ACTION_CLOSE, 0, 0); - menu_entries_append_enum(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); - } - else - { - menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME_CANCEL), - msg_hash_to_str(MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL), - MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL, - MENU_SETTING_ACTION_CLOSE, 0, 0); - menu_entries_append_enum(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); - } - } -#endif -} - -void rcheevos_populate_menu(void* data) -{ -#ifdef HAVE_MENU - int i = 0; - int count = 0; - rcheevos_racheevo_t* cheevo = NULL; - menu_displaylist_info_t* info = (menu_displaylist_info_t*)data; - settings_t* settings = config_get_ptr(); - bool cheevos_enable = settings->bools.cheevos_enable; - bool cheevos_hardcore_mode_enable = settings->bools.cheevos_hardcore_mode_enable; - bool cheevos_test_unofficial = settings->bools.cheevos_test_unofficial; - - if ( cheevos_enable - && cheevos_hardcore_mode_enable - && rcheevos_locals.loaded) - { - if (rcheevos_locals.hardcore_active) - menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_MENU), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, - MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0); - else - menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_MENU), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, - MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0); - } - - cheevo = rcheevos_locals.patchdata.core; - for (count = rcheevos_locals.patchdata.core_count; count > 0; count--) - rcheevos_append_menu_achievement(info, i++, cheevo++); - - if (cheevos_test_unofficial) - { - cheevo = rcheevos_locals.patchdata.unofficial; - for (count = rcheevos_locals.patchdata.unofficial_count; count > 0; count--) - rcheevos_append_menu_achievement(info, i++, cheevo++); - } - - if (i == 0) - { - if (!rcheevos_locals.core_supports) - { - menu_entries_append_enum(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); - } - else if (!settings->arrays.cheevos_token[0]) - { - menu_entries_append_enum(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); - } - else - { - menu_entries_append_enum(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); - } - } -#endif -} - -bool rcheevos_get_description(rcheevos_ctx_desc_t* desc) -{ - unsigned idx; - const rcheevos_racheevo_t* cheevo; - - if (!desc) - return false; - - *desc->s = 0; - - if (rcheevos_locals.loaded) - { - idx = desc->idx; - - if (idx < rcheevos_locals.patchdata.core_count) - cheevo = rcheevos_locals.patchdata.core + idx; - else - { - idx -= rcheevos_locals.patchdata.core_count; - - if (idx >= rcheevos_locals.patchdata.unofficial_count) - return true; - cheevo = rcheevos_locals.patchdata.unofficial + idx; - } - - strlcpy(desc->s, cheevo->description, desc->len); - } - - return true; -} - bool rcheevos_hardcore_active(void) { return rcheevos_locals.hardcore_active; @@ -1500,10 +1256,17 @@ bool rcheevos_unload(void) if (rcheevos_locals.loaded) { - rcheevos_free_patchdata(&rcheevos_locals.patchdata); #ifdef HAVE_MENU - cheevos_reset_menu_badges(); + 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 + rcheevos_free_patchdata(&rcheevos_locals.patchdata); rcheevos_locals.loaded = false; rcheevos_locals.hardcore_active = false; @@ -2451,10 +2214,43 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) } #endif -#ifdef HAVE_MENU - cheevos_reset_menu_badges(); -#endif + /* make sure the directory exists */ + coro->badge_fullpath[0] = '\0'; + fill_pathname_application_special(coro->badge_fullpath, + sizeof(coro->badge_fullpath), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); + if (!path_is_directory(coro->badge_fullpath)) + path_mkdir(coro->badge_fullpath); + + /* fetch the placeholder image */ + strlcpy(coro->badge_name, "00000" FILE_PATH_PNG_EXTENSION, + sizeof(coro->badge_name)); + fill_pathname_join(coro->badge_fullpath, coro->badge_fullpath, + coro->badge_name, sizeof(coro->badge_fullpath)); + + if (!path_is_valid(coro->badge_fullpath)) + { +#ifdef CHEEVOS_LOG_BADGES + CHEEVOS_LOG(RCHEEVOS_TAG "downloading badge %s\n", + coro->badge_fullpath); +#endif + snprintf(coro->url, sizeof(coro->url), + FILE_PATH_RETROACHIEVEMENTS_URL "/Badge/%s", coro->badge_name); + + CORO_GOSUB(RCHEEVOS_HTTP_GET); + + if (coro->json) + { + if (!filestream_write_file(coro->badge_fullpath, coro->json, coro->k)) + CHEEVOS_ERR(RCHEEVOS_TAG "Error writing badge %s\n", coro->badge_fullpath); + + CHEEVOS_FREE(coro->json); + coro->json = NULL; + } + } + + /* fetch the game images */ for (coro->i = 0; coro->i < 2; coro->i++) { if (coro->i == 0) @@ -2470,24 +2266,13 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) for (; coro->cheevo < coro->cheevo_end; coro->cheevo++) { - if (!coro->cheevo->badge[0]) + if (!coro->cheevo->badge || !coro->cheevo->badge[0]) continue; for (coro->j = 0 ; coro->j < 2; coro->j++) { - coro->badge_fullpath[0] = '\0'; - fill_pathname_application_special( - coro->badge_fullpath, - sizeof(coro->badge_fullpath), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); - - if (!path_is_directory(coro->badge_fullpath)) - path_mkdir(coro->badge_fullpath); CORO_YIELD(); - if (!coro->cheevo->badge || !coro->cheevo->badge[0]) - continue; - if (coro->j == 0) snprintf(coro->badge_name, sizeof(coro->badge_name), @@ -2499,6 +2284,11 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) "%s_lock" FILE_PATH_PNG_EXTENSION, coro->cheevo->badge); + coro->badge_fullpath[0] = '\0'; + fill_pathname_application_special(coro->badge_fullpath, + sizeof(coro->badge_fullpath), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); + fill_pathname_join( coro->badge_fullpath, coro->badge_fullpath, @@ -2524,11 +2314,9 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) if (!filestream_write_file(coro->badge_fullpath, coro->json, coro->k)) CHEEVOS_ERR(RCHEEVOS_TAG "Error writing badge %s\n", coro->badge_fullpath); - else - { - CHEEVOS_FREE(coro->json); - coro->json = NULL; - } + + CHEEVOS_FREE(coro->json); + coro->json = NULL; } } } diff --git a/cheevos/cheevos.h b/cheevos/cheevos.h index e168faea63..cfeb29ad35 100644 --- a/cheevos/cheevos.h +++ b/cheevos/cheevos.h @@ -21,64 +21,39 @@ #include -#include "../verbosity.h" - #include RETRO_BEGIN_DECLS -typedef struct rcheevos_ctx_desc -{ - unsigned idx; - char *s; - size_t len; -} rcheevos_ctx_desc_t; - -enum -{ - RCHEEVOS_ACTIVE_SOFTCORE = 1 << 0, - RCHEEVOS_ACTIVE_HARDCORE = 1 << 1, - RCHEEVOS_ACTIVE_UNOFFICIAL = 1 << 2 -}; - bool rcheevos_load(const void *data); size_t rcheevos_get_serialize_size(void); bool rcheevos_get_serialized_data(void* buffer); bool rcheevos_set_serialized_data(void* buffer); -void rcheevos_reset_game(bool widgets_ready); - -void rcheevos_populate_menu(void* data); -void rcheevos_populate_hardcore_pause_menu(void* data); -void rcheevos_get_achievement_state(unsigned index, char* buffer, size_t buffer_size); - -bool rcheevos_get_description(rcheevos_ctx_desc_t *desc); - -void rcheevos_pause_hardcore(void); - bool rcheevos_unload(void); +void rcheevos_test(void); + +void rcheevos_reset_game(bool widgets_ready); + +void rcheevos_pause_hardcore(void); void rcheevos_hardcore_enabled_changed(void); void rcheevos_toggle_hardcore_paused(void); +bool rcheevos_hardcore_active(void); void rcheevos_validate_config_settings(void); void rcheevos_leaderboards_enabled_changed(void); -void rcheevos_test(void); - void rcheevos_set_support_cheevos(bool state); - bool rcheevos_get_support_cheevos(void); const char* rcheevos_get_hash(void); - int rcheevos_get_richpresence(char buffer[], int buffer_size); +uintptr_t rcheevos_get_badge_texture(const char *badge, bool locked); uint8_t* rcheevos_patch_address(unsigned address); -bool rcheevos_hardcore_active(void); - RETRO_END_DECLS #endif /* __RARCH_CHEEVOS_CHEEVOS_H */ diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h new file mode 100644 index 0000000000..9431d73913 --- /dev/null +++ b/cheevos/cheevos_locals.h @@ -0,0 +1,158 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2015-2018 - Andre Leiradella + * Copyright (C) 2019-2021 - Brian Weiss + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef __RARCH_CHEEVOS_LOCALS_H +#define __RARCH_CHEEVOS_LOCALS_H + +#include "../deps/rcheevos/include/rc_runtime.h" + +#include "cheevos_memory.h" + +#include <../command.h> +#include <../verbosity.h> +#include +#include + +#ifdef HAVE_THREADS +#include +#endif + +#include + +RETRO_BEGIN_DECLS + +/************************************************************************ + * Logging * + ************************************************************************/ + +/* Define this macro to get extra-verbose log for cheevos. */ +#define CHEEVOS_VERBOSE + +#define RCHEEVOS_TAG "[RCHEEVOS]: " +#define CHEEVOS_FREE(p) do { void* q = (void*)p; if (q) free(q); } while (0) + +#ifdef CHEEVOS_VERBOSE + #define CHEEVOS_LOG RARCH_LOG + #define CHEEVOS_ERR RARCH_ERR +#else + void rcheevos_log(const char *fmt, ...); + #define CHEEVOS_LOG rcheevos_log + #define CHEEVOS_ERR RARCH_ERR +#endif + +/************************************************************************ + * State * + ************************************************************************/ + +enum +{ + RCHEEVOS_ACTIVE_SOFTCORE = 1 << 0, + RCHEEVOS_ACTIVE_HARDCORE = 1 << 1, + RCHEEVOS_ACTIVE_UNOFFICIAL = 1 << 2 +}; + +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; +} rcheevos_ralboard_t; + +typedef struct rcheevos_rapatchdata_t +{ + char* title; + rcheevos_racheevo_t* core; + rcheevos_racheevo_t* unofficial; + rcheevos_ralboard_t* lboards; + char* richpresence_script; + + unsigned game_id; + unsigned console_id; + unsigned core_count; + unsigned unofficial_count; + unsigned lboard_count; +} rcheevos_rapatchdata_t; + +#ifdef HAVE_MENU + +typedef struct rcheevos_menuitem_t +{ + rcheevos_racheevo_t* cheevo; + enum msg_hash_enums state_label_idx; +} rcheevos_menuitem_t; + +void rcheevos_menu_reset_badges(void); + +#endif + +typedef struct rcheevos_locals_t +{ + rc_runtime_t runtime; /* rcheevos runtime state */ + rcheevos_rapatchdata_t patchdata; /* achievement/leaderboard data from the server */ + rcheevos_memory_regions_t memory; /* achievement addresses to core memory mappings */ + + retro_task_t* task; /* load task */ +#ifdef HAVE_THREADS + slock_t* task_lock; /* mutex for starting/stopping load task */ + enum event_command queued_command; /* action queued by background thread to be run on main thread */ +#endif + + char token[32]; /* user's session token */ + char hash[33]; /* retroachievements hash for current content */ + char user_agent_prefix[128]; /* RetroArch/OS version information */ + +#ifdef HAVE_MENU + rcheevos_menuitem_t* menuitems; /* array of items for the achievements quick menu */ + unsigned menuitem_capacity; /* maximum number of items in the menuitems array */ + unsigned menuitem_count; /* current number of items in the menuitems array */ +#endif + + bool hardcore_active; /* hardcore functionality is active */ + bool loaded; /* load task has completed */ + bool core_supports; /* false if core explicitly disables achievements */ + bool leaderboards_enabled; /* leaderboards are enabled */ + bool leaderboard_notifications; /* leaderboard notifications are enabled */ + bool leaderboard_trackers; /* leaderboard trackers are enabled */ +} rcheevos_locals_t; + +rcheevos_locals_t* get_rcheevos_locals(); + +RETRO_END_DECLS + +#endif /* __RARCH_CHEEVOS_LOCALS_H */ diff --git a/cheevos/cheevos_memory.c b/cheevos/cheevos_memory.c index 5f075f2e5e..2fcb4ce9cd 100644 --- a/cheevos/cheevos_memory.c +++ b/cheevos/cheevos_memory.c @@ -15,7 +15,7 @@ #include "cheevos_memory.h" -#include "util.h" +#include "cheevos_locals.h" #include "../retroarch.h" #include "../verbosity.h" diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c new file mode 100644 index 0000000000..afac7afdd1 --- /dev/null +++ b/cheevos/cheevos_menu.c @@ -0,0 +1,708 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2019-2021 - Brian Weiss + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "cheevos_locals.h" + +#include "../gfx/gfx_display.h" + +#ifdef HAVE_MENU + +#include "cheevos.h" + +#include "../deps/rcheevos/include/rc_runtime_types.h" + +#include "../file_path_special.h" +#include "../menu/menu_driver.h" +#include "../menu/menu_entries.h" + +#include + +enum rcheevos_menuitem_bucket +{ + RCHEEVOS_MENUITEM_BUCKET_UNKNOWN = 0, + RCHEEVOS_MENUITEM_BUCKET_LOCKED, + RCHEEVOS_MENUITEM_BUCKET_UNLOCKED, + RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED, + 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) +{ + if (!cheevo->memaddr) + { + /* non-active unsupported achievement */ + cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_UNSUPPORTED; + } + else if (!(cheevo->active & RCHEEVOS_ACTIVE_HARDCORE)) + { + /* non-active unlocked in hardcore achievement */ + cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_UNLOCKED; + } + else + { + const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); + rc_trigger_t* trigger; + + 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 */ + cheevo->menu_bucket = RCHEEVOS_MENUITEM_BUCKET_LOCKED; + cheevo->menu_progress = 0; + + trigger = rc_runtime_get_achievement(&rcheevos_locals->runtime, cheevo->id); + if (trigger) + { + if (trigger->measured_value && 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(bool cheevos_test_unofficial) +{ + const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); + rcheevos_racheevo_t* cheevo = rcheevos_locals->patchdata.core; + rcheevos_racheevo_t* stop = cheevo + rcheevos_locals->patchdata.core_count; + + while (cheevo < stop) + { + rcheevos_menu_update_bucket(cheevo); + ++cheevo; + } + + if (cheevos_test_unofficial) + { + cheevo = rcheevos_locals->patchdata.unofficial; + stop = cheevo + rcheevos_locals->patchdata.unofficial_count; + + while (cheevo < stop) + { + rcheevos_menu_update_bucket(cheevo); + ++cheevo; + } + } +} + +bool rcheevos_menu_get_state(unsigned menu_offset, char *buffer, size_t len) +{ + 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) + { + snprintf(buffer, len, "%s - %d%%", + msg_hash_to_str(menuitem->state_label_idx), + cheevo->menu_progress); + } + else + { + strlcpy(buffer, msg_hash_to_str(menuitem->state_label_idx), len); + } + + return true; + } + } + + if (buffer) + buffer[0] = '\0'; + + return false; +} + +bool rcheevos_menu_get_sublabel(unsigned menu_offset, char *buffer, size_t len) +{ + 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, len); + 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->patchdata.core; + rcheevos_racheevo_t* stop = cheevo + rcheevos_locals->patchdata.core_count; + + while (cheevo < stop) + { + if (cheevo->menu_badge_texture) + video_driver_texture_unload(&cheevo->menu_badge_texture); + ++cheevo; + } + + cheevo = rcheevos_locals->patchdata.unofficial; + stop = cheevo + rcheevos_locals->patchdata.unofficial_count; + + while (cheevo < stop) + { + if (cheevo->menu_badge_texture) + video_driver_texture_unload(&cheevo->menu_badge_texture); + ++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_UNSUPPORTED: + case RCHEEVOS_MENUITEM_BUCKET_ALMOST_THERE: + case RCHEEVOS_MENUITEM_BUCKET_ACTIVE_CHALLENGE: + badge_grayscale = true; + break; + + default: + badge_grayscale = false; + 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); + + 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); + + if (cheevo->menu_badge_texture) + cheevo->menu_badge_grayscale = 2; + } + } +} + +static void rcheevos_menu_append_items(rcheevos_locals_t* rcheevos_locals, + bool cheevos_test_unofficial, enum rcheevos_menuitem_bucket bucket) +{ + const settings_t *settings = config_get_ptr(); + rcheevos_racheevo_t* cheevo = rcheevos_locals->patchdata.core; + rcheevos_racheevo_t* stop = cheevo + rcheevos_locals->patchdata.core_count; + bool processing_unofficial = false; + const unsigned first_index = rcheevos_locals->menuitem_count; + + do + { + if (cheevo == stop) + { + if (!cheevos_test_unofficial || processing_unofficial) + break; + + processing_unofficial = true; + cheevo = rcheevos_locals->patchdata.unofficial; + stop = cheevo + rcheevos_locals->patchdata.unofficial_count; + continue; + } + + 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 (processing_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] && settings && + settings->bools.cheevos_badges_enable) + { + rcheevos_menu_update_badge(cheevo); + } + } + + ++cheevo; + } while (true); +} + +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 == 64) + { + 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_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_CANCEL), + msg_hash_to_str(MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE_CANCEL), + MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_CANCEL, + MENU_SETTING_ACTION_CLOSE, 0, 0); + menu_entries_append_enum(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); + } + else + { + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME_CANCEL), + msg_hash_to_str(MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL), + MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL, + MENU_SETTING_ACTION_CLOSE, 0, 0); + menu_entries_append_enum(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); + } + } +} + +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(); + const bool cheevos_test_unofficial = settings->bools.cheevos_test_unofficial; + 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 */ + bool processing_unofficial = false; + 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_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_MENU), + MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, + MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0); + else + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_MENU), + MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, + MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0); + } + + /* update the bucket for each achievement */ + rcheevos_menu_update_buckets(cheevos_test_unofficial); + + /* count items in each bucket */ + cheevo = rcheevos_locals->patchdata.core; + stop = cheevo + rcheevos_locals->patchdata.core_count; + + do + { + if (cheevo == stop) + { + if (!cheevos_test_unofficial || processing_unofficial) + break; + + processing_unofficial = true; + cheevo = rcheevos_locals->patchdata.unofficial; + stop = cheevo + rcheevos_locals->patchdata.unofficial_count; + continue; + } + + 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: + ++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; + } while(true); + + 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->patchdata.core_count + 6; + if (cheevos_test_unofficial) + rcheevos_locals->menuitem_capacity += rcheevos_locals->patchdata.unofficial_count; + + 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, cheevos_test_unofficial, + 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, cheevos_test_unofficial, + 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, cheevos_test_unofficial, + 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, cheevos_test_unofficial, + RCHEEVOS_MENUITEM_BUCKET_LOCKED); + } + + /* 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, cheevos_test_unofficial, + RCHEEVOS_MENUITEM_BUCKET_UNLOCKED); + } + + if (rcheevos_locals->menuitem_count > 0) + { + /* convert to menu entries */ + rcheevos_menuitem_t* menuitem = rcheevos_locals->menuitems; + rcheevos_menuitem_t* stop = menuitem + rcheevos_locals->menuitem_count; + char buffer[128]; + unsigned idx = 0; + + do + { + if (menuitem->cheevo) + { + menu_entries_append_enum(info->list, menuitem->cheevo->title, + menuitem->cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, + MENU_SETTINGS_CHEEVOS_START + idx, 0, 0); + } + else + { + snprintf(buffer, sizeof(buffer), "----- %s -----", + msg_hash_to_str(menuitem->state_label_idx)); + + menu_entries_append_enum(info->list, buffer, "", + MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, + MENU_SETTINGS_CHEEVOS_START + idx, 0, 0); + } + + ++idx; + ++menuitem; + } while (menuitem != stop); + } + else + { + /* no achievements found */ + if (!rcheevos_locals->core_supports) + { + menu_entries_append_enum(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); + } + else if (!rcheevos_locals->token[0]) + { + menu_entries_append_enum(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); + } + else + { + menu_entries_append_enum(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); + } + } +} + +#endif /* HAVE_MENU */ + +uintptr_t rcheevos_get_badge_texture(const char *badge, bool locked) +{ + char badge_file[24]; + char fullpath[PATH_MAX_LENGTH]; + uintptr_t tex = 0; + + if (!badge) + return 0; + + snprintf(badge_file, sizeof(badge_file), "%s%s%s", badge, + locked ? "_lock" : "", FILE_PATH_PNG_EXTENSION); + + 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)) + { + tex = 0; + } + + return tex; +} + diff --git a/cheevos/badges.h b/cheevos/cheevos_menu.h similarity index 61% rename from cheevos/badges.h rename to cheevos/cheevos_menu.h index cdbb4375a4..eea43fb2d5 100644 --- a/cheevos/badges.h +++ b/cheevos/cheevos_menu.h @@ -1,5 +1,6 @@ /* RetroArch - A frontend for libretro. * Copyright (C) 2015-2018 - Andre Leiradella + * Copyright (C) 2019-2021 - Brian Weiss * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- @@ -13,23 +14,28 @@ * If not, see . */ -#ifndef __RARCH_CHEEVOS_BADGE_H -#define __RARCH_CHEEVOS_BADGE_H +#ifndef __RARCH_CHEEVOS_MENU_H +#define __RARCH_CHEEVOS_MENU_H + +#ifdef HAVE_MENU #include -#include +#include + #include +#include + RETRO_BEGIN_DECLS -#ifdef HAVE_MENU -void cheevos_reset_menu_badges(void); -void cheevos_set_menu_badge(int index, const char *badge, bool locked); -uintptr_t cheevos_get_menu_badge_texture(int index); -#endif - -uintptr_t cheevos_get_badge_texture(const char* badge, bool locked); +void rcheevos_menu_populate(void* data); +void rcheevos_menu_populate_hardcore_pause_submenu(void* data); +bool rcheevos_menu_get_state(unsigned menu_offset, char* buffer, size_t buffer_size); +bool rcheevos_menu_get_sublabel(unsigned menu_offset, char* buffer, size_t buffer_size); +uintptr_t rcheevos_menu_get_badge_texture(unsigned menu_offset); RETRO_END_DECLS -#endif +#endif /* HAVE_MENU */ + +#endif /* __RARCH_CHEEVOS_MENU_H */ diff --git a/cheevos/cheevos_parser.c b/cheevos/cheevos_parser.c index 163a4634c1..cc8374485c 100644 --- a/cheevos/cheevos_parser.c +++ b/cheevos/cheevos_parser.c @@ -1,6 +1,6 @@ #include "cheevos_parser.h" -#include "util.h" +#include "cheevos_locals.h" #include #include diff --git a/cheevos/cheevos_parser.h b/cheevos/cheevos_parser.h index 8f27aeb811..6bec0063f2 100644 --- a/cheevos/cheevos_parser.h +++ b/cheevos/cheevos_parser.h @@ -16,49 +16,10 @@ #ifndef __RARCH_CHEEVOS_PARSER_H #define __RARCH_CHEEVOS_PARSER_H -#include -#include -#include - -#include +#include "cheevos_locals.h" RETRO_BEGIN_DECLS -typedef struct -{ - const char* title; - const char* description; - const char* badge; - const char* memaddr; - unsigned points; - unsigned id; - unsigned active; -} rcheevos_racheevo_t; - -typedef struct -{ - const char* title; - const char* description; - const char* mem; - unsigned id; - unsigned format; -} rcheevos_ralboard_t; - -typedef struct -{ - char* title; - rcheevos_racheevo_t* core; - rcheevos_racheevo_t* unofficial; - rcheevos_ralboard_t* lboards; - char* richpresence_script; - - unsigned game_id; - unsigned console_id; - unsigned core_count; - unsigned unofficial_count; - unsigned lboard_count; -} rcheevos_rapatchdata_t; - typedef void (*rcheevos_unlock_cb_t)(unsigned id, void* userdata); int rcheevos_get_json_error(const char* json, char* token, size_t length); diff --git a/cheevos/util.h b/cheevos/util.h deleted file mode 100644 index f94be8d506..0000000000 --- a/cheevos/util.h +++ /dev/null @@ -1,53 +0,0 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2015-2016 - Andre Leiradella - * - * RetroArch is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with RetroArch. - * If not, see . - */ - -#ifndef __RARCH_CHEEVOS_UTIL_H -#define __RARCH_CHEEVOS_UTIL_H - -#include - -RETRO_BEGIN_DECLS - -/***************************************************************************** -Setup - mainly for debugging -*****************************************************************************/ - -/* Define this macro to get extra-verbose log for cheevos. */ -#define CHEEVOS_VERBOSE - -/***************************************************************************** -End of setup -*****************************************************************************/ - -#define RCHEEVOS_TAG "[RCHEEVOS]: " -#define CHEEVOS_FREE(p) do { void* q = (void*)p; if (q) free(q); } while (0) - -#ifdef CHEEVOS_VERBOSE - -#define CHEEVOS_LOG RARCH_LOG -#define CHEEVOS_ERR RARCH_ERR - -#else - -#define CHEEVOS_LOG rcheevos_log -#define CHEEVOS_ERR RARCH_ERR - -void rcheevos_log(const char *fmt, ...); - -#endif - -RETRO_END_DECLS - -#endif /* __RARCH_CHEEVOS_UTIL_H */ diff --git a/gfx/widgets/gfx_widget_achievement_popup.c b/gfx/widgets/gfx_widget_achievement_popup.c index decf158216..5553e24091 100644 --- a/gfx/widgets/gfx_widget_achievement_popup.c +++ b/gfx/widgets/gfx_widget_achievement_popup.c @@ -18,7 +18,7 @@ #include "../gfx_display.h" #include "../gfx_widgets.h" -#include "../cheevos/badges.h" +#include "../cheevos/cheevos.h" #ifdef HAVE_THREADS #define SLOCK_LOCK(x) slock_lock(x) @@ -391,7 +391,7 @@ void gfx_widgets_push_achievement(const char *title, const char *badge) /* important - this must be done outside the lock because it has the potential to need to * lock the video thread, which may be waiting for the popup queue lock to render popups */ - uintptr_t badge_id = cheevos_get_badge_texture(badge, 0); + uintptr_t badge_id = rcheevos_get_badge_texture(badge, 0); if (state->queue_read_index < 0) { diff --git a/griffin/griffin.c b/griffin/griffin.c index ec0204448f..d9bc61da3a 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -191,7 +191,7 @@ ACHIEVEMENTS #include "../network/net_http_special.c" #include "../cheevos/cheevos.c" -#include "../cheevos/badges.c" +#include "../cheevos/cheevos_menu.c" #include "../cheevos/cheevos_memory.c" #include "../cheevos/cheevos_parser.c" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index d7d5deec7f..6fa127a0ac 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -7979,6 +7979,18 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CHEEVOS_UNSUPPORTED_ENTRY, "Unsupported" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_RECENTLY_UNLOCKED_ENTRY, + "Recently Unlocked" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_ALMOST_THERE_ENTRY, + "Almost There" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_ACTIVE_CHALLENGES_ENTRY, + "Active Challenges" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CHEEVOS_TRACKERS_ONLY, "Trackers Only" diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index f5befe000c..8952e1d830 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -53,7 +53,7 @@ #endif #ifdef HAVE_CHEEVOS -#include "../../cheevos/cheevos.h" +#include "../../cheevos/cheevos_menu.h" #endif #ifndef BIND_ACTION_GET_VALUE @@ -129,7 +129,7 @@ static void menu_action_setting_disp_set_label_cheevos_entry( *w = 19; strlcpy(s2, path, len2); - rcheevos_get_achievement_state(type - MENU_SETTINGS_CHEEVOS_START, s, len); + rcheevos_menu_get_state(type - MENU_SETTINGS_CHEEVOS_START, s, len); } #endif diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index beaf60aef9..f0103d59fb 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -26,7 +26,7 @@ #include "../../core_option_manager.h" #ifdef HAVE_CHEEVOS -#include "../../cheevos/cheevos.h" +#include "../../cheevos/cheevos_menu.h" #endif #include "../../core_info.h" #include "../../verbosity.h" @@ -1052,20 +1052,8 @@ static int action_bind_sublabel_cheevos_entry( const char *label, const char *path, char *s, size_t len) { - rcheevos_ctx_desc_t desc_info; - unsigned new_id; - char fetched_sublabel[MENU_SUBLABEL_MAX_LENGTH]; - - fetched_sublabel[0] = '\0'; - - new_id = type - MENU_SETTINGS_CHEEVOS_START; - desc_info.idx = new_id; - desc_info.s = fetched_sublabel; - desc_info.len = len; - - rcheevos_get_description((rcheevos_ctx_desc_t*) &desc_info); - - strlcpy(s, desc_info.s, len); + unsigned offset = type - MENU_SETTINGS_CHEEVOS_START; + rcheevos_menu_get_sublabel(offset, s, len); return 0; } #endif diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c index 19db500552..425d04351d 100644 --- a/menu/drivers/ozone/ozone_texture.c +++ b/menu/drivers/ozone/ozone_texture.c @@ -24,7 +24,7 @@ #include #ifdef HAVE_CHEEVOS -#include "../../../cheevos/badges.h" +#include "../../../cheevos/cheevos_menu.h" #endif #include "../../../file_path_special.h" @@ -464,11 +464,17 @@ uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, (type < MENU_SETTINGS_NETPLAY_ROOMS_START) ) { + char buffer[64]; int index = type - MENU_SETTINGS_CHEEVOS_START; - uintptr_t badge_texture = cheevos_get_menu_badge_texture(index); + uintptr_t badge_texture = rcheevos_menu_get_badge_texture(index); if (badge_texture) return badge_texture; - /* Should be replaced with placeholder badge icon. */ + + /* no state means its a header - show the info icon */ + if (!rcheevos_menu_get_state(index, buffer, sizeof(buffer))) + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INFO]; + + /* placeholder badge image was not found, show generic menu icon */ return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENTS]; } #endif diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index c8725bbc39..94d14143af 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -59,7 +59,7 @@ #include "../../tasks/tasks_internal.h" #ifdef HAVE_CHEEVOS -#include "../../cheevos/badges.h" +#include "../../cheevos/cheevos_menu.h" #endif #include "../../content.h" @@ -2905,11 +2905,17 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, (type < MENU_SETTINGS_NETPLAY_ROOMS_START) ) { + char buffer[64]; int index = type - MENU_SETTINGS_CHEEVOS_START; - uintptr_t badge_texture = cheevos_get_menu_badge_texture(index); + uintptr_t badge_texture = rcheevos_menu_get_badge_texture(index); if (badge_texture) return badge_texture; - /* Should be replaced with placeholder badge icon. */ + + /* no state means its a header - show the info icon */ + if (!rcheevos_menu_get_state(index, buffer, sizeof(buffer))) + return xmb->textures.list[XMB_TEXTURE_INFO]; + + /* placeholder badge image was not found, show generic menu icon */ return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENTS]; } #endif diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 57e022f335..0c5a62ce8c 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -41,6 +41,7 @@ #ifdef HAVE_CHEEVOS #include "../cheevos/cheevos.h" +#include "../cheevos/cheevos_menu.h" #endif #ifdef HAVE_NETWORKING @@ -10982,7 +10983,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_ACHIEVEMENT_PAUSE_MENU: #ifdef HAVE_CHEEVOS menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); - rcheevos_populate_hardcore_pause_menu(info); + rcheevos_menu_populate_hardcore_pause_submenu(info); #endif info->need_push = true; info->need_refresh = true; @@ -10990,7 +10991,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_ACHIEVEMENT_LIST: #ifdef HAVE_CHEEVOS menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); - rcheevos_populate_menu(info); + rcheevos_menu_populate(info); #endif info->need_push = true; info->need_refresh = true; diff --git a/msg_hash.h b/msg_hash.h index e57bfcf90e..8a9ec5d700 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -704,6 +704,9 @@ enum msg_hash_enums MENU_LABEL(CHEEVOS_LOCKED_ENTRY), MENU_LABEL(CHEEVOS_UNSUPPORTED_ENTRY), MENU_LABEL(CHEEVOS_UNOFFICIAL_ENTRY), + MENU_ENUM_LABEL_VALUE_CHEEVOS_RECENTLY_UNLOCKED_ENTRY, + MENU_ENUM_LABEL_VALUE_CHEEVOS_ALMOST_THERE_ENTRY, + MENU_ENUM_LABEL_VALUE_CHEEVOS_ACTIVE_CHALLENGES_ENTRY, MENU_ENUM_LABEL_VALUE_CHEEVOS_TRACKERS_ONLY, MENU_ENUM_LABEL_VALUE_CHEEVOS_NOTIFICATIONS_ONLY, diff --git a/retroarch.c b/retroarch.c index 354ebc1a3e..5ab1ba30a6 100644 --- a/retroarch.c +++ b/retroarch.c @@ -174,6 +174,7 @@ #ifdef HAVE_CHEEVOS #include "cheevos/cheevos.h" +#include "cheevos/cheevos_menu.h" #endif #ifdef HAVE_TRANSLATE @@ -789,13 +790,8 @@ static int menu_dialog_iterate( #ifdef HAVE_CHEEVOS case MENU_DIALOG_HELP_CHEEVOS_DESCRIPTION: - { - rcheevos_ctx_desc_t desc_info; - desc_info.idx = p_dialog->current_id; - desc_info.s = s; - desc_info.len = len; - rcheevos_get_description((rcheevos_ctx_desc_t*) &desc_info); - } + if (!rcheevos_menu_get_sublabel(p_dialog->current_id, s, len)) + return 1; break; #endif