Achievements: Use rc_client as source of truth for HC mode

This commit is contained in:
Stenzek 2025-04-09 22:26:54 +10:00
parent a0aac8ef17
commit 1bb1354d4e
No known key found for this signature in database
9 changed files with 282 additions and 310 deletions

View File

@ -7,6 +7,7 @@
#include "achievements_private.h"
#include "bios.h"
#include "bus.h"
#include "cheats.h"
#include "cpu_core.h"
#include "fullscreen_ui.h"
#include "game_list.h"
@ -144,14 +145,14 @@ static void ReportRCError(int err, fmt::format_string<T...> fmt, T&&... args);
static void ClearGameInfo();
static void ClearGameHash();
static bool TryLoggingInWithToken();
static void SetHardcoreMode(bool enabled, bool force_display_message);
static void EnableHardcodeMode(bool display_message, bool display_game_summary);
static void OnHardcoreModeChanged(bool enabled, bool display_message, bool display_game_summary);
static bool IsLoggedInOrLoggingIn();
static bool CanEnableHardcoreMode();
static void FinishLogin(const rc_client_t* client);
static void ShowLoginNotification();
static void IdentifyGame(const std::string& path, CDImage* image);
static bool IdentifyGame(CDImage* image);
static bool IdentifyCurrentGame();
static void BeginLoadGame();
static void BeginChangeDisc();
static void UpdateGameSummary(bool update_progress_database, bool force_update_progress_database);
static std::string GetLocalImagePath(const std::string_view image_name, int type);
static void DownloadImage(std::string url, std::string cache_path);
@ -244,7 +245,6 @@ struct PauseMenuAchievementInfo
struct State
{
rc_client_t* client = nullptr;
bool hardcore_mode = false;
bool has_achievements = false;
bool has_leaderboards = false;
bool has_rich_presence = false;
@ -330,9 +330,8 @@ const rc_client_user_game_summary_t& Achievements::GetGameSummary()
void Achievements::ReportError(std::string_view sv)
{
std::string error = fmt::format("Achievements error: {}", sv);
ERROR_LOG(error.c_str());
Host::AddOSDMessage(std::move(error), Host::OSD_CRITICAL_ERROR_DURATION);
ERROR_LOG(sv);
Host::AddIconOSDWarning(std::string(), ICON_EMOJI_WARNING, std::string(sv), Host::OSD_CRITICAL_ERROR_DURATION);
}
template<typename... T>
@ -581,7 +580,11 @@ bool Achievements::IsHardcoreModeActive()
return RA_HardcoreModeIsActive() != 0;
#endif
return s_state.hardcore_mode;
if (!s_state.client)
return false;
const auto lock = GetLock();
return rc_client_get_hardcore_enabled(s_state.client);
}
bool Achievements::HasActiveGame()
@ -651,25 +654,27 @@ bool Achievements::Initialize()
if (!CreateClient(&s_state.client, &s_state.http_downloader))
return false;
// Hardcore starts off. We enable it on first boot.
s_state.hardcore_mode = false;
rc_client_set_event_handler(s_state.client, ClientEventHandler);
rc_client_set_hardcore_enabled(s_state.client, s_state.hardcore_mode);
// Hardcore starts off. We enable it on first boot.
rc_client_set_hardcore_enabled(s_state.client, false);
rc_client_set_encore_mode_enabled(s_state.client, g_settings.achievements_encore_mode);
rc_client_set_unofficial_enabled(s_state.client, g_settings.achievements_unofficial_test_mode);
rc_client_set_spectator_mode_enabled(s_state.client, g_settings.achievements_spectator_mode);
// Begin disc identification early, before the login finishes.
if (System::IsValid())
IdentifyGame(System::GetDiscPath(), nullptr);
// Start logging in. This can take a while.
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)
DisplayHardcoreDeferredMessage();
// Are we running a game?
if (System::IsValid())
{
IdentifyCurrentGame();
BeginLoadGame();
// Hardcore mode isn't enabled when achievements first starts, if a game is already running.
if (IsLoggedInOrLoggingIn() && g_settings.achievements_hardcore_mode)
DisplayHardcoreDeferredMessage();
}
return true;
}
@ -753,7 +758,7 @@ void Achievements::UpdateSettings(const Settings& old_config)
if (!g_settings.achievements_enabled)
{
// we're done here
Shutdown(false);
Shutdown();
return;
}
@ -766,26 +771,23 @@ void Achievements::UpdateSettings(const Settings& old_config)
if (g_settings.achievements_hardcore_mode != old_config.achievements_hardcore_mode)
{
// Hardcore mode can only be enabled through reset (ResetChallengeMode()).
if (s_state.hardcore_mode && !g_settings.achievements_hardcore_mode)
{
ResetHardcoreMode(false);
}
else if (!s_state.hardcore_mode && g_settings.achievements_hardcore_mode)
{
if (HasActiveGame())
DisplayHardcoreDeferredMessage();
}
// Enables have to wait for reset, disables can go through immediately.
if (g_settings.achievements_hardcore_mode)
DisplayHardcoreDeferredMessage();
else
DisableHardcoreMode(true, true);
}
// These cannot be modified while a game is loaded, so just toss state and reload.
auto lock = GetLock();
if (HasActiveGame())
{
lock.unlock();
if (g_settings.achievements_encore_mode != old_config.achievements_encore_mode ||
g_settings.achievements_spectator_mode != old_config.achievements_spectator_mode ||
g_settings.achievements_unofficial_test_mode != old_config.achievements_unofficial_test_mode)
{
Shutdown(false);
Shutdown();
Initialize();
return;
}
@ -801,48 +803,27 @@ void Achievements::UpdateSettings(const Settings& old_config)
}
}
bool Achievements::Shutdown(bool allow_cancel)
void Achievements::Shutdown()
{
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
{
if (System::IsValid() && allow_cancel && !RA_ConfirmLoadNewRom(true))
return false;
RA_SetPaused(false);
RA_ActivateGame(0);
return true;
}
#endif
if (!IsActive())
return true;
return;
auto lock = GetLock();
Assert(s_state.client && s_state.http_downloader);
ClearGameInfo();
ClearGameHash();
DisableHardcoreMode();
DisableHardcoreMode(false, false);
UpdateGlyphRanges();
CancelHashDatabaseRequests();
if (s_state.load_game_request)
{
rc_client_abort_async(s_state.client, s_state.load_game_request);
s_state.load_game_request = nullptr;
}
if (s_state.login_request)
{
rc_client_abort_async(s_state.client, s_state.login_request);
s_state.login_request = nullptr;
}
s_state.hardcore_mode = false;
DestroyClient(&s_state.client, &s_state.http_downloader);
Host::OnAchievementsRefreshed();
return true;
}
void Achievements::ClientMessageCallback(const char* message, const rc_client_t* client)
@ -892,7 +873,7 @@ void Achievements::ClientServerCall(const rc_api_request_t* request, rc_client_s
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) :
status_code;
rr.body_length = data.size();
rr.body = reinterpret_cast<const char*>(data.data());
rr.body = data.empty() ? nullptr : reinterpret_cast<const char*>(data.data());
callback(&rr, callback_data);
};
@ -1147,40 +1128,109 @@ void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lo
lock.lock();
}
void Achievements::GameChanged(const std::string& path, CDImage* image, bool booting)
void Achievements::OnSystemStarting(CDImage* image, bool disable_hardcore_mode)
{
std::unique_lock lock(s_state.mutex);
if (!IsActive())
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)
if (!IsLoggedInOrLoggingIn())
{
WARNING_LOG("Not logged in on game booting, trying again.");
TryLoggingInWithToken();
}
// HC should have been disabled, we're now enabling it
// only enable hardcore mode if we're logged in, or waiting for a login response
AssertMsg(!rc_client_get_hardcore_enabled(s_state.client), "Hardcode mode should be disabled prior to boot");
if (!disable_hardcore_mode && g_settings.achievements_hardcore_mode && IsLoggedInOrLoggingIn())
EnableHardcodeMode(false, false);
// now we can finally identify the game
IdentifyGame(image);
BeginLoadGame();
}
void Achievements::IdentifyGame(const std::string& path, CDImage* image)
void Achievements::OnSystemDestroyed()
{
if (s_state.game_path == path)
ClearGameInfo();
ClearGameHash();
DisableHardcoreMode(false, false);
UpdateGlyphRanges();
}
void Achievements::OnSystemPaused(bool paused)
{
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
RA_SetPaused(paused);
#endif
}
void Achievements::OnSystemReset()
{
const auto lock = GetLock();
if (!IsActive())
return;
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
{
WARNING_LOG("Game path is unchanged.");
RA_OnReset();
return;
}
#endif
std::unique_ptr<CDImage> temp_image;
if (!path.empty() && !image)
// Do we need to enable hardcore mode?
if (System::IsValid() && g_settings.achievements_hardcore_mode && !rc_client_get_hardcore_enabled(s_state.client))
{
temp_image = CDImage::Open(path.c_str(), g_settings.cdrom_load_image_patches, nullptr);
image = temp_image.get();
if (!temp_image)
ERROR_LOG("Failed to open temporary CD image '{}'", path);
// This will raise the silly reset event, but we can safely ignore that since we're immediately resetting the client
DEV_LOG("Enabling hardcore mode after reset");
EnableHardcodeMode(true, true);
}
DEV_LOG("Reset client");
rc_client_reset(s_state.client);
}
void Achievements::GameChanged(CDImage* image)
{
std::unique_lock lock(s_state.mutex);
if (!IsActive())
return;
// disc changed?
if (!IdentifyGame(image))
return;
// cancel previous requests
if (s_state.load_game_request)
{
rc_client_abort_async(s_state.client, s_state.load_game_request);
s_state.load_game_request = nullptr;
}
// Use a hash that will never match if we removed the disc. See rc_client_begin_change_media().
TinyString game_hash_str;
if (s_state.game_hash.has_value())
game_hash_str = GameHashToString(s_state.game_hash.value());
else
game_hash_str = "[NO HASH]";
s_state.load_game_request = rc_client_begin_change_media_from_hash(
s_state.client, game_hash_str.c_str(), ClientLoadGameCallback, reinterpret_cast<void*>(static_cast<uintptr_t>(1)));
}
bool Achievements::IdentifyGame(CDImage* image)
{
if (s_state.game_path == image->GetPath())
{
WARNING_LOG("Game path is unchanged.");
return false;
}
std::optional<GameHash> game_hash;
@ -1189,63 +1239,64 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image)
u32 bytes_hashed;
game_hash = GetGameHash(image, &bytes_hashed);
if (game_hash.has_value())
INFO_LOG("RA Hash: {} ({} bytes hashed)", GameHashToString(game_hash.value()), bytes_hashed);
{
INFO_COLOR_LOG(StrongOrange, "RA Hash: {} ({} bytes hashed)", GameHashToString(game_hash.value()), bytes_hashed);
}
else
{
// If we are starting with this game and it's bad, notify the user that this is why.
Host::AddIconOSDWarning(
"AchievementsHashFailed", ICON_EMOJI_WARNING,
TRANSLATE_STR("Achievements", "Failed to read executable from disc. Achievements disabled."),
Host::OSD_ERROR_DURATION);
}
}
s_state.game_path = image ? image->GetPath() : std::string();
if (s_state.game_hash == game_hash)
{
// only the path has changed - different format/save state/etc.
INFO_LOG("Detected path change from '{}' to '{}'", s_state.game_path, path);
s_state.game_path = path;
return;
INFO_LOG("Detected path change to '{}'", s_state.game_path);
s_state.game_path = image->GetPath();
return false;
}
ClearGameHash();
s_state.game_path = path;
s_state.game_hash = std::move(game_hash);
s_state.game_hash = game_hash;
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
{
RAIntegration::GameChanged();
return;
}
#endif
// shouldn't have a load game request when we're not logged in.
Assert(IsLoggedInOrLoggingIn() || !s_state.load_game_request);
return true;
}
// bail out if we're not logged in, just save the hash
if (!IsLoggedInOrLoggingIn())
bool Achievements::IdentifyCurrentGame()
{
DebugAssert(System::IsValid());
// this crap is only needed because we can't grab the image from the reader...
std::unique_ptr<CDImage> temp_image;
if (const std::string& disc_path = System::GetDiscPath(); !disc_path.empty())
{
INFO_LOG("Skipping load game because we're not logged in.");
DisableHardcoreMode();
return;
Error error;
temp_image = CDImage::Open(disc_path.c_str(), g_settings.cdrom_load_image_patches, &error);
if (!temp_image)
ERROR_LOG("Failed to open disc for late game identification: {}", error.GetDescription());
}
if (!rc_client_is_game_loaded(s_state.client))
BeginLoadGame();
else
BeginChangeDisc();
return IdentifyGame(temp_image.get());
}
void Achievements::BeginLoadGame()
{
ClearGameInfo();
DebugAssert(IsLoggedInOrLoggingIn());
if (!s_state.game_hash.has_value())
{
// when we're booting the bios, this will fail
if (!s_state.game_path.empty())
{
Host::AddKeyedOSDMessage(
"retroachievements_disc_read_failed",
TRANSLATE_STR("Achievements", "Failed to read executable from disc. Achievements disabled."),
Host::OSD_ERROR_DURATION);
}
DisableHardcoreMode();
UpdateGlyphRanges();
// no need to go through ClientLoadGameCallback, just bail out straight away
DisableHardcoreMode(false, false);
return;
}
@ -1253,37 +1304,6 @@ void Achievements::BeginLoadGame()
ClientLoadGameCallback, nullptr);
}
void Achievements::BeginChangeDisc()
{
// cancel previous requests
if (s_state.load_game_request)
{
rc_client_abort_async(s_state.client, s_state.load_game_request);
s_state.load_game_request = nullptr;
}
if (!s_state.game_hash.has_value())
{
// when we're booting the bios, this will fail
if (!s_state.game_path.empty())
{
Host::AddKeyedOSDMessage(
"retroachievements_disc_read_failed",
TRANSLATE_STR("Achievements", "Failed to read executable from disc. Achievements disabled."),
Host::OSD_ERROR_DURATION);
}
ClearGameInfo();
DisableHardcoreMode();
UpdateGlyphRanges();
return;
}
s_state.load_game_request =
rc_client_begin_change_media_from_hash(s_state.client, GameHashToString(s_state.game_hash.value()),
ClientLoadGameCallback, reinterpret_cast<void*>(static_cast<uintptr_t>(1)));
}
void Achievements::ClientLoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata)
{
const bool was_disc_change = (userdata != nullptr);
@ -1300,7 +1320,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
UpdateGlyphRanges();
}
DisableHardcoreMode();
DisableHardcoreMode(false, false);
return;
}
else if (result == RC_LOGIN_REQUIRED)
@ -1309,6 +1329,14 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
// Once we've done so, we'll reload the game.
return;
}
else if (result == RC_HARDCORE_DISABLED)
{
if (error_message)
ReportError(error_message);
OnHardcoreModeChanged(false, true, false);
return;
}
else if (result != RC_OK)
{
ReportFmtError("Loading game failed: {}", error_message);
@ -1318,16 +1346,9 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
UpdateGlyphRanges();
}
DisableHardcoreMode();
DisableHardcoreMode(false, false);
return;
}
else if (result == RC_HARDCORE_DISABLED)
{
if (error_message)
ReportError(error_message);
DisableHardcoreMode();
}
const rc_client_game_t* info = rc_client_get_game_info(s_state.client);
if (!info)
@ -1339,7 +1360,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
UpdateGlyphRanges();
}
DisableHardcoreMode();
DisableHardcoreMode(false, false);
return;
}
@ -1349,13 +1370,10 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
// Only display summary if the game title has changed across discs.
const bool display_summary = (s_state.game_id != info->id || s_state.game_title != info->title);
// If the game has a RetroAchievements entry but no achievements or leaderboards,
// enforcing hardcore mode is pointless.
if (!has_achievements && !has_leaderboards)
DisableHardcoreMode();
// We should have matched hardcore mode state.
Assert(s_state.hardcore_mode == (rc_client_get_hardcore_enabled(client) != 0));
// If the game has a RetroAchievements entry but no achievements or leaderboards, enforcing hardcore mode
// is pointless. Have to re-query leaderboards because hidden should still trip HC.
if (!has_achievements && !rc_client_has_leaderboards(client, true))
DisableHardcoreMode(false, false);
s_state.game_id = info->id;
s_state.game_title = info->title;
@ -1469,7 +1487,7 @@ void Achievements::DisplayAchievementSummary()
void Achievements::DisplayHardcoreDeferredMessage()
{
if (g_settings.achievements_hardcore_mode && !s_state.hardcore_mode && System::IsValid())
if (g_settings.achievements_hardcore_mode && System::IsValid())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::Initialize())
@ -1484,12 +1502,7 @@ void Achievements::DisplayHardcoreDeferredMessage()
void Achievements::HandleResetEvent(const rc_client_event_t* event)
{
// We handle system resets ourselves, but still need to reset the client's state.
INFO_LOG("Resetting runtime due to reset event");
rc_client_reset(s_state.client);
if (HasActiveGame())
UpdateGameSummary(false, false);
WARNING_LOG("Ignoring RC_CLIENT_EVENT_RESET.");
}
void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
@ -1806,32 +1819,17 @@ void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
});
}
void Achievements::Reset()
void Achievements::EnableHardcodeMode(bool display_message, bool display_game_summary)
{
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
{
RA_OnReset();
return;
}
#endif
if (!IsActive())
DebugAssert(IsActive());
if (rc_client_get_hardcore_enabled(s_state.client))
return;
DEV_LOG("Reset client");
rc_client_reset(s_state.client);
rc_client_set_hardcore_enabled(s_state.client, true);
OnHardcoreModeChanged(true, display_message, display_game_summary);
}
void Achievements::OnSystemPaused(bool paused)
{
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
RA_SetPaused(paused);
#endif
}
void Achievements::DisableHardcoreMode()
void Achievements::DisableHardcoreMode(bool show_message, bool display_game_summary)
{
if (!IsActive())
return;
@ -1846,43 +1844,19 @@ void Achievements::DisableHardcoreMode()
}
#endif
if (!s_state.hardcore_mode)
return;
SetHardcoreMode(false, true);
}
bool Achievements::ResetHardcoreMode(bool is_booting)
{
if (!IsActive())
return false;
const auto lock = GetLock();
// If we're not logged in, don't apply hardcore mode restrictions.
// If we later log in, we'll start with it off anyway.
const bool wanted_hardcore_mode = (IsLoggedInOrLoggingIn() || s_state.load_game_request) &&
(HasActiveGame() || s_state.load_game_request) &&
g_settings.achievements_hardcore_mode;
if (s_state.hardcore_mode == wanted_hardcore_mode)
return false;
if (!is_booting && wanted_hardcore_mode && !CanEnableHardcoreMode())
return false;
SetHardcoreMode(wanted_hardcore_mode, false);
return true;
}
void Achievements::SetHardcoreMode(bool enabled, bool force_display_message)
{
if (enabled == s_state.hardcore_mode)
if (!rc_client_get_hardcore_enabled(s_state.client))
return;
// new mode
s_state.hardcore_mode = enabled;
rc_client_set_hardcore_enabled(s_state.client, false);
OnHardcoreModeChanged(false, show_message, display_game_summary);
}
if (System::IsValid() && (HasActiveGame() || force_display_message))
void Achievements::OnHardcoreModeChanged(bool enabled, bool display_message, bool display_game_summary)
{
INFO_COLOR_LOG(StrongYellow, "Hardcore mode/restrictions are now {}.", enabled ? "ACTIVE" : "inactive");
if (System::IsValid() && display_message)
{
GPUThread::RunOnThread([enabled]() {
if (!FullscreenUI::Initialize())
@ -1895,17 +1869,28 @@ void Achievements::SetHardcoreMode(bool enabled, bool force_display_message)
});
}
rc_client_set_hardcore_enabled(s_state.client, enabled);
DebugAssert((rc_client_get_hardcore_enabled(s_state.client) != 0) == enabled);
if (HasActiveGame())
if (HasActiveGame() && display_game_summary)
{
UpdateGameSummary(true, true);
DisplayAchievementSummary();
}
DebugAssert((rc_client_get_hardcore_enabled(s_state.client) != 0) == enabled);
// Reload setting to permit cheating-like things if we were just disabled.
if (!enabled && System::IsValid())
if (System::IsValid())
{
// Make sure a pre-existing cheat file hasn't been loaded when resetting after enabling HC mode.
Cheats::ReloadCheats(true, true, false, true, true);
// Defer settings update in case something is using it.
Host::RunOnCPUThread([]() { System::ApplySettings(false); });
}
else if (System::GetState() == System::State::Starting)
{
// Initial HC enable, activate restrictions.
System::ApplySettings(false);
}
// Toss away UI state, because it's invalid now
ClearUIState();
@ -1947,9 +1932,6 @@ bool Achievements::DoState(StateWrapper& sw)
GPUThread::RunOnThread([]() { FullscreenUI::CloseLoadingScreen(); });
}
// loading an old state without cheevos, so reset the runtime
Achievements::Reset();
u32 data_size = 0;
sw.DoEx(&data_size, REQUIRED_VERSION, 0u);
if (data_size == 0)
@ -2096,12 +2078,6 @@ bool Achievements::IsLoggedInOrLoggingIn()
return (rc_client_get_user_info(s_state.client) != nullptr || s_state.login_request);
}
bool Achievements::CanEnableHardcoreMode()
{
// have to re-query leaderboards because hidden should still trip HC
return (s_state.load_game_request || s_state.has_achievements || rc_client_has_leaderboards(s_state.client, true));
}
bool Achievements::Login(const char* username, const char* password, Error* error)
{
auto lock = GetLock();
@ -2145,7 +2121,10 @@ bool Achievements::Login(const char* username, const char* password, Error* erro
// If we were't a temporary client, get the game loaded.
if (System::IsValid() && !is_temporary_client)
{
IdentifyCurrentGame();
BeginLoadGame();
}
return true;
}
@ -2223,9 +2202,6 @@ void Achievements::ClientLoginWithTokenCallback(int result, const char* error_me
}
FinishLogin(client);
if (System::IsValid())
BeginLoadGame();
}
void Achievements::FinishLogin(const rc_client_t* client)
@ -2338,7 +2314,7 @@ void Achievements::Logout()
ClearProgressDatabase();
}
bool Achievements::ConfirmSystemReset()
bool Achievements::ConfirmGameChange()
{
#ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration())
@ -2364,7 +2340,7 @@ bool Achievements::ConfirmHardcoreModeDisable(const char* trigger)
if (!confirmed)
return false;
DisableHardcoreMode();
DisableHardcoreMode(true, true);
return true;
}
@ -2374,7 +2350,7 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
// don't run the callback in the middle of rendering the UI
Host::RunOnCPUThread([callback = std::move(callback), res]() {
if (res)
DisableHardcoreMode();
DisableHardcoreMode(true, true);
callback(res);
});
};
@ -2826,7 +2802,7 @@ void Achievements::DrawAchievementsWindow()
using ImGuiFullscreen::RenderShadowedTextClipped;
using ImGuiFullscreen::UIStyle;
auto lock = Achievements::GetLock();
const auto lock = Achievements::GetLock();
// achievements can get turned off via the main UI
if (!s_state.achievement_list)
@ -2886,7 +2862,7 @@ void Achievements::DrawAchievementsWindow()
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.LargeFont->FontSize));
text.assign(s_state.game_title);
if (s_state.hardcore_mode)
if (rc_client_get_hardcore_enabled(s_state.client))
text.append(TRANSLATE_SV("Achievements", " (Hardcore Mode)"));
top += UIStyle.LargeFont->FontSize + spacing;

