Achievements: Add 'Refresh Progress' option

Manually refreshes the all progress database if you have completed
unlocks on other devices/machines.
This commit is contained in:
Stenzek 2025-06-05 19:56:55 +10:00
parent 0a0379f31f
commit d54077e345
No known key found for this signature in database
11 changed files with 169 additions and 3 deletions

View File

@ -140,6 +140,7 @@ static bool TryLoggingInWithToken();
static void EnableHardcodeMode(bool display_message, bool display_game_summary);
static void OnHardcoreModeChanged(bool enabled, bool display_message, bool display_game_summary);
static bool IsRAIntegrationInitializing();
static bool IsLoggedIn();
static bool IsLoggedInOrLoggingIn();
static void FinishInitialize();
static void FinishLogin(const rc_client_t* client);
@ -220,6 +221,8 @@ static void FetchHashLibraryCallback(int result, const char* error_message, rc_c
rc_client_t* client, void* callback_userdata);
static void FetchAllProgressCallback(int result, const char* error_message, rc_client_all_user_progress_t* list,
rc_client_t* client, void* callback_userdata);
static void RefreshAllProgressCallback(int result, const char* error_message, rc_client_all_user_progress_t* list,
rc_client_t* client, void* callback_userdata);
static void BuildHashDatabase(const rc_client_hash_library_t* hashlib, const rc_client_all_user_progress_t* allprog);
static bool SortAndSaveHashDatabase(Error* error);
@ -296,6 +299,7 @@ struct State
rc_client_hash_library_t* fetch_hash_library_result = nullptr;
rc_client_async_handle_t* fetch_all_progress_request = nullptr;
rc_client_all_user_progress_t* fetch_all_progress_result = nullptr;
rc_client_async_handle_t* refresh_all_progress_request = nullptr;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
rc_client_async_handle_t* load_raintegration_request = nullptr;
@ -696,6 +700,8 @@ void Achievements::FinishInitialize()
if (IsLoggedInOrLoggingIn() && g_settings.achievements_hardcore_mode)
DisplayHardcoreDeferredMessage();
}
Host::OnAchievementsActiveChanged(true);
}
bool Achievements::CreateClient(rc_client_t** client, std::unique_ptr<HTTPDownloader>* http)
@ -865,6 +871,7 @@ void Achievements::Shutdown()
#endif
DestroyClient(&s_state.client, &s_state.http_downloader);
Host::OnAchievementsActiveChanged(false);
}
void Achievements::ClientMessageCallback(const char* message, const rc_client_t* client)
@ -2085,9 +2092,14 @@ std::string Achievements::GetLeaderboardUserBadgePath(const rc_client_leaderboar
return path;
}
bool Achievements::IsLoggedIn()
{
return (rc_client_get_user_info(s_state.client) != nullptr);
}
bool Achievements::IsLoggedInOrLoggingIn()
{
return (rc_client_get_user_info(s_state.client) != nullptr || s_state.login_request);
return (IsLoggedIn() || s_state.login_request);
}
bool Achievements::Login(const char* username, const char* password, Error* error)
@ -4055,6 +4067,62 @@ void Achievements::FinishRefreshHashDatabase()
// update game list, we might have some new games that weren't in the seed database
GameList::UpdateAllAchievementData();
Host::OnAchievementsAllProgressRefreshed();
}
bool Achievements::RefreshAllProgressDatabase(Error* error)
{
if (!IsLoggedIn())
{
Error::SetStringView(error, TRANSLATE_SV("Achievements", "User is not logged in."));
return false;
}
if (s_state.fetch_hash_library_request || s_state.fetch_all_progress_request || s_state.refresh_all_progress_request)
{
Error::SetStringView(error, TRANSLATE_SV("Achievements", "Progress is already being updated."));
return false;
}
// refresh in progress
s_state.refresh_all_progress_request = rc_client_begin_fetch_all_user_progress(s_state.client, RC_CONSOLE_PLAYSTATION,
RefreshAllProgressCallback, nullptr);
return true;
}
void Achievements::RefreshAllProgressCallback(int result, const char* error_message,
rc_client_all_user_progress_t* list, rc_client_t* client,
void* callback_userdata)
{
s_state.refresh_all_progress_request = nullptr;
if (result != RC_OK)
{
Host::ReportErrorAsync(TRANSLATE_SV("Achievements", "Error"),
fmt::format("{}: {}\n{}", TRANSLATE_SV("Achievements", "Refresh all progress failed"),
rc_error_str(result), error_message));
return;
}
BuildProgressDatabase(list);
rc_client_destroy_all_user_progress(list);
GameList::UpdateAllAchievementData();
Host::OnAchievementsAllProgressRefreshed();
if (FullscreenUI::IsInitialized())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::IsInitialized())
return;
ImGuiFullscreen::ShowToast({}, TRANSLATE_STR("Achievements", "Updated achievement progress database."),
Host::OSD_INFO_DURATION);
});
}
}
void Achievements::BuildHashDatabase(const rc_client_hash_library_t* hashlib,

View File

@ -80,6 +80,9 @@ void UpdateSettings(const Settings& old_config);
/// Shuts down the RetroAchievements client.
void Shutdown();
/// Call to refresh the all-progress database.
bool RefreshAllProgressDatabase(Error* error);
/// Called when the system is start. Engages hardcore mode if enabled.
void OnSystemStarting(CDImage* image, bool disable_hardcore_mode);
@ -217,9 +220,15 @@ void OnAchievementsLoginSuccess(const char* display_name, u32 points, u32 sc_poi
/// Implementers can assume the lock is held when this is called.
void OnAchievementsRefreshed();
/// Called when achievements login completes or they are disabled.
void OnAchievementsActiveChanged(bool active);
/// Called whenever hardcore mode is toggled.
void OnAchievementsHardcoreModeChanged(bool enabled);
/// Called whenever all progress is manually refreshed and completed.
void OnAchievementsAllProgressRefreshed();
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
/// Called when the RAIntegration menu changes.

View File

@ -6350,10 +6350,19 @@ void FullscreenUI::DrawAchievementsSettingsPage()
false);
ImGui::PopStyleColor();
if (MenuButton(FSUI_ICONVSTR(ICON_FA_KEY, "Logout"), FSUI_VSTR("Logs out of RetroAchievements.")))
if (MenuButton(FSUI_ICONVSTR(ICON_FA_LIST_OL, "Update Progress"),
FSUI_VSTR("Updates the progress database for achievements shown in the game list.")))
{
Host::RunOnCPUThread(&Achievements::Logout);
Host::RunOnCPUThread([]() {
Error error;
if (!Achievements::RefreshAllProgressDatabase(&error))
ImGuiFullscreen::ShowToast(FSUI_STR("Failed to update progress database"), error.TakeDescription(),
Host::OSD_ERROR_DURATION);
});
}
if (MenuButton(FSUI_ICONVSTR(ICON_FA_KEY, "Logout"), FSUI_VSTR("Logs out of RetroAchievements.")))
Host::RunOnCPUThread(&Achievements::Logout);
}
else
{
@ -9365,6 +9374,7 @@ TRANSLATE_NOOP("FullscreenUI", "Failed to delete {}.");
TRANSLATE_NOOP("FullscreenUI", "Failed to load '{}'.");
TRANSLATE_NOOP("FullscreenUI", "Failed to load shader {}. It may be invalid.\nError was:");
TRANSLATE_NOOP("FullscreenUI", "Failed to save controller preset '{}'.");
TRANSLATE_NOOP("FullscreenUI", "Failed to update progress database");
TRANSLATE_NOOP("FullscreenUI", "Fast Boot");
TRANSLATE_NOOP("FullscreenUI", "Fast Forward Boot");
TRANSLATE_NOOP("FullscreenUI", "Fast Forward Memory Card Access");
@ -9769,6 +9779,8 @@ TRANSLATE_NOOP("FullscreenUI", "Ungrouped");
TRANSLATE_NOOP("FullscreenUI", "Unknown");
TRANSLATE_NOOP("FullscreenUI", "Unknown File Size");
TRANSLATE_NOOP("FullscreenUI", "Unlimited");
TRANSLATE_NOOP("FullscreenUI", "Update Progress");
TRANSLATE_NOOP("FullscreenUI", "Updates the progress database for achievements shown in the game list.");
TRANSLATE_NOOP("FullscreenUI", "Upscales the game's rendering by the specified multiplier.");
TRANSLATE_NOOP("FullscreenUI", "Use Blit Swap Chain");
TRANSLATE_NOOP("FullscreenUI", "Use Debug GPU Device");

View File

@ -745,6 +745,8 @@ void GameList::PopulateEntryAchievements(Entry* entry, const Achievements::Progr
entry->achievements_game_id = hentry->game_id;
entry->num_achievements = Truncate16(hentry->num_achievements);
entry->unlocked_achievements = 0;
entry->unlocked_achievements_hc = 0;
if (entry->num_achievements > 0)
{
const Achievements::ProgressDatabase::Entry* apd_entry = achievements_progress.LookupGame(hentry->game_id);

View File

@ -1113,11 +1113,21 @@ void Host::OnAchievementsRefreshed()
// noop
}
void Host::OnAchievementsActiveChanged(bool active)
{
// noop
}
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
{
// noop
}
void Host::OnAchievementsAllProgressRefreshed()
{
// noop
}
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
void Host::OnRAIntegrationMenuChanged()

View File

@ -2100,6 +2100,8 @@ void MainWindow::connectSignals()
connect(m_ui.actionGridViewZoomOut, &QAction::triggered, this, &MainWindow::onViewGameGridZoomOutActionTriggered);
connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget,
&GameListWidget::refreshGridCovers);
connect(m_ui.actionViewRefreshAchievementProgress, &QAction::triggered, g_emu_thread,
&EmuThread::refreshAchievementsAllProgress);
connect(m_ui.actionChangeGameListBackground, &QAction::triggered, this,
&MainWindow::onViewChangeGameListBackgroundTriggered);
connect(m_ui.actionClearGameListBackground, &QAction::triggered, this,
@ -2128,8 +2130,11 @@ 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::achievementsActiveChanged, this, &MainWindow::onAchievementsActiveChanged);
connect(g_emu_thread, &EmuThread::achievementsHardcoreModeChanged, this,
&MainWindow::onAchievementsHardcoreModeChanged);
connect(g_emu_thread, &EmuThread::achievementsAllProgressRefreshed, this,
&MainWindow::onAchievementsAllProgressRefreshed);
connect(g_emu_thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(g_emu_thread, &EmuThread::onCreateAuxiliaryRenderWindow, this, &MainWindow::onCreateAuxiliaryRenderWindow,
Qt::BlockingQueuedConnection);
@ -2758,6 +2763,11 @@ void MainWindow::onAchievementsLoginSuccess(const QString& username, quint32 poi
}
}
void MainWindow::onAchievementsActiveChanged(bool active)
{
m_ui.actionViewRefreshAchievementProgress->setEnabled(active);
}
void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
{
if (enabled)
@ -2770,6 +2780,11 @@ void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
updateEmulationActions(s_system_starting, s_system_valid, enabled);
}
void MainWindow::onAchievementsAllProgressRefreshed()
{
m_ui.statusBar->showMessage(tr("RA: Updated achievement progress database."));
}
bool MainWindow::onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height,
const QString& title, const QString& icon_name,
Host::AuxiliaryRenderWindowUserData userdata,

View File

@ -151,7 +151,9 @@ private Q_SLOTS:
void onMediaCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
void onAchievementsLoginSuccess(const QString& username, quint32 points, quint32 sc_points, quint32 unread_messages);
void onAchievementsActiveChanged(bool active);
void onAchievementsHardcoreModeChanged(bool enabled);
void onAchievementsAllProgressRefreshed();
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

@ -213,6 +213,7 @@
<addaction name="actionGridViewZoomIn"/>
<addaction name="actionGridViewZoomOut"/>
<addaction name="actionGridViewRefreshCovers"/>
<addaction name="actionViewRefreshAchievementProgress"/>
<addaction name="separator"/>
<addaction name="actionChangeGameListBackground"/>
<addaction name="actionClearGameListBackground"/>
@ -987,6 +988,14 @@
<string>Clear List Background</string>
</property>
</action>
<action name="actionViewRefreshAchievementProgress">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Refresh Achievement &amp;Progress</string>
</property>
</action>
</widget>
<resources>
<include location="resources/duckstation-qt.qrc"/>

View File

@ -1631,6 +1631,22 @@ void EmuThread::saveScreenshot()
System::SaveScreenshot();
}
void EmuThread::refreshAchievementsAllProgress()
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, &EmuThread::refreshAchievementsAllProgress, Qt::QueuedConnection);
return;
}
Error error;
if (!Achievements::RefreshAllProgressDatabase(&error))
{
emit errorReported(tr("Error"), QString::fromStdString(error.GetDescription()));
return;
}
}
void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
{
emit g_emu_thread->achievementsLoginRequested(reason);
@ -1669,11 +1685,21 @@ void Host::OnAchievementsRefreshed()
emit g_emu_thread->achievementsRefreshed(game_id, game_info);
}
void Host::OnAchievementsActiveChanged(bool active)
{
emit g_emu_thread->achievementsActiveChanged(active);
}
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
{
emit g_emu_thread->achievementsHardcoreModeChanged(enabled);
}
void Host::OnAchievementsAllProgressRefreshed()
{
emit g_emu_thread->achievementsAllProgressRefreshed();
}
void Host::OnCoverDownloaderOpenRequested()
{
emit g_emu_thread->onCoverDownloaderOpenRequested();

View File

@ -155,7 +155,9 @@ 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 achievementsActiveChanged(bool active);
void achievementsHardcoreModeChanged(bool enabled);
void achievementsAllProgressRefreshed();
void cheatEnabled(quint32 index, bool enabled);
void mediaCaptureStarted();
void mediaCaptureStopped();
@ -183,6 +185,7 @@ public Q_SLOTS:
void closeInputSources();
void startFullscreenUI();
void stopFullscreenUI();
void refreshAchievementsAllProgress();
void bootSystem(std::shared_ptr<SystemBootParameters> params);
void resumeSystemFromMostRecentState();
void shutdownSystem(bool save_state, bool check_memcard_busy);

View File

@ -576,11 +576,21 @@ void Host::OnAchievementsRefreshed()
// noop
}
void Host::OnAchievementsActiveChanged(bool active)
{
// noop
}
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
{
// noop
}
void Host::OnAchievementsAllProgressRefreshed()
{
// noop
}
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
void Host::OnRAIntegrationMenuChanged()