From 5ade5f4fe7a0302d919ad1207c69034d155cc720 Mon Sep 17 00:00:00 2001 From: spycrab Date: Mon, 23 Jul 2018 00:10:22 +0200 Subject: [PATCH] Qt/GameList: Add option to show covers in grid mode --- Source/Core/Common/CommonPaths.h | 1 + Source/Core/Common/FileUtil.cpp | 2 + Source/Core/Common/FileUtil.h | 1 + Source/Core/Core/Config/UISettings.cpp | 1 + Source/Core/Core/Config/UISettings.h | 1 + Source/Core/DolphinQt/GameList/GameList.cpp | 42 +++++ Source/Core/DolphinQt/GameList/GameList.h | 4 + .../Core/DolphinQt/GameList/GameListModel.cpp | 10 ++ .../Core/DolphinQt/GameList/GameListModel.h | 4 + .../Core/DolphinQt/GameList/GameTracker.cpp | 13 ++ Source/Core/DolphinQt/GameList/GameTracker.h | 1 + .../DolphinQt/GameList/GridProxyModel.cpp | 35 +++- Source/Core/DolphinQt/Settings.cpp | 10 ++ Source/Core/DolphinQt/Settings.h | 4 + .../Core/DolphinQt/Settings/InterfacePane.cpp | 17 ++ .../Core/DolphinQt/Settings/InterfacePane.h | 1 + Source/Core/UICommon/GameFile.cpp | 154 ++++++++++++++++++ Source/Core/UICommon/GameFile.h | 17 ++ Source/Core/UICommon/GameFileCache.cpp | 16 +- Source/Core/UICommon/UICommon.cpp | 1 + 20 files changed, 327 insertions(+), 8 deletions(-) diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 3022e7c5b6bc..0977f96a9e2a 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -39,6 +39,7 @@ #define GAMESETTINGS_DIR "GameSettings" #define MAPS_DIR "Maps" #define CACHE_DIR "Cache" +#define COVERCACHE_DIR "GameCovers" #define SHADERCACHE_DIR "Shaders" #define STATESAVES_DIR "StateSaves" #define SCREENSHOTS_DIR "ScreenShots" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 41cc73240ea7..2784d1ebda9f 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -759,6 +759,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_GAMESETTINGS_IDX] = s_user_paths[D_USER_IDX] + GAMESETTINGS_DIR DIR_SEP; s_user_paths[D_MAPS_IDX] = s_user_paths[D_USER_IDX] + MAPS_DIR DIR_SEP; s_user_paths[D_CACHE_IDX] = s_user_paths[D_USER_IDX] + CACHE_DIR DIR_SEP; + s_user_paths[D_COVERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + COVERCACHE_DIR DIR_SEP; s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP; s_user_paths[D_SHADERS_IDX] = s_user_paths[D_USER_IDX] + SHADERS_DIR DIR_SEP; s_user_paths[D_STATESAVES_IDX] = s_user_paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP; @@ -814,6 +815,7 @@ static void RebuildUserDirectories(unsigned int dir_index) break; case D_CACHE_IDX: + s_user_paths[D_COVERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + COVERCACHE_DIR DIR_SEP; s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP; break; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 035cecb48f1e..8f4c3633eb46 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -29,6 +29,7 @@ enum // settings (per game) D_MAPS_IDX, D_CACHE_IDX, + D_COVERCACHE_IDX, D_SHADERCACHE_IDX, D_SHADERS_IDX, D_STATESAVES_IDX, diff --git a/Source/Core/Core/Config/UISettings.cpp b/Source/Core/Core/Config/UISettings.cpp index 889c215f36ed..2c705a82c5de 100644 --- a/Source/Core/Core/Config/UISettings.cpp +++ b/Source/Core/Core/Config/UISettings.cpp @@ -10,5 +10,6 @@ namespace Config const ConfigInfo MAIN_USE_DISCORD_PRESENCE{{System::Main, "General", "UseDiscordPresence"}, true}; +const ConfigInfo MAIN_USE_GAME_COVERS{{System::Main, "General", "UseGameCovers"}, false}; } // namespace Config diff --git a/Source/Core/Core/Config/UISettings.h b/Source/Core/Core/Config/UISettings.h index e515c2472d94..014854860af8 100644 --- a/Source/Core/Core/Config/UISettings.h +++ b/Source/Core/Core/Config/UISettings.h @@ -17,5 +17,6 @@ namespace Config // UI.General extern const ConfigInfo MAIN_USE_DISCORD_PRESENCE; +extern const ConfigInfo MAIN_USE_GAME_COVERS; } // namespace Config diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index 4167fb8c1599..c1c2e7b363ec 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,15 @@ GameList::GameList(QWidget* parent) : QStackedWidget(parent) addWidget(m_empty); m_prefer_list = Settings::Instance().GetPreferredView(); ConsiderViewChange(); + + auto* zoom_in = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Plus), this); + auto* zoom_out = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Minus), this); + + connect(zoom_in, &QShortcut::activated, this, &GameList::ZoomIn); + connect(zoom_out, &QShortcut::activated, this, &GameList::ZoomOut); + + connect(&Settings::Instance(), &Settings::MetadataRefreshCompleted, this, + [this] { m_grid_proxy->invalidate(); }); } void GameList::MakeListView() @@ -854,3 +864,35 @@ void GameList::SetSearchTerm(const QString& term) UpdateColumnVisibility(); } + +void GameList::ZoomIn() +{ + m_model->SetScale(m_model->GetScale() + 0.1); + + m_list_proxy->invalidate(); + m_grid_proxy->invalidate(); + + UpdateFont(); +} + +void GameList::ZoomOut() +{ + if (m_model->GetScale() <= 0.1) + return; + + m_model->SetScale(m_model->GetScale() - 0.1); + + m_list_proxy->invalidate(); + m_grid_proxy->invalidate(); + + UpdateFont(); +} + +void GameList::UpdateFont() +{ + QFont f; + + f.setPointSizeF(m_model->GetScale() * f.pointSize()); + + m_grid->setFont(f); +} diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index 2e730c558d06..2d42c7cbc36e 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -62,6 +62,9 @@ class GameList final : public QStackedWidget void ChangeDisc(); void UpdateColumnVisibility(); + void ZoomIn(); + void ZoomOut(); + void OnHeaderViewChanged(); void OnSectionResized(int index, int, int); @@ -71,6 +74,7 @@ class GameList final : public QStackedWidget // We only have two views, just use a bool to distinguish. void SetPreferredView(bool list); void ConsiderViewChange(); + void UpdateFont(); GameListModel* m_model; QSortFilterProxyModel* m_list_proxy; diff --git a/Source/Core/DolphinQt/GameList/GameListModel.cpp b/Source/Core/DolphinQt/GameList/GameListModel.cpp index c2367bb98515..890140340d20 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.cpp +++ b/Source/Core/DolphinQt/GameList/GameListModel.cpp @@ -283,3 +283,13 @@ void GameListModel::SetSearchTerm(const QString& term) { m_term = term; } + +void GameListModel::SetScale(float scale) +{ + m_scale = scale; +} + +float GameListModel::GetScale() const +{ + return m_scale; +} diff --git a/Source/Core/DolphinQt/GameList/GameListModel.h b/Source/Core/DolphinQt/GameList/GameListModel.h index 62116dd314d3..99cc28e080b2 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.h +++ b/Source/Core/DolphinQt/GameList/GameListModel.h @@ -59,6 +59,9 @@ class GameListModel final : public QAbstractTableModel void UpdateGame(const std::shared_ptr& game); void RemoveGame(const std::string& path); + void SetScale(float scale); + float GetScale() const; + private: // Index in m_games, or -1 if it isn't found int FindGame(const std::string& path) const; @@ -67,4 +70,5 @@ class GameListModel final : public QAbstractTableModel QList> m_games; Core::TitleDatabase m_title_database; QString m_term; + float m_scale = 1.0; }; diff --git a/Source/Core/DolphinQt/GameList/GameTracker.cpp b/Source/Core/DolphinQt/GameList/GameTracker.cpp index 7d87ae746940..056bcf0e66eb 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt/GameList/GameTracker.cpp @@ -44,6 +44,10 @@ GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent) } }); + connect(&Settings::Instance(), &Settings::MetadataRefreshRequested, this, [this] { + m_load_thread.EmplaceItem(Command{CommandType::UpdateMetadata, {}}); + }); + m_load_thread.Reset([this](Command command) { switch (command.type) { @@ -64,6 +68,13 @@ GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent) case CommandType::UpdateFile: UpdateFileInternal(command.path); break; + case CommandType::UpdateMetadata: + m_cache.UpdateAdditionalMetadata( + [this](const std::shared_ptr& game) { + emit GameUpdated(game); + }); + QueueOnObject(this, [this] { Settings::Instance().NotifyMetadataRefreshComplete(); }); + break; } }); @@ -121,6 +132,8 @@ void GameTracker::StartInternal() cache_updated |= m_cache.UpdateAdditionalMetadata(emit_game_updated); if (cache_updated) m_cache.Save(); + + QueueOnObject(this, [this] { Settings::Instance().NotifyMetadataRefreshComplete(); }); } bool GameTracker::AddPath(const QString& dir) diff --git a/Source/Core/DolphinQt/GameList/GameTracker.h b/Source/Core/DolphinQt/GameList/GameTracker.h index db41cf3ef436..d212d983a203 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.h +++ b/Source/Core/DolphinQt/GameList/GameTracker.h @@ -70,6 +70,7 @@ class GameTracker final : public QFileSystemWatcher RemoveDirectory, UpdateDirectory, UpdateFile, + UpdateMetadata }; struct Command diff --git a/Source/Core/DolphinQt/GameList/GridProxyModel.cpp b/Source/Core/DolphinQt/GameList/GridProxyModel.cpp index 516357ac762a..36df8306c085 100644 --- a/Source/Core/DolphinQt/GameList/GridProxyModel.cpp +++ b/Source/Core/DolphinQt/GameList/GridProxyModel.cpp @@ -4,11 +4,16 @@ #include "DolphinQt/GameList/GridProxyModel.h" +#include #include #include #include "DolphinQt/GameList/GameListModel.h" +#include "Core/Config/UISettings.h" + +#include "UICommon/GameFile.h" + const QSize LARGE_BANNER_SIZE(144, 48); GridProxyModel::GridProxyModel(QObject* parent) : QSortFilterProxyModel(parent) @@ -27,12 +32,30 @@ QVariant GridProxyModel::data(const QModelIndex& i, int role) const } else if (role == Qt::DecorationRole) { - auto pixmap = sourceModel() - ->data(sourceModel()->index(source_index.row(), GameListModel::COL_BANNER), - Qt::DecorationRole) - .value(); - return pixmap.scaled(LARGE_BANNER_SIZE * pixmap.devicePixelRatio(), Qt::KeepAspectRatio, - Qt::SmoothTransformation); + auto* model = static_cast(sourceModel()); + + const auto& buffer = model->GetGameFile(source_index.row())->GetCoverImage().buffer; + + QPixmap pixmap; + + if (buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) + { + pixmap = model + ->data(model->index(source_index.row(), GameListModel::COL_BANNER), + Qt::DecorationRole) + .value(); + + return pixmap.scaled(LARGE_BANNER_SIZE * model->GetScale() * pixmap.devicePixelRatio(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + else + { + pixmap = QPixmap::fromImage(QImage::fromData( + reinterpret_cast(&buffer[0]), static_cast(buffer.size()))); + + return pixmap.scaled(QSize(160, 224) * model->GetScale() * pixmap.devicePixelRatio(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + } } return QVariant(); } diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index c1dce6b29d96..a8ffc4139664 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -137,6 +137,16 @@ void Settings::RefreshGameList() emit GameListRefreshRequested(); } +void Settings::RefreshMetadata() +{ + emit MetadataRefreshRequested(); +} + +void Settings::NotifyMetadataRefreshComplete() +{ + emit MetadataRefreshCompleted(); +} + void Settings::ReloadTitleDB() { emit TitleDBReloadRequested(); diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 68303530fa78..4d98acb532df 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -75,6 +75,8 @@ class Settings final : public QObject QString GetDefaultGame() const; void SetDefaultGame(QString path); void RefreshGameList(); + void RefreshMetadata(); + void NotifyMetadataRefreshComplete(); void ReloadTitleDB(); bool IsAutoRefreshEnabled() const; void SetAutoRefreshEnabled(bool enabled); @@ -144,6 +146,8 @@ class Settings final : public QObject void DefaultGameChanged(const QString&); void GameListRefreshRequested(); void TitleDBReloadRequested(); + void MetadataRefreshRequested(); + void MetadataRefreshCompleted(); void AutoRefreshToggled(bool enabled); void HideCursorChanged(); void KeepWindowOnTopChanged(bool top); diff --git a/Source/Core/DolphinQt/Settings/InterfacePane.cpp b/Source/Core/DolphinQt/Settings/InterfacePane.cpp index af1225f90ab1..ad6f66e8423b 100644 --- a/Source/Core/DolphinQt/Settings/InterfacePane.cpp +++ b/Source/Core/DolphinQt/Settings/InterfacePane.cpp @@ -19,10 +19,14 @@ #include "Common/MsgHandler.h" #include "Common/StringUtil.h" +#include "Core/Config/UISettings.h" #include "Core/ConfigManager.h" +#include "DolphinQt/GameList/GameListModel.h" #include "DolphinQt/Settings.h" +#include "UICommon/GameFile.h" + static QComboBox* MakeLanguageComboBox() { static const struct @@ -144,11 +148,14 @@ void InterfacePane::CreateUI() m_checkbox_top_window = new QCheckBox(tr("Keep Window on Top")); m_checkbox_use_builtin_title_database = new QCheckBox(tr("Use Built-In Database of Game Names")); m_checkbox_use_userstyle = new QCheckBox(tr("Use Custom User Style")); + m_checkbox_use_covers = + new QCheckBox(tr("Download Game Covers from GameTDB.com for Use in Grid Mode")); m_checkbox_show_debugging_ui = new QCheckBox(tr("Show Debugging UI")); groupbox_layout->addWidget(m_checkbox_top_window); groupbox_layout->addWidget(m_checkbox_use_builtin_title_database); groupbox_layout->addWidget(m_checkbox_use_userstyle); + groupbox_layout->addWidget(m_checkbox_use_covers); groupbox_layout->addWidget(m_checkbox_show_debugging_ui); } @@ -179,6 +186,7 @@ void InterfacePane::ConnectLayout() connect(m_checkbox_top_window, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_use_builtin_title_database, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); + connect(m_checkbox_use_covers, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_show_debugging_ui, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_combobox_theme, static_cast(&QComboBox::currentIndexChanged), @@ -229,6 +237,7 @@ void InterfacePane::LoadConfig() m_checkbox_enable_osd->setChecked(startup_params.bOnScreenDisplayMessages); m_checkbox_show_active_title->setChecked(startup_params.m_show_active_title); m_checkbox_pause_on_focus_lost->setChecked(startup_params.m_PauseOnFocusLost); + m_checkbox_use_covers->setChecked(Config::Get(Config::MAIN_USE_GAME_COVERS)); m_checkbox_hide_mouse->setChecked(Settings::Instance().GetHideCursor()); } @@ -264,5 +273,13 @@ void InterfacePane::OnSaveConfig() tr("You must restart Dolphin in order for the change to take effect.")); } + const bool use_covers = m_checkbox_use_covers->isChecked(); + + if (use_covers != Config::Get(Config::MAIN_USE_GAME_COVERS)) + { + Config::SetBase(Config::MAIN_USE_GAME_COVERS, use_covers); + Settings::Instance().RefreshMetadata(); + } + settings.SaveSettings(); } diff --git a/Source/Core/DolphinQt/Settings/InterfacePane.h b/Source/Core/DolphinQt/Settings/InterfacePane.h index e3c2a707bd5e..44ed9df96e94 100644 --- a/Source/Core/DolphinQt/Settings/InterfacePane.h +++ b/Source/Core/DolphinQt/Settings/InterfacePane.h @@ -35,6 +35,7 @@ class InterfacePane final : public QWidget QCheckBox* m_checkbox_use_builtin_title_database; QCheckBox* m_checkbox_use_userstyle; QCheckBox* m_checkbox_show_debugging_ui; + QCheckBox* m_checkbox_use_covers; QCheckBox* m_checkbox_confirm_on_stop; QCheckBox* m_checkbox_use_panic_handlers; diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index af8421e5d910..2177bc992271 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -23,6 +23,7 @@ #include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Hash.h" +#include "Common/HttpRequest.h" #include "Common/Image.h" #include "Common/IniFile.h" #include "Common/NandPaths.h" @@ -30,6 +31,7 @@ #include "Common/Swap.h" #include "Core/Boot/Boot.h" +#include "Core/Config/UISettings.h" #include "Core/ConfigManager.h" #include "Core/IOS/ES/Formats.h" #include "Core/TitleDatabase.h" @@ -39,6 +41,8 @@ #include "DiscIO/Volume.h" #include "DiscIO/WiiSaveBanner.h" +constexpr const char* COVER_URL = "https://art.gametdb.com/wii/cover/%s/%s.png"; + namespace UICommon { static const std::string EMPTY_STRING; @@ -149,6 +153,144 @@ bool GameFile::IsValid() const return true; } +bool GameFile::CustomCoverChanged() +{ + if (!m_custom_cover.buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) + return false; + + std::string path, name; + SplitPath(m_file_path, &path, &name, nullptr); + + std::string contents; + + // This icon naming format is intended as an alternative to Homebrew Channel icons + // for those who don't want to have a Homebrew Channel style folder structure. + bool success = File::Exists(path + name + ".cover.png") && + File::ReadFileToString(path + name + ".cover.png", contents); + + if (!success) + { + success = + File::Exists(path + "cover.png") && File::ReadFileToString(path + "cover.png", contents); + } + + if (success) + m_pending.custom_cover.buffer = {contents.begin(), contents.end()}; + + return success; +} + +void GameFile::DownloadDefaultCover() +{ +#ifndef ANDROID + if (!m_default_cover.buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) + return; + + const auto cover_path = File::GetUserPath(D_COVERCACHE_IDX) + DIR_SEP; + + // If covers have already been downloaded, abort + if (File::Exists(cover_path + m_game_id + ".png") || + File::Exists(cover_path + m_game_id.substr(0, 4) + ".png")) + return; + + Common::HttpRequest request; + + std::string region_code; + + auto user_lang = SConfig::GetInstance().GetCurrentLanguage(DiscIO::IsWii(GetPlatform())); + + switch (m_region) + { + case DiscIO::Region::NTSC_J: + region_code = "JA"; + break; + case DiscIO::Region::NTSC_U: + region_code = "US"; + break; + case DiscIO::Region::NTSC_K: + region_code = "KO"; + break; + case DiscIO::Region::PAL: + switch (user_lang) + { + case DiscIO::Language::German: + region_code = "DE"; + break; + case DiscIO::Language::French: + region_code = "FR"; + break; + case DiscIO::Language::Spanish: + region_code = "ES"; + break; + case DiscIO::Language::Italian: + region_code = "IT"; + break; + case DiscIO::Language::Dutch: + region_code = "NL"; + break; + case DiscIO::Language::English: + default: + region_code = "EN"; + break; + } + break; + case DiscIO::Region::Unknown: + region_code = "EN"; + break; + } + + auto response = request.Get(StringFromFormat(COVER_URL, region_code.c_str(), m_game_id.c_str())); + + if (response) + { + File::WriteStringToFile(std::string(response.value().begin(), response.value().end()), + cover_path + m_game_id + ".png"); + return; + } + + response = + request.Get(StringFromFormat(COVER_URL, region_code.c_str(), m_game_id.substr(0, 4).c_str())); + + if (response) + { + File::WriteStringToFile(std::string(response.value().begin(), response.value().end()), + cover_path + m_game_id.substr(0, 4) + ".png"); + } +#endif +} + +bool GameFile::DefaultCoverChanged() +{ + if (!m_default_cover.buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) + return false; + + const auto cover_path = File::GetUserPath(D_COVERCACHE_IDX) + DIR_SEP; + + std::string contents; + + File::ReadFileToString(cover_path + m_game_id + ".png", contents); + + if (contents.empty()) + File::ReadFileToString(cover_path + m_game_id.substr(0, 4).c_str() + ".png", contents); + + if (contents.empty()) + return false; + + m_pending.default_cover.buffer = {contents.begin(), contents.end()}; + + return true; +} + +void GameFile::CustomCoverCommit() +{ + m_custom_cover = std::move(m_pending.custom_cover); +} + +void GameFile::DefaultCoverCommit() +{ + m_default_cover = std::move(m_pending.default_cover); +} + void GameBanner::DoState(PointerWrap& p) { p.Do(buffer); @@ -156,6 +298,11 @@ void GameBanner::DoState(PointerWrap& p) p.Do(height); } +void GameCover::DoState(PointerWrap& p) +{ + p.Do(buffer); +} + void GameFile::DoState(PointerWrap& p) { p.Do(m_valid); @@ -185,6 +332,8 @@ void GameFile::DoState(PointerWrap& p) m_volume_banner.DoState(p); m_custom_banner.DoState(p); + m_default_cover.DoState(p); + m_custom_cover.DoState(p); } bool GameFile::IsElfOrDol() const @@ -353,4 +502,9 @@ const GameBanner& GameFile::GetBannerImage() const return m_custom_banner.empty() ? m_volume_banner : m_custom_banner; } +const GameCover& GameFile::GetCoverImage() const +{ + return m_custom_cover.empty() ? m_default_cover : m_custom_cover; +} + } // namespace UICommon diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h index 60d874a375c0..e5b4257db661 100644 --- a/Source/Core/UICommon/GameFile.h +++ b/Source/Core/UICommon/GameFile.h @@ -30,6 +30,13 @@ struct GameBanner void DoState(PointerWrap& p); }; +struct GameCover +{ + std::vector buffer{}; + bool empty() const { return buffer.empty(); } + void DoState(PointerWrap& p); +}; + bool operator==(const GameBanner& lhs, const GameBanner& rhs); bool operator!=(const GameBanner& lhs, const GameBanner& rhs); @@ -75,11 +82,17 @@ class GameFile final u64 GetFileSize() const { return m_file_size; } u64 GetVolumeSize() const { return m_volume_size; } const GameBanner& GetBannerImage() const; + const GameCover& GetCoverImage() const; void DoState(PointerWrap& p); bool WiiBannerChanged(); void WiiBannerCommit(); bool CustomBannerChanged(); void CustomBannerCommit(); + void DownloadDefaultCover(); + bool DefaultCoverChanged(); + void DefaultCoverCommit(); + bool CustomCoverChanged(); + void CustomCoverCommit(); private: static const std::string& Lookup(DiscIO::Language language, @@ -120,6 +133,8 @@ class GameFile final GameBanner m_volume_banner{}; GameBanner m_custom_banner{}; + GameCover m_default_cover{}; + GameCover m_custom_cover{}; // The following data members allow GameFileCache to construct updated versions // of GameFiles in a threadsafe way. They should not be handled in DoState. @@ -127,6 +142,8 @@ class GameFile final { GameBanner volume_banner; GameBanner custom_banner; + GameCover default_cover; + GameCover custom_cover; } m_pending{}; }; diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index 074dc7fe6211..82ade8e964c7 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -27,7 +27,7 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 11; // Last changed in PR 7058 +static constexpr u32 CACHE_REVISION = 12; // Last changed in PR 7285 std::vector FindAllGamePaths(const std::vector& directories_to_scan, bool recursive_scan) @@ -165,7 +165,14 @@ bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr* game_fil { const bool wii_banner_changed = (*game_file)->WiiBannerChanged(); const bool custom_banner_changed = (*game_file)->CustomBannerChanged(); - if (!wii_banner_changed && !custom_banner_changed) + + (*game_file)->DownloadDefaultCover(); + + const bool default_cover_changed = (*game_file)->DefaultCoverChanged(); + const bool custom_cover_changed = (*game_file)->CustomCoverChanged(); + + if (!wii_banner_changed && !custom_banner_changed && !default_cover_changed && + !custom_cover_changed) return false; // If a cached file needs an update, apply the updates to a copy and delete the original. @@ -176,6 +183,11 @@ bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr* game_fil copy->WiiBannerCommit(); if (custom_banner_changed) copy->CustomBannerCommit(); + if (default_cover_changed) + copy->DefaultCoverCommit(); + if (custom_cover_changed) + copy->CustomCoverCommit(); + *game_file = std::move(copy); return true; diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index c84d4af04b7f..2055c4d863af 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -158,6 +158,7 @@ void CreateDirectories() { File::CreateFullPath(File::GetUserPath(D_USER_IDX)); File::CreateFullPath(File::GetUserPath(D_CACHE_IDX)); + File::CreateFullPath(File::GetUserPath(D_COVERCACHE_IDX)); File::CreateFullPath(File::GetUserPath(D_CONFIG_IDX)); File::CreateFullPath(File::GetUserPath(D_DUMPDSP_IDX)); File::CreateFullPath(File::GetUserPath(D_DUMPSSL_IDX));