View File

@ -77,18 +77,24 @@ bool Initialize();
/// Updates achievements settings.
void UpdateSettings(const Settings& old_config);
/// Resets the internal state of all achievement tracking. Call on system reset.
void Reset();
/// Shuts down the RetroAchievements client.
void Shutdown();
/// Called when the system is being reset. If it returns false, the reset should be aborted.
bool ConfirmSystemReset();
/// Called when the system is start. Engages hardcore mode if enabled.
void OnSystemStarting(CDImage* image, bool disable_hardcore_mode);
/// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted.
bool Shutdown(bool allow_cancel);
/// Called when the system is shutting down. If this returns false, the shutdown should be aborted.
void OnSystemDestroyed();
/// Called when the system is being reset. Resets the internal state of all achievement tracking.
void OnSystemReset();
/// Called when the system is being paused and resumed.
void OnSystemPaused(bool paused);
/// Called when the system changes game.
void GameChanged(CDImage* image);
/// Called once a frame at vsync time on the CPU thread.
void FrameUpdate();
@ -108,14 +114,8 @@ bool Login(const char* username, const char* password, Error* error);
/// Logs out of RetroAchievements, clearing any credentials.
void Logout();
/// Called when the system changes game, or is booting.
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);
/// Forces hardcore mode off until next reset.
void DisableHardcoreMode();
void DisableHardcoreMode(bool show_message, bool display_game_summary);
/// Prompts the user to disable hardcore mode, if they agree, returns true.
bool ConfirmHardcoreModeDisable(const char* trigger);
@ -127,6 +127,9 @@ bool IsHardcoreModeActive();
/// RAIntegration only exists for Windows, so no point checking it on other platforms.
bool IsUsingRAIntegration();
/// Hook for RAIntegration to confirm reset/shutdown.
bool ConfirmGameChange();
/// Returns true if the achievement system is active. Achievements can be active without a valid client.
bool IsActive();

