diff --git a/data/resources/images/warning.svg b/data/resources/images/warning.svg new file mode 100644 index 000000000..5e821ed49 --- /dev/null +++ b/data/resources/images/warning.svg @@ -0,0 +1 @@ +risk \ No newline at end of file diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 3824e57c3..485ac89b3 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -137,6 +137,7 @@ static void ReportRCError(int err, fmt::format_string fmt, T&&... args); static void ClearGameInfo(); static void ClearGameHash(); static std::string GetGameHash(CDImage* image); +static bool TryLoggingInWithToken(); static void SetHardcoreMode(bool enabled, bool force_display_message); static bool IsLoggedInOrLoggingIn(); static bool CanEnableHardcoreMode(); @@ -583,24 +584,7 @@ bool Achievements::Initialize() if (System::IsValid()) IdentifyGame(System::GetDiscPath(), nullptr); - std::string username = Host::GetBaseStringSettingValue("Cheevos", "Username"); - std::string api_token = Host::GetBaseStringSettingValue("Cheevos", "Token"); - if (!username.empty() && !api_token.empty()) - { - INFO_LOG("Attempting login with user '{}'...", username); - - // If we can't decrypt the token, it was an old config and we need to re-login. - if (const TinyString decrypted_api_token = DecryptLoginToken(api_token, username); !decrypted_api_token.empty()) - { - s_state.login_request = rc_client_begin_login_with_token( - s_state.client, username.c_str(), decrypted_api_token.c_str(), ClientLoginWithTokenCallback, nullptr); - } - else - { - WARNING_LOG("Invalid encrypted login token, requesitng a new one."); - Host::OnAchievementsLoginRequested(LoginRequestReason::TokenInvalid); - } - } + TryLoggingInWithToken(); // Hardcore mode isn't enabled when achievements first starts, if a game is already running. if (System::IsValid() && IsLoggedInOrLoggingIn() && g_settings.achievements_hardcore_mode) @@ -649,6 +633,36 @@ void Achievements::DestroyClient(rc_client_t** client, std::unique_ptrreset(); } +bool Achievements::TryLoggingInWithToken() +{ + std::string username = Host::GetBaseStringSettingValue("Cheevos", "Username"); + std::string api_token = Host::GetBaseStringSettingValue("Cheevos", "Token"); + if (username.empty() || api_token.empty()) + return false; + + INFO_LOG("Attempting token login with user '{}'...", username); + + // If we can't decrypt the token, it was an old config and we need to re-login. + if (const TinyString decrypted_api_token = DecryptLoginToken(api_token, username); !decrypted_api_token.empty()) + { + s_state.login_request = rc_client_begin_login_with_token( + s_state.client, username.c_str(), decrypted_api_token.c_str(), ClientLoginWithTokenCallback, nullptr); + if (!s_state.login_request) + { + WARNING_LOG("Creating login request failed."); + return false; + } + + return true; + } + else + { + WARNING_LOG("Invalid encrypted login token, requesitng a new one."); + Host::OnAchievementsLoginRequested(LoginRequestReason::TokenInvalid); + return false; + } +} + void Achievements::UpdateSettings(const Settings& old_config) { if (IsUsingRAIntegration()) @@ -976,7 +990,7 @@ void Achievements::UpdateRichPresence(std::unique_lock& lo lock.lock(); } -void Achievements::GameChanged(const std::string& path, CDImage* image) +void Achievements::GameChanged(const std::string& path, CDImage* image, bool booting) { std::unique_lock lock(s_state.mutex); @@ -984,6 +998,15 @@ void Achievements::GameChanged(const std::string& path, CDImage* image) return; IdentifyGame(path, image); + + // if we're not logged in, and there's no login request, retry logging in + // this'll happen if we had no network connection on startup, but gained it before starting a game. + // follow the same order as Initialize() - identify, then log in + if (!IsLoggedInOrLoggingIn() && booting) + { + WARNING_LOG("Not logged in on game booting, trying again."); + TryLoggingInWithToken(); + } } void Achievements::IdentifyGame(const std::string& path, CDImage* image) @@ -1985,12 +2008,32 @@ void Achievements::ClientLoginWithTokenCallback(int result, const char* error_me { s_state.login_request = nullptr; - if (result != RC_OK) + if (result == RC_INVALID_CREDENTIALS || result == RC_EXPIRED_TOKEN) { - ReportFmtError("Login failed: {}", error_message); + ERROR_LOG("Login failed due to invalid token: {}: {}", rc_error_str(result), error_message); Host::OnAchievementsLoginRequested(LoginRequestReason::TokenInvalid); return; } + else if (result != RC_OK) + { + ERROR_LOG("Login failed: {}: {}", rc_error_str(result), error_message); + + // only display user error if they've started a game + if (System::IsValid()) + { + std::string message = + fmt::format("Achievement unlocks will not be submitted for this session.\nError: {}", error_message); + GPUThread::RunOnThread([message = std::move(message)]() mutable { + if (!GPUThread::HasGPUBackend() || !FullscreenUI::Initialize()) + return; + + ImGuiFullscreen::AddNotification("AchievementsLoginFailed", Host::OSD_ERROR_DURATION, + "RetroAchievements Login Failed", std::move(message), "images/warning.svg"); + }); + } + + return; + } ShowLoginSuccess(client); diff --git a/src/core/achievements.h b/src/core/achievements.h index 7a2b5dc89..2ac140e99 100644 --- a/src/core/achievements.h +++ b/src/core/achievements.h @@ -72,7 +72,7 @@ bool Login(const char* username, const char* password, Error* error); void Logout(); /// Called when the system changes game, or is booting. -void GameChanged(const std::string& path, CDImage* image); +void GameChanged(const std::string& path, CDImage* image, bool booting); /// Re-enables hardcore mode if it is enabled in the settings. bool ResetHardcoreMode(bool is_booting); diff --git a/src/core/system.cpp b/src/core/system.cpp index e37f18cd9..65cf6b11a 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2014,7 +2014,7 @@ void System::ClearRunningGame() Host::OnGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title); - Achievements::GameChanged(s_state.running_game_path, nullptr); + Achievements::GameChanged(s_state.running_game_path, nullptr, false); UpdateRichPresence(true); } @@ -4175,7 +4175,7 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo if (booting) Achievements::ResetHardcoreMode(true); - Achievements::GameChanged(s_state.running_game_path, image); + Achievements::GameChanged(s_state.running_game_path, image, booting); // game layer reloads cheats, but only the active list, we need new files Cheats::ReloadCheats(true, false, false, true); diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index 607c1dc7b..7c241e490 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -3219,7 +3219,8 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) const ImVec2 badge_max(badge_min.x + badge_size, badge_min.y + badge_size); if (!notif.badge_path.empty()) { - GPUTexture* tex = GetCachedTexture(notif.badge_path.c_str()); + GPUTexture* tex = + GetCachedTexture(notif.badge_path.c_str(), static_cast(badge_size), static_cast(badge_size)); if (tex) { dl->AddImage(tex, badge_min, badge_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f),