diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index bd0f946991..5561b0e187 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -63,6 +63,7 @@ #define COVERCACHE_DIR "GameCovers" #define REDUMPCACHE_DIR "Redump" #define SHADERCACHE_DIR "Shaders" +#define RETROACHIEVEMENTSCACHE_DIR "RetroAchievements" #define STATESAVES_DIR "StateSaves" #define SCREENSHOTS_DIR "ScreenShots" #define LOAD_DIR "Load" diff --git a/Source/Core/Common/Crypto/SHA1.cpp b/Source/Core/Common/Crypto/SHA1.cpp index f87bbd2c6d..8c4aa646bd 100644 --- a/Source/Core/Common/Crypto/SHA1.cpp +++ b/Source/Core/Common/Crypto/SHA1.cpp @@ -385,4 +385,20 @@ Digest CalculateDigest(const u8* msg, size_t len) ctx->Update(msg, len); return ctx->Finish(); } + +std::string DigestToString(const Digest& digest) +{ + static constexpr std::array lookup = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + std::string hash; + hash.reserve(digest.size() * 2); + for (size_t i = 0; i < digest.size(); ++i) + { + const u8 upper = static_cast((digest[i] >> 4) & 0xf); + const u8 lower = static_cast(digest[i] & 0xf); + hash.push_back(lookup[upper]); + hash.push_back(lookup[lower]); + } + return hash; +} } // namespace Common::SHA1 diff --git a/Source/Core/Common/Crypto/SHA1.h b/Source/Core/Common/Crypto/SHA1.h index 83c9875a71..b828c28f15 100644 --- a/Source/Core/Common/Crypto/SHA1.h +++ b/Source/Core/Common/Crypto/SHA1.h @@ -51,4 +51,6 @@ inline Digest CalculateDigest(const std::array& msg) static_assert(std::is_trivially_copyable_v); return CalculateDigest(reinterpret_cast(msg.data()), sizeof(msg)); } + +std::string DigestToString(const Digest& digest); } // namespace Common::SHA1 diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 34350c9207..5582018b30 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -843,6 +843,8 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_COVERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + COVERCACHE_DIR DIR_SEP; s_user_paths[D_REDUMPCACHE_IDX] = s_user_paths[D_CACHE_IDX] + REDUMPCACHE_DIR DIR_SEP; s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP; + s_user_paths[D_RETROACHIEVEMENTSCACHE_IDX] = + s_user_paths[D_CACHE_IDX] + RETROACHIEVEMENTSCACHE_DIR DIR_SEP; s_user_paths[D_SHADERS_IDX] = s_user_paths[D_USER_IDX] + SHADERS_DIR DIR_SEP; s_user_paths[D_STATESAVES_IDX] = s_user_paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP; s_user_paths[D_SCREENSHOTS_IDX] = s_user_paths[D_USER_IDX] + SCREENSHOTS_DIR DIR_SEP; @@ -926,6 +928,8 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_COVERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + COVERCACHE_DIR DIR_SEP; s_user_paths[D_REDUMPCACHE_IDX] = s_user_paths[D_CACHE_IDX] + REDUMPCACHE_DIR DIR_SEP; s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP; + s_user_paths[D_RETROACHIEVEMENTSCACHE_IDX] = + s_user_paths[D_CACHE_IDX] + RETROACHIEVEMENTSCACHE_DIR DIR_SEP; break; case D_GCUSER_IDX: diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 7d2d5e737a..a887ffd8f3 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -40,6 +40,7 @@ enum D_COVERCACHE_IDX, D_REDUMPCACHE_IDX, D_SHADERCACHE_IDX, + D_RETROACHIEVEMENTSCACHE_IDX, D_SHADERS_IDX, D_STATESAVES_IDX, D_SCREENSHOTS_IDX, diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 99128d68dd..4ef78bf322 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -16,6 +16,7 @@ #include "Common/Assert.h" #include "Common/CommonPaths.h" #include "Common/FileUtil.h" +#include "Common/IOFile.h" #include "Common/Image.h" #include "Common/Logging/Log.h" #include "Common/ScopeGuard.h" @@ -993,30 +994,57 @@ void AchievementManager::FetchBadge(AchievementManager::Badge* badge, u32 badge_ if (name_to_fetch.empty()) return; } - rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), - .image_type = badge_type}; - Badge fetched_badge; - rc_api_request_t api_request; - Common::HttpRequest http_request; - if (rc_api_init_fetch_image_request(&api_request, &icon_request) != RC_OK) + + const std::string cache_path = fmt::format( + "{}/badge-{}-{}.png", File::GetUserPath(D_RETROACHIEVEMENTSCACHE_IDX), badge_type, + Common::SHA1::DigestToString(Common::SHA1::CalculateDigest(name_to_fetch))); + + AchievementManager::Badge tmp_badge; + if (!LoadPNGTexture(&tmp_badge, cache_path)) { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid request for image {}.", name_to_fetch); - return; - } - auto http_response = http_request.Get(api_request.url, USER_AGENT_HEADER, - Common::HttpRequest::AllowedReturnCodes::All); - if (http_response.has_value() && http_response->size() <= 0) - { - WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed on image request.\n URL: {}", - api_request.url); + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = badge_type}; + Badge fetched_badge; + rc_api_request_t api_request; + Common::HttpRequest http_request; + if (rc_api_init_fetch_image_request(&api_request, &icon_request) != RC_OK) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid request for image {}.", name_to_fetch); + return; + } + auto http_response = http_request.Get(api_request.url, USER_AGENT_HEADER, + Common::HttpRequest::AllowedReturnCodes::All); + if (http_response.has_value() && http_response->size() <= 0) + { + WARN_LOG_FMT(ACHIEVEMENTS, + "RetroAchievements connection failed on image request.\n URL: {}", + api_request.url); + rc_api_destroy_request(&api_request); + m_update_callback(callback_data); + return; + } + rc_api_destroy_request(&api_request); - m_update_callback(callback_data); - return; + + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch); + + if (!LoadPNGTexture(&tmp_badge, *http_response)) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Badge '{}' failed to load", name_to_fetch); + return; + } + + std::string temp_path = fmt::format("{}.tmp", cache_path); + File::IOFile temp_file(temp_path, "wb"); + if (!temp_file.IsOpen() || + !temp_file.WriteBytes(http_response->data(), http_response->size()) || + !temp_file.Close() || !File::Rename(temp_path, cache_path)) + { + File::Delete(temp_path); + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to store badge '{}' to cache", name_to_fetch); + } } - rc_api_destroy_request(&api_request); - - INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch); std::lock_guard lg{m_lock}; if (function(*this).empty() || name_to_fetch != function(*this)) { @@ -1024,12 +1052,7 @@ void AchievementManager::FetchBadge(AchievementManager::Badge* badge, u32 badge_ return; } - if (!LoadPNGTexture(badge, *http_response)) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Default game badge '{}' failed to load", - DEFAULT_GAME_BADGE_FILENAME); - } - + *badge = std::move(tmp_badge); m_update_callback(callback_data); if (badge_type == RC_IMAGE_TYPE_ACHIEVEMENT && m_active_challenges.contains(*callback_data.achievements.begin())) diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 0875b53383..4575498fb5 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -271,6 +271,7 @@ void CreateDirectories() File::CreateFullPath(File::GetUserPath(D_SCREENSHOTS_IDX)); File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX)); File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX) + ANAGLYPH_DIR DIR_SEP); + File::CreateFullPath(File::GetUserPath(D_RETROACHIEVEMENTSCACHE_IDX)); File::CreateFullPath(File::GetUserPath(D_STATESAVES_IDX)); File::CreateFullPath(File::GetUserPath(D_ASM_ROOT_IDX)); #ifndef ANDROID