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 @@
+
\ 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),