Achievements: Swap to sqlite-backed storage

This commit is contained in:
Stenzek
2026-06-07 00:00:20 +10:00
parent 1230c83989
commit 98ec05550d
7 changed files with 814 additions and 968 deletions
+794 -865
View File
File diff suppressed because it is too large Load Diff
+12 -15
View File
@@ -33,19 +33,13 @@ enum class LoginRequestReason
inline constexpr size_t GAME_HASH_LENGTH = 16;
using GameHash = std::array<u8, GAME_HASH_LENGTH>;
struct HashDatabaseEntry
{
GameHash hash;
u32 game_id;
u32 num_achievements;
};
class ProgressDatabase
{
public:
struct Entry
{
u32 game_id;
u16 num_achievements;
u16 num_achievements_unlocked;
u16 num_hc_achievements_unlocked;
};
@@ -55,9 +49,16 @@ public:
bool Load(Error* error);
const Entry* LookupGame(u32 game_id) const;
const Entry* LookupHash(const GameHash& hash) const;
private:
struct HashEntry
{
GameHash hash;
u32 game_id;
};
std::vector<HashEntry> m_hashes;
std::vector<Entry> m_entries;
};
@@ -68,15 +69,15 @@ std::unique_lock<std::recursive_mutex> GetLock();
std::optional<GameHash> GetGameHash(CDImage* image);
std::optional<GameHash> GetGameHash(const std::string_view executable_name, std::span<const u8> executable_data);
/// Returns the number of achievements for a given hash.
const HashDatabaseEntry* LookupGameHash(const GameHash& hash);
/// Converts a game hash to a string for display. If the hash is nullopt, returns "[NO HASH]".
TinyString GameHashToString(const std::optional<GameHash>& hash);
/// Updates achievements settings.
void UpdateSettings(const Settings& old_config);
/// Call to refresh the game database.
bool RefreshGameList(ProgressCallback* progress, Error* error);
/// Call to refresh the all-progress database.
bool RefreshAllProgressDatabase(ProgressCallback* progress, Error* error);
@@ -179,10 +180,6 @@ SmallString GetLoggedInUserPointsSummary();
/// Returns the URL for the specified game icon, using the game ID.
std::string GetGameBadgeURL(u32 game_id);
/// Downloads game icons from RetroAchievements for all games that have an achievements_game_id.
/// This fetches the game badge images that are normally downloaded when a game is opened.
bool DownloadGameIcons(ProgressCallback* progress, Error* error);
/// Returns 0 if pausing is allowed, otherwise the number of frames until pausing is allowed.
u32 GetPauseThrottleFrames();
-31
View File
@@ -105,7 +105,6 @@ static void DrawCoverDownloaderWindow();
static void SaveCoverDownloaderURLs();
static void DrawAchievementsLoginWindow();
static void StartAchievementsProgressRefresh();
static void StartAchievementsGameIconDownload();
static void ClearWebCache();
static bool ShouldShowAdvancedSettings();
@@ -5278,12 +5277,6 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
StartAchievementsProgressRefresh();
}
if (MenuButton(FSUI_ICONVSTR(ICON_FA_DOWNLOAD, "Download Game Icons"),
FSUI_VSTR("Downloads icons for all games from RetroAchievements."), enabled))
{
StartAchievementsGameIconDownload();
}
if (MenuButton(FSUI_ICONVSTR(ICON_FA_TRASH, "Clear Web Cache"),
FSUI_VSTR("Clears all cached images in the web cache."), enabled))
{
@@ -5456,30 +5449,6 @@ void FullscreenUI::StartAchievementsProgressRefresh()
});
}
void FullscreenUI::StartAchievementsGameIconDownload()
{
auto progress = OpenModalProgressDialog(FSUI_STR("Download Game Icons"));
Host::QueueAsyncTask([progress = progress.release()]() {
Error error;
const bool result = Achievements::DownloadGameIcons(progress, &error);
Host::RunOnCoreThread([error = std::move(error), progress, result]() mutable {
VideoThread::RunOnThread([error = std::move(error), progress, result]() mutable {
delete progress;
if (result)
{
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Game icons downloaded."));
}
else
{
FullscreenUI::OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, FSUI_STR("Download Game Icons"),
error.TakeDescription());
}
});
});
});
}
void FullscreenUI::ClearWebCache()
{
Error error;
+8 -20
View File
@@ -847,23 +847,14 @@ void GameList::SetCustomSerialOnEntry(Entry* entry, std::string serial, bool upd
void GameList::PopulateEntryAchievements(Entry* entry, const Achievements::ProgressDatabase& achievements_progress)
{
const Achievements::HashDatabaseEntry* hentry = Achievements::LookupGameHash(entry->achievements_hash);
if (!hentry)
const Achievements::ProgressDatabase::Entry* aentry = achievements_progress.LookupHash(entry->achievements_hash);
if (!aentry)
return;
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);
if (apd_entry)
{
entry->unlocked_achievements = apd_entry->num_achievements_unlocked;
entry->unlocked_achievements_hc = apd_entry->num_hc_achievements_unlocked;
}
}
entry->achievements_game_id = aentry->game_id;
entry->num_achievements = Truncate16(aentry->num_achievements);
entry->unlocked_achievements = aentry->num_achievements_unlocked;
entry->unlocked_achievements_hc = aentry->num_hc_achievements_unlocked;
}
void GameList::UpdateAchievementData(std::span<const u8, 16> hash, u32 game_id, u32 num_achievements, u32 num_unlocked,
@@ -1119,11 +1110,8 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback*
custom_attributes_ini.Load();
Achievements::ProgressDatabase achievements_progress;
if (Achievements::HasSavedCredentials())
{
if (!achievements_progress.Load(&error))
WARNING_LOG("Failed to load achievements progress: {}", error.GetDescription());
}
if (!achievements_progress.Load(&error))
WARNING_LOG("Failed to load achievements progress: {}", error.GetDescription());
#ifdef __ANDROID__
recursive_dirs.push_back(Path::Combine(EmuFolders::DataRoot, "games"));
-20
View File
@@ -2577,8 +2577,6 @@ void MainWindow::connectSignals()
connect(m_ui.actionISOBrowser, &QAction::triggered, this, &MainWindow::onToolsISOBrowserTriggered);
connect(m_ui.actionControllerTest, &QAction::triggered, g_core_thread, &CoreThread::startControllerTest);
connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(m_ui.actionToolsDownloadAchievementGameIcons, &QAction::triggered, this,
&MainWindow::onToolsDownloadAchievementGameIconsTriggered);
connect(m_ui.actionToolsRefreshAchievementProgress, &QAction::triggered, g_main_window,
&MainWindow::refreshAchievementProgress);
connect(m_ui.actionMediaCapture, &QAction::triggered, this, &MainWindow::onToolsMediaCaptureTriggered);
@@ -3287,7 +3285,6 @@ void MainWindow::onAchievementsLoginSuccess(const QString& username, quint32 poi
void MainWindow::onAchievementsActiveChanged(bool active)
{
m_ui.actionToolsRefreshAchievementProgress->setEnabled(active);
m_ui.actionToolsDownloadAchievementGameIcons->setEnabled(active);
}
void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
@@ -3358,23 +3355,6 @@ void MainWindow::onToolsCoverDownloaderTriggered()
QtUtils::ShowOrRaiseWindow(m_cover_download_window, this, true);
}
void MainWindow::onToolsDownloadAchievementGameIconsTriggered()
{
QtAsyncTaskWithProgressDialog::create(
this, TRANSLATE_STR("GameListWidget", "Download Game Icons"),
TRANSLATE_STR("GameListWidget", "Downloading game icons..."), false, true, 0, 0, 0.0f, true,
[](ProgressCallback* progress) {
Error error;
const bool result = Achievements::DownloadGameIcons(progress, &error);
return [error = std::move(error), result]() {
if (!result)
g_main_window->reportError(tr("Error"), QString::fromStdString(error.GetDescription()));
g_main_window->m_game_list_widget->getModel()->invalidateColumn(GameListModel::Column_Icon);
};
});
}
void MainWindow::refreshAchievementProgress()
{
QtAsyncTaskWithProgressDialog::create(
-1
View File
@@ -301,7 +301,6 @@ private:
void onToolsMemoryScannerTriggered();
void onToolsISOBrowserTriggered();
void onToolsCoverDownloaderTriggered();
void onToolsDownloadAchievementGameIconsTriggered();
void onToolsMediaCaptureTriggered(bool checked);
void onToolsOpenDataDirectoryTriggered();
void onToolsOpenTextureDirectoryTriggered();
-16
View File
@@ -259,7 +259,6 @@
<addaction name="actionCoverDownloader"/>
<addaction name="actionControllerTest"/>
<addaction name="separator"/>
<addaction name="actionToolsDownloadAchievementGameIcons"/>
<addaction name="actionToolsRefreshAchievementProgress"/>
<addaction name="separator"/>
<addaction name="actionMemoryEditor"/>
@@ -1521,21 +1520,6 @@
<string>Prioritizes the games badges used for RetroAchievements over memory card icons.</string>
</property>
</action>
<action name="actionToolsDownloadAchievementGameIcons">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="resources/duckstation-qt.qrc">
<normaloff>:/icons/monochrome/svg/download-2-line.svg</normaloff>:/icons/monochrome/svg/download-2-line.svg</iconset>
</property>
<property name="text">
<string>Download Achievement &amp;Icons</string>
</property>
<property name="toolTip">
<string>Downloads icons for all games from RetroAchievements.</string>
</property>
</action>
<action name="actionViewSystemLog">
<property name="checkable">
<bool>true</bool>