Achievements: Use badge IDs from server

Fixes icons not invalidating if they're changed/reassigned in the RA
database.
This commit is contained in:
Stenzek 2024-08-04 17:00:06 +10:00
parent f9079b0151
commit ec5d8cb1d6
No known key found for this signature in database
2 changed files with 57 additions and 49 deletions

View file

@ -40,6 +40,7 @@
#include "fmt/format.h" #include "fmt/format.h"
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h" #include "imgui_internal.h"
#include "rc_api_runtime.h"
#include "rc_client.h" #include "rc_client.h"
#include <algorithm> #include <algorithm>
@ -66,6 +67,7 @@ static constexpr const char* INFO_SOUND_NAME = "sounds/achievements/message.wav"
static constexpr const char* UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav"; static constexpr const char* UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav";
static constexpr const char* LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav"; static constexpr const char* LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav";
static constexpr const char* ACHEIVEMENT_DETAILS_URL_TEMPLATE = "https://retroachievements.org/achievement/{}"; static constexpr const char* ACHEIVEMENT_DETAILS_URL_TEMPLATE = "https://retroachievements.org/achievement/{}";
static constexpr const char* CACHE_SUBDIRECTORY_NAME = "achievement_images";
static constexpr u32 LEADERBOARD_NEARBY_ENTRIES_TO_FETCH = 10; static constexpr u32 LEADERBOARD_NEARBY_ENTRIES_TO_FETCH = 10;
static constexpr u32 LEADERBOARD_ALL_FETCH_SIZE = 20; static constexpr u32 LEADERBOARD_ALL_FETCH_SIZE = 20;
@ -123,7 +125,7 @@ template<typename... T>
static void ReportFmtError(fmt::format_string<T...> fmt, T&&... args); static void ReportFmtError(fmt::format_string<T...> fmt, T&&... args);
template<typename... T> template<typename... T>
static void ReportRCError(int err, fmt::format_string<T...> fmt, T&&... args); static void ReportRCError(int err, fmt::format_string<T...> fmt, T&&... args);
static void EnsureCacheDirectoriesExist(); static void EnsureCacheDirectoryExists();
static void ClearGameInfo(); static void ClearGameInfo();
static void ClearGameHash(); static void ClearGameHash();
static std::string GetGameHash(CDImage* image); static std::string GetGameHash(CDImage* image);
@ -136,6 +138,7 @@ static void IdentifyGame(const std::string& path, CDImage* image);
static void BeginLoadGame(); static void BeginLoadGame();
static void BeginChangeDisc(); static void BeginChangeDisc();
static void UpdateGameSummary(); static void UpdateGameSummary();
static std::string GetLocalImagePath(const std::string_view image_name, int type);
static void DownloadImage(std::string url, std::string cache_filename); static void DownloadImage(std::string url, std::string cache_filename);
static bool CreateClient(rc_client_t** client, std::unique_ptr<HTTPDownloader>* http); static bool CreateClient(rc_client_t** client, std::unique_ptr<HTTPDownloader>* http);
@ -194,7 +197,6 @@ static bool s_using_raintegration = false;
static std::recursive_mutex s_achievements_mutex; static std::recursive_mutex s_achievements_mutex;
static rc_client_t* s_client; static rc_client_t* s_client;
static std::string s_image_directory;
static std::unique_ptr<HTTPDownloader> s_http_downloader; static std::unique_ptr<HTTPDownloader> s_http_downloader;
static std::string s_game_path; static std::string s_game_path;
@ -307,6 +309,36 @@ std::string Achievements::GetGameHash(CDImage* image)
return hash_str; return hash_str;
} }
std::string Achievements::GetLocalImagePath(const std::string_view image_name, int type)
{
std::string_view prefix;
switch (type)
{
case RC_IMAGE_TYPE_GAME:
prefix = "image"; // https://media.retroachievements.org/Images/{}.png
break;
case RC_IMAGE_TYPE_USER:
prefix = "user"; // https://media.retroachievements.org/UserPic/{}.png
break;
case RC_IMAGE_TYPE_ACHIEVEMENT: // https://media.retroachievements.org/Badge/{}.png
case RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED:
default:
prefix = "badge";
break;
}
std::string ret;
if (!image_name.empty())
{
ret = fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}" FS_OSPATH_SEPARATOR_STR "{}_{}.png", EmuFolders::Cache,
CACHE_SUBDIRECTORY_NAME, prefix, Path::SanitizeFileName(image_name));
}
return ret;
}
void Achievements::DownloadImage(std::string url, std::string cache_filename) void Achievements::DownloadImage(std::string url, std::string cache_filename)
{ {
auto callback = [cache_filename](s32 status_code, const std::string& content_type, auto callback = [cache_filename](s32 status_code, const std::string& content_type,
@ -395,7 +427,7 @@ bool Achievements::Initialize()
if (IsUsingRAIntegration()) if (IsUsingRAIntegration())
return true; return true;
EnsureCacheDirectoriesExist(); EnsureCacheDirectoryExists();
auto lock = GetLock(); auto lock = GetLock();
AssertMsg(g_settings.achievements_enabled, "Achievements are enabled"); AssertMsg(g_settings.achievements_enabled, "Achievements are enabled");
@ -532,7 +564,7 @@ void Achievements::UpdateSettings(const Settings& old_config)
} }
// in case cache directory changed // in case cache directory changed
EnsureCacheDirectoriesExist(); EnsureCacheDirectoryExists();
} }
bool Achievements::Shutdown(bool allow_cancel) bool Achievements::Shutdown(bool allow_cancel)
@ -577,14 +609,13 @@ bool Achievements::Shutdown(bool allow_cancel)
return true; return true;
} }
void Achievements::EnsureCacheDirectoriesExist() void Achievements::EnsureCacheDirectoryExists()
{ {
s_image_directory = Path::Combine(EmuFolders::Cache, "achievement_images"); Error error;
if (const std::string path = Path::Combine(EmuFolders::Cache, CACHE_SUBDIRECTORY_NAME);
if (!FileSystem::DirectoryExists(s_image_directory.c_str()) && !FileSystem::EnsureDirectoryExists(path.c_str(), false, &error))
!FileSystem::CreateDirectory(s_image_directory.c_str(), false))
{ {
ReportFmtError("Failed to create cache directory '{}'", s_image_directory); ReportFmtError("Failed to create cache directory '{}': {}", CACHE_SUBDIRECTORY_NAME, error.GetDescription());
} }
} }
@ -997,26 +1028,22 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
s_has_achievements = has_achievements; s_has_achievements = has_achievements;
s_has_leaderboards = has_leaderboards; s_has_leaderboards = has_leaderboards;
s_has_rich_presence = rc_client_has_rich_presence(client); s_has_rich_presence = rc_client_has_rich_presence(client);
s_game_icon = {};
// ensure fullscreen UI is ready for notifications // ensure fullscreen UI is ready for notifications
if (display_summary) if (display_summary)
FullscreenUI::Initialize(); FullscreenUI::Initialize();
if (const std::string_view badge_name = info->badge_name; !badge_name.empty()) s_game_icon = GetLocalImagePath(info->badge_name, RC_IMAGE_TYPE_GAME);
if (!s_game_icon.empty() && !FileSystem::FileExists(s_game_icon.c_str()))
{ {
s_game_icon = Path::Combine(s_image_directory, fmt::format("game_{}.png", info->id)); char buf[512];
if (!FileSystem::FileExists(s_game_icon.c_str())) if (int err = rc_client_game_get_image_url(info, buf, std::size(buf)); err == RC_OK)
{ {
char buf[512]; DownloadImage(buf, s_game_icon);
if (int err = rc_client_game_get_image_url(info, buf, std::size(buf)); err == RC_OK) }
{ else
DownloadImage(buf, s_game_icon); {
} ReportRCError(err, "rc_client_game_get_image_url() failed: ");
else
{
ReportRCError(err, "rc_client_game_get_image_url() failed: ");
}
} }
} }
@ -1618,18 +1645,10 @@ bool Achievements::DoState(StateWrapper& sw)
std::string Achievements::GetAchievementBadgePath(const rc_client_achievement_t* achievement, int state, std::string Achievements::GetAchievementBadgePath(const rc_client_achievement_t* achievement, int state,
bool download_if_missing) bool download_if_missing)
{ {
static constexpr std::array<const char*, NUM_RC_CLIENT_ACHIEVEMENT_STATES> s_achievement_state_strings = { const std::string path = GetLocalImagePath(achievement->badge_name, (state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) ?
{"inactive", "active", "unlocked", "disabled"}}; RC_IMAGE_TYPE_ACHIEVEMENT :
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED);
std::string path; if (download_if_missing && !path.empty() && !FileSystem::FileExists(path.c_str()))
if (achievement->badge_name[0] == 0)
return path;
path = Path::Combine(s_image_directory, TinyString::from_format("achievement_{}_{}_{}.png", s_game_id,
achievement->id, s_achievement_state_strings[state]));
if (download_if_missing && !FileSystem::FileExists(path.c_str()))
{ {
char buf[512]; char buf[512];
const int res = rc_client_achievement_get_image_url(achievement, state, buf, std::size(buf)); const int res = rc_client_achievement_get_image_url(achievement, state, buf, std::size(buf));
@ -1642,20 +1661,10 @@ std::string Achievements::GetAchievementBadgePath(const rc_client_achievement_t*
return path; return path;
} }
std::string Achievements::GetUserBadgePath(std::string_view username)
{
// definitely want to sanitize usernames... :)
std::string path;
const std::string clean_username = Path::SanitizeFileName(username);
if (!clean_username.empty())
path = Path::Combine(s_image_directory, TinyString::from_format("user_{}.png", clean_username));
return path;
}
std::string Achievements::GetLeaderboardUserBadgePath(const rc_client_leaderboard_entry_t* entry) std::string Achievements::GetLeaderboardUserBadgePath(const rc_client_leaderboard_entry_t* entry)
{ {
// TODO: maybe we should just cache these in memory... // TODO: maybe we should just cache these in memory...
std::string path = GetUserBadgePath(entry->user); const std::string path = GetLocalImagePath(entry->user, RC_IMAGE_TYPE_USER);
if (!FileSystem::FileExists(path.c_str())) if (!FileSystem::FileExists(path.c_str()))
{ {
@ -1837,8 +1846,8 @@ std::string Achievements::GetLoggedInUserBadgePath()
if (!user) [[unlikely]] if (!user) [[unlikely]]
return badge_path; return badge_path;
badge_path = GetUserBadgePath(user->username); badge_path = GetLocalImagePath(user->username, RC_IMAGE_TYPE_USER);
if (!FileSystem::FileExists(badge_path.c_str())) [[unlikely]] if (!badge_path.empty() && !FileSystem::FileExists(badge_path.c_str())) [[unlikely]]
{ {
char url[512]; char url[512];
const int res = rc_client_user_get_image_url(user, url, std::size(url)); const int res = rc_client_user_get_image_url(user, url, std::size(url));

View file

@ -17,7 +17,6 @@ const std::string& GetGameIconPath();
std::string GetAchievementBadgePath(const rc_client_achievement_t* achievement, int state, std::string GetAchievementBadgePath(const rc_client_achievement_t* achievement, int state,
bool download_if_missing = true); bool download_if_missing = true);
std::string GetUserBadgePath(std::string_view username);
std::string GetLeaderboardUserBadgePath(const rc_client_leaderboard_entry_t* entry); std::string GetLeaderboardUserBadgePath(const rc_client_leaderboard_entry_t* entry);
void OpenLeaderboard(const rc_client_leaderboard_t* lboard); void OpenLeaderboard(const rc_client_leaderboard_t* lboard);