View File

@ -542,7 +542,7 @@ void System::CPUThreadShutdown()
ShutdownDiscordPresence();
#endif
Achievements::Shutdown(false);
Achievements::Shutdown();
InputManager::CloseSources();
@ -1349,12 +1349,17 @@ void System::ApplySettings(bool display_osd_messages)
LoadSettings(display_osd_messages);
// If we've disabled/enabled game settings, we need to reload without it.
// Also reload cheats when safe mode is toggled, because patches might change.
if (g_settings.apply_game_settings != old_settings.apply_game_settings)
{
UpdateGameSettingsLayer();
LoadSettings(display_osd_messages);
}
else if (g_settings.achievements_hardcore_mode != old_settings.achievements_hardcore_mode)
{
// Hardcore mode enabled/disabled. May need to disable restrictions.
Achievements::UpdateSettings(old_settings);
LoadSettings(display_osd_messages);
}
CheckForSettingsChanges(old_settings);
Host::CheckForSettingsChanges(old_settings);
@ -1546,19 +1551,9 @@ void System::UpdateInputSettingsLayer(std::string input_profile_name, std::uniqu
void System::ResetSystem()
{
if (!IsValid())
if (!IsValid() || !Achievements::ConfirmGameChange())
return;
if (!Achievements::ConfirmSystemReset())
return;
if (Achievements::ResetHardcoreMode(false))
{
// Make sure a pre-existing cheat file hasn't been loaded when resetting after enabling HC mode.
Cheats::ReloadCheats(true, true, false, true, true);
ApplySettings(false);
}
InternalReset();
// Reset boot mode/reload BIOS if needed. Preserve exe/psf boot.
@ -1731,6 +1726,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
// Update running game, this will apply settings as well.
UpdateRunningGame(disc ? disc->GetPath() : parameters.filename, disc.get(), true);
Achievements::OnSystemStarting(disc.get(), parameters.disable_achievements_hardcore_mode);
// Determine console region. Has to be done here, because gamesettings can override it.
s_state.region = (g_settings.region == ConsoleRegion::Auto) ? auto_console_region : g_settings.region;
@ -1755,13 +1751,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
// Achievement hardcore checks before committing to anything.
if (disc)
{
// Check for resuming with hardcore mode.
const bool hc_mode_was_enabled = Achievements::IsHardcoreModeActive();
if (parameters.disable_achievements_hardcore_mode)
Achievements::DisableHardcoreMode();
else
Achievements::ResetHardcoreMode(true);
if ((!parameters.save_state.empty() || !exe_override.empty()) && Achievements::IsHardcoreModeActive())
{
const bool is_exe_override_boot = parameters.save_state.empty();
@ -1794,10 +1783,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
return true;
}
}
// Need to reinit things like emulation speed, cpu overclock, etc.
if (Achievements::IsHardcoreModeActive() != hc_mode_was_enabled)
ApplySettings(false);
}
// Are we fast booting? Must be checked after updating game settings.
@ -1994,6 +1979,7 @@ void System::DestroySystem()
CPU::Shutdown();
Bus::Shutdown();
TimingEvents::Shutdown();
Achievements::OnSystemDestroyed();
ClearRunningGame();
GPUThread::DestroyGPUBackend();
@ -2043,8 +2029,6 @@ void System::ClearRunningGame()
Host::OnGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title,
s_state.running_game_hash);
Achievements::GameChanged(s_state.running_game_path, nullptr, false);
UpdateRichPresence(true);
}
@ -2780,7 +2764,7 @@ void System::InternalReset()
MDEC::Reset();
SIO::Reset();
PCDrv::Reset();
Achievements::Reset();
Achievements::OnSystemReset();
s_state.frame_number = 1;
s_state.internal_frame_number = 0;
}
@ -2963,7 +2947,7 @@ bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bo
ClearMemorySaveStates(false, false);
// Updating game/loading settings can turn on hardcore mode. Catch this.
Achievements::DisableHardcoreMode();
Achievements::DisableHardcoreMode(true, true);
return LoadStateDataFromBuffer(buffer.state_data.cspan(0, buffer.state_size), buffer.version, error, update_display);
}
@ -4205,18 +4189,18 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
}
UpdateGameSettingsLayer();
ApplySettings(true);
if (!IsReplayingGPUDump())
{
Achievements::GameChanged(s_state.running_game_path, image, booting);
// Cheats are loaded later in Initialize().
if (!booting)
{
Achievements::GameChanged(image);
Cheats::ReloadCheats(true, true, false, true, true);
}
}
ApplySettings(true);
if (s_state.running_game_serial != prev_serial)
{
GPUThread::SetGameSerial(s_state.running_game_serial);

View File

@ -56,7 +56,7 @@ void AchievementLoginDialog::cancelClicked()
{
Host::RunOnCPUThread([]() {
if (System::IsValid() && !Achievements::HasActiveGame())
Achievements::DisableHardcoreMode();
Achievements::DisableHardcoreMode(false, false);
});
}

View File

@ -138,9 +138,11 @@ void AchievementSettingsWidget::onHardcoreModeStateChanged()
return;
// don't bother prompting if the game doesn't have achievements
auto lock = Achievements::GetLock();
if (!Achievements::HasActiveGame())
return;
{
auto lock = Achievements::GetLock();
if (!Achievements::HasActiveGame())
return;
}
if (QMessageBox::question(
QtUtils::GetRootWidget(this), tr("Reset System"),

View File

@ -104,8 +104,10 @@ MainWindow* g_main_window = nullptr;
// UI thread VM validity.
static bool s_disable_window_rounded_corners = false;
static bool s_system_starting = false;
static bool s_system_valid = false;
static bool s_system_paused = false;
static bool s_achievements_hardcore_mode = false;
static bool s_fullscreen_ui_started = false;
static std::atomic_uint32_t s_system_locked{false};
static QString s_current_game_title;
@ -170,6 +172,7 @@ void MainWindow::initialize()
m_ui.setupUi(this);
setupAdditionalUi();
updateToolbarActions();
updateEmulationActions(false, false, false);
connectSignals();
restoreStateFromConfig();
@ -515,18 +518,20 @@ void MainWindow::onMouseModeRequested(bool relative_mode, bool hide_cursor)
void MainWindow::onSystemStarting()
{
s_system_starting = true;
s_system_valid = false;
s_system_paused = false;
switchToEmulationView();
updateEmulationActions(true, false, Achievements::IsHardcoreModeActive());
updateEmulationActions(true, false, s_achievements_hardcore_mode);
}
void MainWindow::onSystemStarted()
{
m_was_disc_change_request = false;
s_system_starting = false;
s_system_valid = true;
updateEmulationActions(false, true, Achievements::IsHardcoreModeActive());
updateEmulationActions(false, true, s_achievements_hardcore_mode);
updateWindowTitle();
updateStatusBarWidgetVisibility();
updateDisplayWidgetCursor();
@ -574,6 +579,7 @@ void MainWindow::onSystemDestroyed()
m_ui.actionPause->setChecked(false);
}
s_system_starting = false;
s_system_valid = false;
s_system_paused = false;
@ -585,7 +591,7 @@ void MainWindow::onSystemDestroyed()
return;
}
updateEmulationActions(false, false, Achievements::IsHardcoreModeActive());
updateEmulationActions(false, false, s_achievements_hardcore_mode);
if (m_display_widget)
updateDisplayWidgetCursor();
else
@ -770,7 +776,7 @@ void MainWindow::recreate()
if (was_display_created)
{
g_emu_thread->setSurfaceless(false);
g_main_window->updateEmulationActions(false, System::IsValid(), Achievements::IsHardcoreModeActive());
g_main_window->updateEmulationActions(false, s_system_valid, s_achievements_hardcore_mode);
g_main_window->onFullscreenUIStartedOrStopped(s_fullscreen_ui_started);
}
@ -821,7 +827,6 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
{
std::vector<SaveStateInfo> available_states(System::GetAvailableSaveStates(entry->serial));
const QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
const bool challenge_mode = Achievements::IsHardcoreModeActive();
for (SaveStateInfo& ssi : available_states)
{
if (ssi.global)
@ -835,7 +840,6 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
if (slot < 0)
{
resume_action->setText(tr("Resume (%1)").arg(timestamp_str));
resume_action->setEnabled(!challenge_mode);
action = resume_action;
}
else
@ -844,7 +848,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
action = load_state_menu->addAction(tr("Game Save %1 (%2)").arg(slot).arg(timestamp_str));
}
action->setDisabled(challenge_mode);
action->setDisabled(s_achievements_hardcore_mode);
connect(action, &QAction::triggered,
[this, entry, path = std::move(ssi.path)]() { startFile(entry->path, std::move(path), std::nullopt); });
}
@ -1494,13 +1498,14 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
g_emu_thread->bootSystem(std::move(boot_params));
});
if (m_ui.menuDebug->menuAction()->isVisible() && !Achievements::IsHardcoreModeActive())
if (m_ui.menuDebug->menuAction()->isVisible())
{
connect(menu.addAction(tr("Boot and Debug")), &QAction::triggered, [this, entry]() {
openCPUDebugger();
std::shared_ptr<SystemBootParameters> boot_params = getSystemBootParameters(entry->path);
boot_params->override_start_paused = true;
boot_params->disable_achievements_hardcore_mode = true;
g_emu_thread->bootSystem(std::move(boot_params));
});
}
@ -1805,14 +1810,14 @@ void MainWindow::onToolbarContextMenuRequested(const QPoint& pos)
updateToolbarActions();
}
void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode)
void MainWindow::updateEmulationActions(bool starting, bool running, bool achievements_hardcore_mode)
{
const bool starting_or_running = (starting || running);
const bool starting_or_not_running = (starting || !running);
m_ui.actionStartFile->setDisabled(starting_or_running);
m_ui.actionStartDisc->setDisabled(starting_or_running);
m_ui.actionStartBios->setDisabled(starting_or_running);
m_ui.actionResumeLastState->setDisabled(starting_or_running || cheevos_challenge_mode);
m_ui.actionResumeLastState->setDisabled(starting_or_running || achievements_hardcore_mode);
m_ui.actionStartFullscreenUI->setDisabled(starting_or_running);
m_ui.actionStartFullscreenUI2->setDisabled(starting_or_running);
@ -1821,16 +1826,16 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo
m_ui.actionReset->setDisabled(starting_or_not_running);
m_ui.actionPause->setDisabled(starting_or_not_running);
m_ui.actionChangeDisc->setDisabled(starting_or_not_running);
m_ui.actionCheatsToolbar->setDisabled(starting_or_not_running || cheevos_challenge_mode);
m_ui.actionCheatsToolbar->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionScreenshot->setDisabled(starting_or_not_running);
m_ui.menuChangeDisc->setDisabled(starting_or_not_running);
m_ui.menuCheats->setDisabled(starting_or_not_running || cheevos_challenge_mode);
m_ui.actionCPUDebugger->setDisabled(cheevos_challenge_mode);
m_ui.actionMemoryScanner->setDisabled(cheevos_challenge_mode);
m_ui.menuCheats->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionCPUDebugger->setDisabled(achievements_hardcore_mode);
m_ui.actionMemoryScanner->setDisabled(achievements_hardcore_mode);
m_ui.actionReloadTextureReplacements->setDisabled(starting_or_not_running);
m_ui.actionDumpRAM->setDisabled(starting_or_not_running || cheevos_challenge_mode);
m_ui.actionDumpVRAM->setDisabled(starting_or_not_running || cheevos_challenge_mode);
m_ui.actionDumpSPURAM->setDisabled(starting_or_not_running || cheevos_challenge_mode);
m_ui.actionDumpRAM->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionDumpVRAM->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionDumpSPURAM->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionSaveState->setDisabled(starting_or_not_running);
m_ui.menuSaveState->setDisabled(starting_or_not_running);
@ -2045,8 +2050,6 @@ void MainWindow::switchToEmulationView()
void MainWindow::connectSignals()
{
updateEmulationActions(false, false, Achievements::IsHardcoreModeActive());
connect(qApp, &QGuiApplication::applicationStateChanged, this, &MainWindow::onApplicationStateChanged);
connect(m_ui.toolBar, &QToolBar::customContextMenuRequested, this, &MainWindow::onToolbarContextMenuRequested);
@ -2165,8 +2168,8 @@ void MainWindow::connectSignals()
connect(g_emu_thread, &EmuThread::fullscreenUIStartedOrStopped, this, &MainWindow::onFullscreenUIStartedOrStopped);
connect(g_emu_thread, &EmuThread::achievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
connect(g_emu_thread, &EmuThread::achievementsLoginSuccess, this, &MainWindow::onAchievementsLoginSuccess);
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
&MainWindow::onAchievementsChallengeModeChanged);
connect(g_emu_thread, &EmuThread::achievementsHardcoreModeChanged, this,
&MainWindow::onAchievementsHardcoreModeChanged);
connect(g_emu_thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(g_emu_thread, &EmuThread::onCreateAuxiliaryRenderWindow, this, &MainWindow::onCreateAuxiliaryRenderWindow,
Qt::BlockingQueuedConnection);
@ -2795,7 +2798,7 @@ void MainWindow::onAchievementsLoginSuccess(const QString& username, quint32 poi
}
}
void MainWindow::onAchievementsChallengeModeChanged(bool enabled)
void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
{
if (enabled)
{
@ -2803,7 +2806,8 @@ void MainWindow::onAchievementsChallengeModeChanged(bool enabled)
QtUtils::CloseAndDeleteWindow(m_memory_scanner_window);
}
updateEmulationActions(false, System::IsValid(), enabled);
s_achievements_hardcore_mode = enabled;
updateEmulationActions(s_system_starting, s_system_valid, enabled);
}
bool MainWindow::onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height,
@ -2889,7 +2893,7 @@ void MainWindow::onToolsMediaCaptureToggled(bool checked)
void MainWindow::onToolsMemoryScannerTriggered()
{
if (Achievements::IsHardcoreModeActive())
if (s_achievements_hardcore_mode)
return;
if (!m_memory_scanner_window)
@ -2913,6 +2917,9 @@ void MainWindow::onToolsISOBrowserTriggered()
void MainWindow::openCPUDebugger()
{
if (s_achievements_hardcore_mode)
return;
if (!m_debugger_window)
{
m_debugger_window = new DebuggerWindow();

View File

@ -151,7 +151,7 @@ private Q_SLOTS:
void onMediaCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
void onAchievementsLoginSuccess(const QString& username, quint32 points, quint32 sc_points, quint32 unread_messages);
void onAchievementsChallengeModeChanged(bool enabled);
void onAchievementsHardcoreModeChanged(bool enabled);
bool onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height,
const QString& title, const QString& icon_name,
Host::AuxiliaryRenderWindowUserData userdata,

View File

@ -1677,7 +1677,7 @@ void Host::OnAchievementsRefreshed()
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
{
emit g_emu_thread->achievementsChallengeModeChanged(enabled);
emit g_emu_thread->achievementsHardcoreModeChanged(enabled);
}
void Host::OnCoverDownloaderOpenRequested()

View File

@ -155,7 +155,7 @@ Q_SIGNALS:
void achievementsLoginRequested(Achievements::LoginRequestReason reason);
void achievementsLoginSuccess(const QString& username, quint32 points, quint32 sc_points, quint32 unread_messages);
void achievementsRefreshed(quint32 id, const QString& game_info_string);
void achievementsChallengeModeChanged(bool enabled);
void achievementsHardcoreModeChanged(bool enabled);
void cheatEnabled(quint32 index, bool enabled);
void mediaCaptureStarted();
void mediaCaptureStopped();