From 00208d3d8044c4847ee92e45c265d9d405bb48aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 30 Sep 2026 10:51:07 -0600 Subject: [PATCH] Achievements: Enable HTTPS on UWP, retry failed icon downloads UWP compiled naett with the WinHTTP backend but never reported SYSPROP_SUPPORTS_HTTPS, so achievement traffic and badge icons went over plain http. When the media server redirected to https, icons failed. A failed icon download also left the URL marked pending, so it was never retried for the rest of the session. Mark it failed instead, and let IconCache retry it after 30 seconds. The set-completed badge also skipped RemoveHttpsIfNeeded. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/UI/IconCache.cpp | 20 ++++++++++++++++++++ Common/UI/IconCache.h | 3 +++ Core/RetroAchievements.cpp | 15 ++++++++++----- UWP/PPSSPP_UWPMain.cpp | 2 ++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Common/UI/IconCache.cpp b/Common/UI/IconCache.cpp index 598aad75aa..0262cd9910 100644 --- a/Common/UI/IconCache.cpp +++ b/Common/UI/IconCache.cpp @@ -14,6 +14,9 @@ #define MAX_RUNTIME_CACHE_SIZE (1024 * 1024 * 4) #define MAX_SAVED_CACHE_SIZE (1024 * 1024 * 1) +// Seconds before MarkPending accepts a key whose download failed. +constexpr double FAILED_RETRY_DELAY = 30.0; + constexpr uint32_t ICON_CACHE_MAGIC = MK_FOURCC("pICN"); IconCache g_iconCache; @@ -127,6 +130,7 @@ void IconCache::ClearData() { ClearTextures(); std::unique_lock lock(lock_); cache_.clear(); + failed_.clear(); } void IconCache::FrameUpdate() { @@ -231,10 +235,26 @@ bool IconCache::MarkPending(std::string_view key) { if (pending_.find(key) != pending_.end()) { return false; } + auto failedIter = failed_.find(key); + if (failedIter != failed_.end()) { + if (time_now_d() < failedIter->second + FAILED_RETRY_DELAY) { + return false; + } + failed_.erase(failedIter); + } pending_.emplace(key); return true; } +void IconCache::MarkFailed(std::string_view key) { + std::unique_lock lock(lock_); + auto iter = pending_.find(key); + if (iter != pending_.end()) { + pending_.erase(iter); + } + failed_[std::string(key)] = time_now_d(); +} + void IconCache::CancelPending(std::string_view key) { std::unique_lock lock(lock_); auto iter = pending_.find(key); diff --git a/Common/UI/IconCache.h b/Common/UI/IconCache.h index e631f77ca6..1b22e9ba0f 100644 --- a/Common/UI/IconCache.h +++ b/Common/UI/IconCache.h @@ -37,6 +37,8 @@ public: // It's okay to call these from any thread. bool MarkPending(std::string_view key); // returns false if already pending or loaded void CancelPending(std::string_view key); + // Like CancelPending, but MarkPending refuses the key for a while, so a failing download isn't retried every frame. + void MarkFailed(std::string_view key); bool InsertIcon(std::string_view key, IconFormat format, std::string &&pngData); bool GetDimensions(std::string_view key, int *width, int *height); bool Contains(std::string_view key); @@ -65,6 +67,7 @@ private: std::map> cache_; std::set> pending_; + std::map> failed_; // key -> time of failure std::mutex lock_; diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 5519b25ee4..e0aa262d6a 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -351,11 +351,11 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * const rc_client_game_t *gameInfo = rc_client_get_game_info(g_rcClient); std::string setTitle = gameInfo->title; - std::string badgeUrl = gameInfo->badge_url; + std::string badgeUrl = http::RemoveHttpsIfNeeded(gameInfo->badge_url); if (event->type == RC_CLIENT_EVENT_SUBSET_COMPLETED) { const rc_client_subset_t *subset = event->subset; setTitle = subset->title; - badgeUrl = subset->badge_url; + badgeUrl = http::RemoveHttpsIfNeeded(subset->badge_url); } DownloadImageIfMissing(badgeUrl); @@ -964,10 +964,15 @@ void DownloadImageIfMissing(std::string_view url) { if (g_iconCache.MarkPending(url)) { INFO_LOG(Log::Achievements, "Downloading image: %.*s", STR_VIEW(url)); g_DownloadManager.StartDownload(url, Path(), http::RequestFlags::Default, nullptr, "", [](http::Request &download) { - if (download.ResultCode() != 200) - return; std::string data; - download.buffer().TakeAll(&data); + if (download.ResultCode() == 200) { + download.buffer().TakeAll(&data); + } + if (data.empty()) { + WARN_LOG(Log::Achievements, "Failed to download image (%d): %s", download.ResultCode(), download.url().c_str()); + g_iconCache.MarkFailed(download.url()); + return; + } g_iconCache.InsertIcon(download.url(), IconFormat::PNG, std::move(data)); }); } diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 2b64b1f191..30dfe8ab98 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -494,6 +494,8 @@ bool System_GetPropertyBool(SystemProperty prop) { } case SYSPROP_DEBUGGER_PRESENT: return IsDebuggerPresent(); + case SYSPROP_SUPPORTS_HTTPS: + return !g_Config.bDisableHTTPS; case SYSPROP_OK_BUTTON_LEFT: return true; case SYSPROP_CAN_GET_FREE_SPACE_FAST: