From 1f1dae367d8aeb525df5558ce648d6f287983008 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 31 Dec 2017 20:33:36 +0100 Subject: [PATCH] Unify ISOFile (wx) with GameFile (Qt) and put it in UICommon The original reason I wanted to do this was so that we can replace the Android-specific code with this in the future, but of course, just deduplicating between DolphinWX and DolphinQt2 is nice too. Fixes: - DolphinQt2 showing the wrong size for split WBFS disc images. - DolphinQt2 being case sensitive when checking if a file is a DOL/ELF. - DolphinQt2 not detecting when a Wii banner has become available after the game list cache was created. Removes: - DolphinWX's ability to load PNGs as custom banners. But it was already rather broken (see https://bugs.dolphin-emu.org/issues/10365 and https://bugs.dolphin-emu.org/issues/10366). The reason I removed this was because PNG decoding relied on wx code and we don't have any good non-wx/Qt code for loading PNG files right now (let's not use SOIL), but we should be able to use libpng directly to implement PNG loading in the future. - DolphinQt2's ability to ignore a cached game if the last modified time differs. We currently don't have a non-wx/Qt way to get the time. --- Source/Core/Core/WiiUtils.cpp | 22 + Source/Core/Core/WiiUtils.h | 4 + Source/Core/DiscIO/Blob.h | 2 +- Source/Core/DiscIO/Enums.cpp | 107 +++- Source/Core/DiscIO/Enums.h | 7 +- Source/Core/DolphinQt2/CMakeLists.txt | 3 +- .../Core/DolphinQt2/Config/ARCodeWidget.cpp | 6 +- Source/Core/DolphinQt2/Config/ARCodeWidget.h | 10 +- .../DolphinQt2/Config/FilesystemWidget.cpp | 4 +- .../Core/DolphinQt2/Config/FilesystemWidget.h | 6 +- .../DolphinQt2/Config/GameConfigWidget.cpp | 14 +- .../Core/DolphinQt2/Config/GameConfigWidget.h | 8 +- .../DolphinQt2/Config/GeckoCodeWidget.cpp | 6 +- .../Core/DolphinQt2/Config/GeckoCodeWidget.h | 10 +- Source/Core/DolphinQt2/Config/InfoWidget.cpp | 56 +- Source/Core/DolphinQt2/Config/InfoWidget.h | 15 +- .../Core/DolphinQt2/Config/NewPatchDialog.h | 1 - .../Core/DolphinQt2/Config/PatchesWidget.cpp | 4 +- Source/Core/DolphinQt2/Config/PatchesWidget.h | 6 +- .../DolphinQt2/Config/PropertiesDialog.cpp | 12 +- .../Core/DolphinQt2/Config/PropertiesDialog.h | 7 +- Source/Core/DolphinQt2/DolphinQt2.vcxproj | 6 +- Source/Core/DolphinQt2/GameList/GameFile.cpp | 481 ------------------ Source/Core/DolphinQt2/GameList/GameFile.h | 125 ----- .../DolphinQt2/GameList/GameFileCache.cpp | 76 --- .../Core/DolphinQt2/GameList/GameFileCache.h | 29 -- Source/Core/DolphinQt2/GameList/GameList.cpp | 51 +- Source/Core/DolphinQt2/GameList/GameList.h | 9 +- .../DolphinQt2/GameList/GameListModel.cpp | 78 ++- .../Core/DolphinQt2/GameList/GameListModel.h | 28 +- .../Core/DolphinQt2/GameList/GameTracker.cpp | 32 +- Source/Core/DolphinQt2/GameList/GameTracker.h | 15 +- .../DolphinQt2/GameList/GridProxyModel.cpp | 1 + Source/Core/DolphinQt2/MainWindow.cpp | 13 +- Source/Core/DolphinQt2/MainWindow.h | 2 + Source/Core/DolphinQt2/MenuBar.cpp | 14 +- Source/Core/DolphinQt2/MenuBar.h | 12 +- .../DolphinQt2/NetPlay/GameListDialog.cpp | 2 +- .../Core/DolphinQt2/NetPlay/NetPlayDialog.cpp | 2 +- .../DolphinQt2/NetPlay/NetPlaySetupDialog.cpp | 2 +- .../DolphinQt2/QtUtils/ImageConverter.cpp | 33 ++ .../Core/DolphinQt2/QtUtils/ImageConverter.h | 19 + Source/Core/DolphinQt2/Resources.h | 1 + Source/Core/DolphinWX/CMakeLists.txt | 1 - Source/Core/DolphinWX/DolphinWX.vcxproj | 4 +- .../Core/DolphinWX/DolphinWX.vcxproj.filters | 11 +- Source/Core/DolphinWX/FrameTools.cpp | 19 +- Source/Core/DolphinWX/GameListCtrl.cpp | 317 +++--------- Source/Core/DolphinWX/GameListCtrl.h | 23 +- Source/Core/DolphinWX/ISOFile.cpp | 370 -------------- Source/Core/DolphinWX/ISOFile.h | 140 ----- .../ISOProperties/FilesystemPanel.cpp | 1 - .../DolphinWX/ISOProperties/ISOProperties.cpp | 10 +- .../DolphinWX/ISOProperties/ISOProperties.h | 10 +- .../DolphinWX/ISOProperties/InfoPanel.cpp | 18 +- .../Core/DolphinWX/ISOProperties/InfoPanel.h | 13 +- Source/Core/DolphinWX/NetPlay/NetWindow.cpp | 7 +- Source/Core/DolphinWX/WxUtils.cpp | 23 + Source/Core/DolphinWX/WxUtils.h | 12 + Source/Core/UICommon/CMakeLists.txt | 2 + Source/Core/UICommon/GameFile.cpp | 312 ++++++++++++ Source/Core/UICommon/GameFile.h | 153 ++++++ Source/Core/UICommon/GameFileCache.cpp | 235 +++++++++ Source/Core/UICommon/GameFileCache.h | 58 +++ Source/Core/UICommon/UICommon.cpp | 21 + Source/Core/UICommon/UICommon.h | 4 + Source/Core/UICommon/UICommon.vcxproj | 4 + 67 files changed, 1373 insertions(+), 1736 deletions(-) delete mode 100644 Source/Core/DolphinQt2/GameList/GameFile.cpp delete mode 100644 Source/Core/DolphinQt2/GameList/GameFile.h delete mode 100644 Source/Core/DolphinQt2/GameList/GameFileCache.cpp delete mode 100644 Source/Core/DolphinQt2/GameList/GameFileCache.h create mode 100644 Source/Core/DolphinQt2/QtUtils/ImageConverter.cpp create mode 100644 Source/Core/DolphinQt2/QtUtils/ImageConverter.h delete mode 100644 Source/Core/DolphinWX/ISOFile.cpp delete mode 100644 Source/Core/DolphinWX/ISOFile.h create mode 100644 Source/Core/UICommon/GameFile.cpp create mode 100644 Source/Core/UICommon/GameFile.h create mode 100644 Source/Core/UICommon/GameFileCache.cpp create mode 100644 Source/Core/UICommon/GameFileCache.h diff --git a/Source/Core/Core/WiiUtils.cpp b/Source/Core/Core/WiiUtils.cpp index 3fd653ffde..84ae9a3d20 100644 --- a/Source/Core/Core/WiiUtils.cpp +++ b/Source/Core/Core/WiiUtils.cpp @@ -160,6 +160,28 @@ bool InstallWAD(const std::string& wad_path) return InstallWAD(ios, DiscIO::WiiWAD{wad_path}, InstallType::Permanent); } +bool UninstallTitle(u64 title_id) +{ + IOS::HLE::Kernel ios; + return ios.GetES()->DeleteTitleContent(title_id) == IOS::HLE::IPC_SUCCESS; +} + +bool IsTitleInstalled(u64 title_id) +{ + const std::string content_dir = + Common::GetTitleContentPath(title_id, Common::FromWhichRoot::FROM_CONFIGURED_ROOT); + + if (!File::IsDirectory(content_dir)) + return false; + + // Since this isn't IOS and we only need a simple way to figure out if a title is installed, + // we make the (reasonable) assumption that having more than just the TMD in the content + // directory means that the title is installed. + const auto entries = File::ScanDirectoryTree(content_dir, false); + return std::any_of(entries.children.begin(), entries.children.end(), + [](const auto& file) { return file.virtualName != "title.tmd"; }); +} + // Common functionality for system updaters. class SystemUpdater { diff --git a/Source/Core/Core/WiiUtils.h b/Source/Core/Core/WiiUtils.h index b07863fa3b..8427901f71 100644 --- a/Source/Core/Core/WiiUtils.h +++ b/Source/Core/Core/WiiUtils.h @@ -38,6 +38,10 @@ bool InstallWAD(IOS::HLE::Kernel& ios, const DiscIO::WiiWAD& wad, InstallType ty // and does a permanent install. bool InstallWAD(const std::string& wad_path); +bool UninstallTitle(u64 title_id); + +bool IsTitleInstalled(u64 title_id); + enum class UpdateResult { Succeeded, diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 20107bef0f..703cf1199b 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -25,7 +25,7 @@ namespace DiscIO { -// Increment CACHE_REVISION (GameListCtrl.cpp) if the enum below is modified +// Increment CACHE_REVISION (GameFileCache.cpp) if the enum below is modified enum class BlobType { PLAIN, diff --git a/Source/Core/DiscIO/Enums.cpp b/Source/Core/DiscIO/Enums.cpp index c7b69f5fa1..a91e760e60 100644 --- a/Source/Core/DiscIO/Enums.cpp +++ b/Source/Core/DiscIO/Enums.cpp @@ -5,12 +5,112 @@ #include #include +#include "Common/Assert.h" +#include "Common/Common.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" #include "DiscIO/Enums.h" namespace DiscIO { +std::string GetName(Country country, bool translate) +{ + std::string name; + + switch (country) + { + case Country::COUNTRY_EUROPE: + name = _trans("Europe"); + break; + case Country::COUNTRY_JAPAN: + name = _trans("Japan"); + break; + case Country::COUNTRY_USA: + name = _trans("USA"); + break; + case Country::COUNTRY_AUSTRALIA: + name = _trans("Australia"); + break; + case Country::COUNTRY_FRANCE: + name = _trans("France"); + break; + case Country::COUNTRY_GERMANY: + name = _trans("Germany"); + break; + case Country::COUNTRY_ITALY: + name = _trans("Italy"); + break; + case Country::COUNTRY_KOREA: + name = _trans("Korea"); + break; + case Country::COUNTRY_NETHERLANDS: + name = _trans("Netherlands"); + break; + case Country::COUNTRY_RUSSIA: + name = _trans("Russia"); + break; + case Country::COUNTRY_SPAIN: + name = _trans("Spain"); + break; + case Country::COUNTRY_TAIWAN: + name = _trans("Taiwan"); + break; + case Country::COUNTRY_WORLD: + name = _trans("World"); + break; + default: + name = _trans("Unknown"); + break; + } + + return translate ? GetStringT(name.c_str()) : name; +} + +std::string GetName(Language language, bool translate) +{ + std::string name; + + switch (language) + { + case Language::LANGUAGE_JAPANESE: + name = _trans("Japanese"); + break; + case Language::LANGUAGE_ENGLISH: + name = _trans("English"); + break; + case Language::LANGUAGE_GERMAN: + name = _trans("German"); + break; + case Language::LANGUAGE_FRENCH: + name = _trans("French"); + break; + case Language::LANGUAGE_SPANISH: + name = _trans("Spanish"); + break; + case Language::LANGUAGE_ITALIAN: + name = _trans("Italian"); + break; + case Language::LANGUAGE_DUTCH: + name = _trans("Dutch"); + break; + case Language::LANGUAGE_SIMPLIFIED_CHINESE: + name = _trans("Simplified Chinese"); + break; + case Language::LANGUAGE_TRADITIONAL_CHINESE: + name = _trans("Traditional Chinese"); + break; + case Language::LANGUAGE_KOREAN: + name = _trans("Korean"); + break; + default: + name = _trans("Unknown"); + break; + } + + return translate ? GetStringT(name.c_str()) : name; +} + bool IsDisc(Platform volume_type) { return volume_type == Platform::GAMECUBE_DISC || volume_type == Platform::WII_DISC; @@ -26,7 +126,7 @@ bool IsNTSC(Region region) return region == Region::NTSC_J || region == Region::NTSC_U || region == Region::NTSC_K; } -// Increment CACHE_REVISION (GameListCtrl.cpp) if the code below is modified +// Increment CACHE_REVISION (GameFileCache.cpp) if the code below is modified Country TypicalCountryForRegion(Region region) { @@ -230,7 +330,7 @@ std::string GetSysMenuVersionString(u16 title_version) } } -std::string GetCompanyFromID(const std::string& company_id) +const std::string& GetCompanyFromID(const std::string& company_id) { static const std::map companies = { {"01", "Nintendo"}, @@ -517,10 +617,11 @@ std::string GetCompanyFromID(const std::string& company_id) {"ZW", "Judo Baby"}, {"ZX", "TopWare Interactive"}}; + static const std::string EMPTY_STRING; auto iterator = companies.find(company_id); if (iterator != companies.end()) return iterator->second; else - return ""; + return EMPTY_STRING; } } diff --git a/Source/Core/DiscIO/Enums.h b/Source/Core/DiscIO/Enums.h index fda94c43be..eefeb8dfac 100644 --- a/Source/Core/DiscIO/Enums.h +++ b/Source/Core/DiscIO/Enums.h @@ -10,7 +10,7 @@ namespace DiscIO { -// Increment CACHE_REVISION (GameListCtrl.cpp) if these enums are modified +// Increment CACHE_REVISION (GameFileCache.cpp) if these enums are modified enum class Platform { @@ -68,6 +68,9 @@ enum class Language LANGUAGE_UNKNOWN }; +std::string GetName(Country country, bool translate); +std::string GetName(Language language, bool translate); + bool IsDisc(Platform volume_type); bool IsWii(Platform volume_type); bool IsNTSC(Region region); @@ -82,5 +85,5 @@ Country CountrySwitch(u8 country_code); Region GetSysMenuRegion(u16 title_version); std::string GetSysMenuVersionString(u16 title_version); -std::string GetCompanyFromID(const std::string& company_id); +const std::string& GetCompanyFromID(const std::string& company_id); } diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 36dfa6d599..352ee72610 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -82,8 +82,6 @@ set(SRCS Debugger/RegisterColumn.cpp Debugger/RegisterWidget.cpp Debugger/WatchWidget.cpp - GameList/GameFileCache.cpp - GameList/GameFile.cpp GameList/GameList.cpp GameList/GameListModel.cpp GameList/GameTracker.cpp @@ -98,6 +96,7 @@ set(SRCS NetPlay/PadMappingDialog.cpp QtUtils/DoubleClickEventFilter.cpp QtUtils/ElidedButton.cpp + QtUtils/ImageConverter.cpp QtUtils/ListTabWidget.cpp QtUtils/WindowActivationEventFilter.cpp QtUtils/AspectRatioWidget.cpp diff --git a/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp b/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp index c68fc0c19c..9bb29338e2 100644 --- a/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp +++ b/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp @@ -16,10 +16,10 @@ #include "Core/ConfigManager.h" #include "DolphinQt2/Config/CheatCodeEditor.h" #include "DolphinQt2/Config/CheatWarningWidget.h" -#include "DolphinQt2/GameList/GameFile.h" +#include "UICommon/GameFile.h" -ARCodeWidget::ARCodeWidget(const GameFile& game) - : m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision()) +ARCodeWidget::ARCodeWidget(const UICommon::GameFile& game) + : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()) { CreateWidgets(); ConnectWidgets(); diff --git a/Source/Core/DolphinQt2/Config/ARCodeWidget.h b/Source/Core/DolphinQt2/Config/ARCodeWidget.h index 7b3154ead1..8fda60976f 100644 --- a/Source/Core/DolphinQt2/Config/ARCodeWidget.h +++ b/Source/Core/DolphinQt2/Config/ARCodeWidget.h @@ -12,8 +12,12 @@ #include "Common/CommonTypes.h" #include "Core/ActionReplay.h" -class CheatWarningWidget; +namespace UICommon +{ class GameFile; +} + +class CheatWarningWidget; class QLabel; class QListWidget; class QListWidgetItem; @@ -23,7 +27,7 @@ class ARCodeWidget : public QWidget { Q_OBJECT public: - explicit ARCodeWidget(const GameFile& game); + explicit ARCodeWidget(const UICommon::GameFile& game); signals: void OpenGeneralSettings(); @@ -41,7 +45,7 @@ private: void OnCodeEditPressed(); void OnCodeRemovePressed(); - const GameFile& m_game; + const UICommon::GameFile& m_game; std::string m_game_id; u16 m_game_revision; diff --git a/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp b/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp index ccbb2b28b1..73ff255fb3 100644 --- a/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp +++ b/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp @@ -37,8 +37,8 @@ enum class EntryType }; Q_DECLARE_METATYPE(EntryType); -FilesystemWidget::FilesystemWidget(const GameFile& game) - : m_game(game), m_volume(DiscIO::CreateVolumeFromFilename(game.GetFilePath().toStdString())) +FilesystemWidget::FilesystemWidget(const UICommon::GameFile& game) + : m_game(game), m_volume(DiscIO::CreateVolumeFromFilename(game.GetFilePath())) { CreateWidgets(); ConnectWidgets(); diff --git a/Source/Core/DolphinQt2/Config/FilesystemWidget.h b/Source/Core/DolphinQt2/Config/FilesystemWidget.h index 88fdf16ce0..820168c8b5 100644 --- a/Source/Core/DolphinQt2/Config/FilesystemWidget.h +++ b/Source/Core/DolphinQt2/Config/FilesystemWidget.h @@ -8,7 +8,7 @@ #include #include "DiscIO/Volume.h" -#include "DolphinQt2/GameList/GameFile.h" +#include "UICommon/GameFile.h" class QStandardItem; class QStandardItemModel; @@ -24,7 +24,7 @@ class FilesystemWidget final : public QWidget { Q_OBJECT public: - explicit FilesystemWidget(const GameFile& game); + explicit FilesystemWidget(const UICommon::GameFile& game); private: void CreateWidgets(); @@ -46,6 +46,6 @@ private: QStandardItemModel* m_tree_model; QTreeView* m_tree_view; - GameFile m_game; + UICommon::GameFile m_game; std::unique_ptr m_volume; }; diff --git a/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp b/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp index 2421d056f9..ca0a64f068 100644 --- a/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp +++ b/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp @@ -23,7 +23,7 @@ #include "Core/ConfigLoaders/GameConfigLoader.h" #include "Core/ConfigManager.h" #include "DolphinQt2/Config/Graphics/GraphicsSlider.h" -#include "DolphinQt2/GameList/GameFile.h" +#include "UICommon/GameFile.h" constexpr int DETERMINISM_NOT_SET_INDEX = 0; constexpr int DETERMINISM_AUTO_INDEX = 1; @@ -35,9 +35,9 @@ constexpr const char* DETERMINISM_AUTO_STRING = "auto"; constexpr const char* DETERMINISM_NONE_STRING = "none"; constexpr const char* DETERMINISM_FAKE_COMPLETION_STRING = "fake-completion"; -GameConfigWidget::GameConfigWidget(const GameFile& game) : m_game(game) +GameConfigWidget::GameConfigWidget(const UICommon::GameFile& game) : m_game(game) { - m_game_id = m_game.GetGameID().toStdString(); + m_game_id = m_game.GetGameID(); m_gameini_local_path = QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); m_gameini_local = SConfig::LoadLocalGameIni(m_game_id, m_game.GetRevision()); @@ -241,8 +241,8 @@ void GameConfigWidget::SaveCheckBox(QCheckBox* checkbox, const std::string& sect void GameConfigWidget::LoadSettings() { // Load state information - m_state_combo->setCurrentIndex(m_game.GetRating()); - m_state_comment_edit->setText(m_game.GetIssues()); + m_state_combo->setCurrentIndex(m_game.GetEmuState()); + m_state_comment_edit->setText(QString::fromStdString(m_game.GetIssues())); // Load game-specific settings @@ -301,10 +301,10 @@ void GameConfigWidget::SaveSettings() QString comment = m_state_comment_edit->text(); int state = m_state_combo->currentIndex(); - if (comment != m_game.GetIssues()) + if (comment != QString::fromStdString(m_game.GetIssues())) m_gameini_local.GetOrCreateSection("EmuState")->Set("EmulationIssues", comment.toStdString()); - if (state != m_game.GetRating()) + if (state != m_game.GetEmuState()) m_gameini_local.GetOrCreateSection("EmuState")->Set("EmulationStateId", state); // Save game-specific settings diff --git a/Source/Core/DolphinQt2/Config/GameConfigWidget.h b/Source/Core/DolphinQt2/Config/GameConfigWidget.h index 2062d55224..d33c15122e 100644 --- a/Source/Core/DolphinQt2/Config/GameConfigWidget.h +++ b/Source/Core/DolphinQt2/Config/GameConfigWidget.h @@ -11,7 +11,11 @@ #include "Common/IniFile.h" +namespace UICommon +{ class GameFile; +} + class QCheckBox; class QComboBox; class QGroupBox; @@ -25,7 +29,7 @@ class GameConfigWidget : public QWidget { Q_OBJECT public: - explicit GameConfigWidget(const GameFile& game); + explicit GameConfigWidget(const UICommon::GameFile& game); private: void CreateWidgets(); @@ -65,6 +69,6 @@ private: IniFile m_gameini_local; IniFile m_gameini_default; - const GameFile& m_game; + const UICommon::GameFile& m_game; std::string m_game_id; }; diff --git a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp index 92f2c03af2..79fbc1a089 100644 --- a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp +++ b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp @@ -20,10 +20,10 @@ #include "Core/GeckoCodeConfig.h" #include "DolphinQt2/Config/CheatCodeEditor.h" #include "DolphinQt2/Config/CheatWarningWidget.h" -#include "DolphinQt2/GameList/GameFile.h" +#include "UICommon/GameFile.h" -GeckoCodeWidget::GeckoCodeWidget(const GameFile& game) - : m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision()) +GeckoCodeWidget::GeckoCodeWidget(const UICommon::GameFile& game) + : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()) { CreateWidgets(); ConnectWidgets(); diff --git a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h index 5d68632967..cf885d0e25 100644 --- a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h +++ b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h @@ -13,18 +13,22 @@ #include "Core/GeckoCode.h" class CheatWarningWidget; -class GameFile; class QLabel; class QListWidget; class QListWidgetItem; class QTextEdit; class QPushButton; +namespace UICommon +{ +class GameFile; +} + class GeckoCodeWidget : public QWidget { Q_OBJECT public: - explicit GeckoCodeWidget(const GameFile& game); + explicit GeckoCodeWidget(const UICommon::GameFile& game); signals: void OpenGeneralSettings(); @@ -43,7 +47,7 @@ private: void DownloadCodes(); void SaveCodes(); - const GameFile& m_game; + const UICommon::GameFile& m_game; std::string m_game_id; u16 m_game_revision; diff --git a/Source/Core/DolphinQt2/Config/InfoWidget.cpp b/Source/Core/DolphinQt2/Config/InfoWidget.cpp index bbf8aa4867..7c4661004b 100644 --- a/Source/Core/DolphinQt2/Config/InfoWidget.cpp +++ b/Source/Core/DolphinQt2/Config/InfoWidget.cpp @@ -16,8 +16,10 @@ #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" #include "DolphinQt2/Config/InfoWidget.h" +#include "DolphinQt2/QtUtils/ImageConverter.h" +#include "UICommon/UICommon.h" -InfoWidget::InfoWidget(const GameFile& game) : m_game(game) +InfoWidget::InfoWidget(const UICommon::GameFile& game) : m_game(game) { QVBoxLayout* layout = new QVBoxLayout(); layout->addWidget(CreateISODetails()); @@ -32,17 +34,19 @@ QGroupBox* InfoWidget::CreateISODetails() QLineEdit* file_path = CreateValueDisplay(m_game.GetFilePath()); QLineEdit* internal_name = CreateValueDisplay(m_game.GetInternalName()); - QString game_id_string = m_game.GetGameID(); + + QString game_id_string = QString::fromStdString(m_game.GetGameID()); if (const u64 title_id = m_game.GetTitleID()) game_id_string += QStringLiteral(" (%1)").arg(title_id, 16, 16, QLatin1Char('0')); QLineEdit* game_id = CreateValueDisplay(game_id_string); - QLineEdit* country = CreateValueDisplay(m_game.GetCountry()); + + QLineEdit* country = CreateValueDisplay(DiscIO::GetName(m_game.GetCountry(), true)); QLineEdit* maker = CreateValueDisplay(m_game.GetMaker()); - QLineEdit* maker_id = CreateValueDisplay(QStringLiteral("0x") + m_game.GetMakerID()); + QLineEdit* maker_id = CreateValueDisplay("0x" + m_game.GetMakerID()); QLineEdit* disc_number = CreateValueDisplay(QString::number(m_game.GetDiscNumber())); QLineEdit* revision = CreateValueDisplay(QString::number(m_game.GetRevision())); QLineEdit* apploader_date = CreateValueDisplay(m_game.GetApploaderDate()); - QLineEdit* iso_size = CreateValueDisplay(FormatSize(m_game.GetFileSize())); + QLineEdit* iso_size = CreateValueDisplay(UICommon::FormatSize(m_game.GetFileSize())); QWidget* checksum = CreateChecksumComputer(); layout->addRow(tr("File Path:"), file_path); @@ -75,7 +79,7 @@ QGroupBox* InfoWidget::CreateBannerDetails() CreateLanguageSelector(); layout->addRow(tr("Show Language:"), m_language_selector); - if (m_game.GetPlatformID() == DiscIO::Platform::GAMECUBE_DISC) + if (m_game.GetPlatform() == DiscIO::Platform::GAMECUBE_DISC) { layout->addRow(tr("Short Name:"), m_short_name); layout->addRow(tr("Short Maker:"), m_short_maker); @@ -83,27 +87,26 @@ QGroupBox* InfoWidget::CreateBannerDetails() layout->addRow(tr("Long Maker:"), m_long_maker); layout->addRow(tr("Description:"), m_description); } - else if (DiscIO::IsWii(m_game.GetPlatformID())) + else if (DiscIO::IsWii(m_game.GetPlatform())) { layout->addRow(tr("Name:"), m_long_name); } - if (!m_game.GetBanner().isNull()) - { - layout->addRow(tr("Banner:"), CreateBannerGraphic()); - } + QPixmap banner = ToQPixmap(m_game.GetBannerImage()); + if (!banner.isNull()) + layout->addRow(tr("Banner:"), CreateBannerGraphic(banner)); group->setLayout(layout); return group; } -QWidget* InfoWidget::CreateBannerGraphic() +QWidget* InfoWidget::CreateBannerGraphic(const QPixmap& image) { QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(); QLabel* banner = new QLabel(); - banner->setPixmap(m_game.GetBanner()); + banner->setPixmap(image); QPushButton* save = new QPushButton(tr("Save as...")); connect(save, &QPushButton::clicked, this, &InfoWidget::SaveBanner); @@ -117,7 +120,7 @@ void InfoWidget::SaveBanner() { QString path = QFileDialog::getSaveFileName(this, tr("Select a File"), QDir::currentPath(), tr("PNG image file (*.png);; All Files (*)")); - m_game.GetBanner().save(path, "PNG"); + ToQPixmap(m_game.GetBannerImage()).save(path, "PNG"); } QLineEdit* InfoWidget::CreateValueDisplay(const QString& value) @@ -128,14 +131,18 @@ QLineEdit* InfoWidget::CreateValueDisplay(const QString& value) return value_display; } +QLineEdit* InfoWidget::CreateValueDisplay(const std::string& value) +{ + return CreateValueDisplay(QString::fromStdString(value)); +} + void InfoWidget::CreateLanguageSelector() { m_language_selector = new QComboBox(); - QList languages = m_game.GetAvailableLanguages(); - for (int i = 0; i < languages.count(); i++) + for (DiscIO::Language language : m_game.GetLanguages()) { - DiscIO::Language language = languages.at(i); - m_language_selector->addItem(m_game.GetLanguage(language), static_cast(language)); + m_language_selector->addItem(QString::fromStdString(DiscIO::GetName(language, true)), + static_cast(language)); } if (m_language_selector->count() == 1) m_language_selector->setDisabled(true); @@ -149,11 +156,11 @@ void InfoWidget::ChangeLanguage() { DiscIO::Language language = static_cast(m_language_selector->currentData().toInt()); - m_short_name->setText(m_game.GetShortName(language)); - m_short_maker->setText(m_game.GetShortMaker(language)); - m_long_name->setText(m_game.GetLongName(language)); - m_long_maker->setText(m_game.GetLongMaker(language)); - m_description->setText(m_game.GetDescription(language)); + m_short_name->setText(QString::fromStdString(m_game.GetShortName(language))); + m_short_maker->setText(QString::fromStdString(m_game.GetShortMaker(language))); + m_long_name->setText(QString::fromStdString(m_game.GetLongName(language))); + m_long_maker->setText(QString::fromStdString(m_game.GetLongMaker(language))); + m_description->setText(QString::fromStdString(m_game.GetDescription(language))); } QWidget* InfoWidget::CreateChecksumComputer() @@ -176,8 +183,7 @@ void InfoWidget::ComputeChecksum() { QCryptographicHash hash(QCryptographicHash::Md5); hash.reset(); - std::unique_ptr file( - DiscIO::CreateBlobReader(m_game.GetFilePath().toStdString())); + std::unique_ptr file(DiscIO::CreateBlobReader(m_game.GetFilePath())); std::vector file_data(8 * 1080 * 1080); // read 1MB at a time u64 game_size = file->GetDataSize(); u64 read_offset = 0; diff --git a/Source/Core/DolphinQt2/Config/InfoWidget.h b/Source/Core/DolphinQt2/Config/InfoWidget.h index 45e8aee05c..26e456e513 100644 --- a/Source/Core/DolphinQt2/Config/InfoWidget.h +++ b/Source/Core/DolphinQt2/Config/InfoWidget.h @@ -4,20 +4,23 @@ #pragma once +#include + #include -#include "DolphinQt2/GameList/GameFile.h" +#include "UICommon/GameFile.h" class QComboBox; class QGroupBox; -class QTextEdit; class QLineEdit; +class QPixmap; +class QTextEdit; class InfoWidget final : public QWidget { Q_OBJECT public: - explicit InfoWidget(const GameFile& game); + explicit InfoWidget(const UICommon::GameFile& game); private: void ComputeChecksum(); @@ -26,13 +29,13 @@ private: QGroupBox* CreateBannerDetails(); QGroupBox* CreateISODetails(); - QLineEdit* CreateValueDisplay() { return CreateValueDisplay(QStringLiteral("")); }; QLineEdit* CreateValueDisplay(const QString& value); + QLineEdit* CreateValueDisplay(const std::string& value = ""); QWidget* CreateChecksumComputer(); void CreateLanguageSelector(); - QWidget* CreateBannerGraphic(); + QWidget* CreateBannerGraphic(const QPixmap& image); - GameFile m_game; + UICommon::GameFile m_game; QLineEdit* m_checksum_result; QComboBox* m_language_selector; QLineEdit* m_long_name; diff --git a/Source/Core/DolphinQt2/Config/NewPatchDialog.h b/Source/Core/DolphinQt2/Config/NewPatchDialog.h index 6419545daf..c3e132efb8 100644 --- a/Source/Core/DolphinQt2/Config/NewPatchDialog.h +++ b/Source/Core/DolphinQt2/Config/NewPatchDialog.h @@ -10,7 +10,6 @@ #include #include "Core/PatchEngine.h" -#include "DolphinQt2/GameList/GameFile.h" class QDialogButtonBox; class QGroupBox; diff --git a/Source/Core/DolphinQt2/Config/PatchesWidget.cpp b/Source/Core/DolphinQt2/Config/PatchesWidget.cpp index a922371669..b0af60eb36 100644 --- a/Source/Core/DolphinQt2/Config/PatchesWidget.cpp +++ b/Source/Core/DolphinQt2/Config/PatchesWidget.cpp @@ -14,8 +14,8 @@ #include "Core/ConfigManager.h" #include "DolphinQt2/Config/NewPatchDialog.h" -PatchesWidget::PatchesWidget(const GameFile& game) - : m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision()) +PatchesWidget::PatchesWidget(const UICommon::GameFile& game) + : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()) { IniFile game_ini_local; game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); diff --git a/Source/Core/DolphinQt2/Config/PatchesWidget.h b/Source/Core/DolphinQt2/Config/PatchesWidget.h index 86a0174607..f8998f121b 100644 --- a/Source/Core/DolphinQt2/Config/PatchesWidget.h +++ b/Source/Core/DolphinQt2/Config/PatchesWidget.h @@ -10,7 +10,7 @@ #include #include "Core/PatchEngine.h" -#include "DolphinQt2/GameList/GameFile.h" +#include "UICommon/GameFile.h" class QListWidget; class QListWidgetItem; @@ -19,7 +19,7 @@ class QPushButton; class PatchesWidget : public QWidget { public: - explicit PatchesWidget(const GameFile& game); + explicit PatchesWidget(const UICommon::GameFile& game); private: void CreateWidgets(); @@ -39,7 +39,7 @@ private: QPushButton* m_remove_button; std::vector m_patches; - const GameFile& m_game; + const UICommon::GameFile& m_game; std::string m_game_id; u16 m_game_revision; }; diff --git a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp index cd2355d5d7..55b69859ed 100644 --- a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp +++ b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp @@ -13,11 +13,15 @@ #include "DolphinQt2/Config/InfoWidget.h" #include "DolphinQt2/Config/PatchesWidget.h" #include "DolphinQt2/Config/PropertiesDialog.h" +#include "UICommon/GameFile.h" -PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDialog(parent) +PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& game) + : QDialog(parent) { - setWindowTitle( - QStringLiteral("%1: %2 - %3").arg(game.GetFileName(), game.GetGameID(), game.GetLongName())); + setWindowTitle(QStringLiteral("%1: %2 - %3") + .arg(QString::fromStdString(game.GetFileName()), + QString::fromStdString(game.GetGameID()), + QString::fromStdString(game.GetLongName()))); QVBoxLayout* layout = new QVBoxLayout(); QTabWidget* tab_widget = new QTabWidget(this); @@ -39,7 +43,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia tab_widget->addTab(gecko, tr("Gecko Codes")); tab_widget->addTab(info, tr("Info")); - if (DiscIO::IsDisc(game.GetPlatformID())) + if (DiscIO::IsDisc(game.GetPlatform())) { FilesystemWidget* filesystem = new FilesystemWidget(game); tab_widget->addTab(filesystem, tr("Filesystem")); diff --git a/Source/Core/DolphinQt2/Config/PropertiesDialog.h b/Source/Core/DolphinQt2/Config/PropertiesDialog.h index 85f1ef64f5..5a4bb6af29 100644 --- a/Source/Core/DolphinQt2/Config/PropertiesDialog.h +++ b/Source/Core/DolphinQt2/Config/PropertiesDialog.h @@ -6,13 +6,16 @@ #include -#include "DolphinQt2/GameList/GameFile.h" +namespace UICommon +{ +class GameFile; +} class PropertiesDialog final : public QDialog { Q_OBJECT public: - explicit PropertiesDialog(QWidget* parent, const GameFile& game); + explicit PropertiesDialog(QWidget* parent, const UICommon::GameFile& game); signals: void OpenGeneralSettings(); diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 87eec88595..45877e96a3 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -97,7 +97,6 @@ - @@ -250,8 +249,6 @@ - - @@ -271,6 +268,7 @@ + @@ -311,9 +309,9 @@ - + diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp deleted file mode 100644 index 9f5314a444..0000000000 --- a/Source/Core/DolphinQt2/GameList/GameFile.cpp +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include -#include -#include - -#include "Common/Assert.h" -#include "Common/FileUtil.h" -#include "Common/NandPaths.h" -#include "Common/StringUtil.h" -#include "Core/ConfigManager.h" -#include "Core/HW/WiiSaveCrypted.h" -#include "Core/IOS/ES/ES.h" -#include "Core/IOS/IOS.h" -#include "Core/WiiUtils.h" -#include "DiscIO/Blob.h" -#include "DiscIO/Enums.h" -#include "DiscIO/Volume.h" -#include "DolphinQt2/GameList/GameFile.h" -#include "DolphinQt2/Resources.h" -#include "DolphinQt2/Settings.h" - -QList GameFile::GetAvailableLanguages() const -{ - return m_long_names.keys(); -} - -static QMap -ConvertLanguageMap(const std::map& map) -{ - QMap result; - for (auto entry : map) - result.insert(entry.first, QString::fromStdString(entry.second).trimmed()); - return result; -} - -GameFile::GameFile() -{ - m_valid = false; -} - -GameFile::GameFile(const QString& path) : m_path(path) -{ - m_valid = false; - - if (!LoadFileInfo(path)) - return; - - if (TryLoadVolume()) - { - LoadState(); - } - else if (!TryLoadElfDol()) - { - return; - } - - m_valid = true; -} - -bool GameFile::IsValid() const -{ - if (!m_valid) - return false; - - if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id)) - return false; - - return true; -} - -void GameFile::ReadBanner(const DiscIO::Volume& volume) -{ - int width, height; - std::vector buffer = volume.GetBanner(&width, &height); - QImage banner(width, height, QImage::Format_RGB888); - for (int i = 0; i < width * height; i++) - { - int x = i % width, y = i / width; - banner.setPixel(x, y, qRgb((buffer[i] & 0xFF0000) >> 16, (buffer[i] & 0x00FF00) >> 8, - (buffer[i] & 0x0000FF) >> 0)); - } - - if (!banner.isNull()) - m_banner = QPixmap::fromImage(banner); -} - -bool GameFile::LoadFileInfo(const QString& path) -{ - QFileInfo info(path); - if (!info.exists() || !info.isReadable()) - return false; - - m_last_modified = info.lastModified(); - m_size = info.size(); - - return true; -} - -void GameFile::LoadState() -{ - IniFile ini = SConfig::LoadGameIni(m_game_id.toStdString(), m_revision); - std::string issues_temp; - ini.GetIfExists("EmuState", "EmulationStateId", &m_rating); - ini.GetIfExists("EmuState", "EmulationIssues", &issues_temp); - m_issues = QString::fromStdString(issues_temp); -} - -bool GameFile::IsElfOrDol() -{ - QString extension = GetFileExtension(); - return extension == QStringLiteral("elf") || extension == QStringLiteral("dol"); -} - -bool GameFile::TryLoadVolume() -{ - QSharedPointer volume( - DiscIO::CreateVolumeFromFilename(m_path.toStdString()).release()); - if (volume == nullptr) - return false; - - m_game_id = QString::fromStdString(volume->GetGameID()); - std::string maker_id = volume->GetMakerID(); - m_title_id = volume->GetTitleID().value_or(0); - m_maker = QString::fromStdString(DiscIO::GetCompanyFromID(maker_id)); - m_maker_id = QString::fromStdString(maker_id); - m_revision = volume->GetRevision().value_or(0); - m_internal_name = QString::fromStdString(volume->GetInternalName()); - m_short_names = ConvertLanguageMap(volume->GetShortNames()); - m_long_names = ConvertLanguageMap(volume->GetLongNames()); - m_short_makers = ConvertLanguageMap(volume->GetShortMakers()); - m_long_makers = ConvertLanguageMap(volume->GetLongMakers()); - m_descriptions = ConvertLanguageMap(volume->GetDescriptions()); - m_disc_number = volume->GetDiscNumber().value_or(0); - m_platform = volume->GetVolumeType(); - m_region = volume->GetRegion(); - m_country = volume->GetCountry(); - m_blob_type = volume->GetBlobType(); - m_raw_size = volume->GetRawSize(); - m_apploader_date = QString::fromStdString(volume->GetApploaderDate()); - - ReadBanner(*volume); - - return true; -} - -bool GameFile::TryLoadElfDol() -{ - if (!IsElfOrDol()) - return false; - - m_revision = 0; - m_platform = DiscIO::Platform::ELF_DOL; - m_region = DiscIO::Region::UNKNOWN_REGION; - m_country = DiscIO::Country::COUNTRY_UNKNOWN; - m_blob_type = DiscIO::BlobType::DIRECTORY; - m_raw_size = m_size; - m_rating = 0; - - return true; -} - -QString GameFile::GetFileName() const -{ - return QFileInfo(m_path).fileName(); -} - -QString GameFile::GetFileExtension() const -{ - return QFileInfo(m_path).suffix(); -} - -QString GameFile::GetFileFolder() const -{ - return QFileInfo(m_path).dir().dirName(); -} - -QString GameFile::GetBannerString(const QMap& m) const -{ - // Try the settings language, then English, then just pick one. - if (m.isEmpty()) - return QString(); - - bool wii = m_platform != DiscIO::Platform::GAMECUBE_DISC; - DiscIO::Language current_lang = SConfig::GetInstance().GetCurrentLanguage(wii); - - if (m.contains(current_lang)) - return m[current_lang]; - if (m.contains(DiscIO::Language::LANGUAGE_ENGLISH)) - return m[DiscIO::Language::LANGUAGE_ENGLISH]; - return m.first(); -} - -QString GameFile::GetPlatform() const -{ - switch (m_platform) - { - case DiscIO::Platform::GAMECUBE_DISC: - return QObject::tr("GameCube"); - case DiscIO::Platform::WII_DISC: - return QObject::tr("Wii"); - case DiscIO::Platform::WII_WAD: - return QObject::tr("Wii Channel"); - case DiscIO::Platform::ELF_DOL: - return QObject::tr("ELF/DOL"); - default: - return QObject::tr("Unknown"); - } -} - -QString GameFile::GetCountry() const -{ - switch (m_country) - { - case DiscIO::Country::COUNTRY_EUROPE: - return QObject::tr("Europe"); - case DiscIO::Country::COUNTRY_JAPAN: - return QObject::tr("Japan"); - case DiscIO::Country::COUNTRY_USA: - return QObject::tr("USA"); - case DiscIO::Country::COUNTRY_AUSTRALIA: - return QObject::tr("Australia"); - case DiscIO::Country::COUNTRY_FRANCE: - return QObject::tr("France"); - case DiscIO::Country::COUNTRY_GERMANY: - return QObject::tr("Germany"); - case DiscIO::Country::COUNTRY_ITALY: - return QObject::tr("Italy"); - case DiscIO::Country::COUNTRY_KOREA: - return QObject::tr("Korea"); - case DiscIO::Country::COUNTRY_NETHERLANDS: - return QObject::tr("Netherlands"); - case DiscIO::Country::COUNTRY_RUSSIA: - return QObject::tr("Russia"); - case DiscIO::Country::COUNTRY_SPAIN: - return QObject::tr("Spain"); - case DiscIO::Country::COUNTRY_TAIWAN: - return QObject::tr("Taiwan"); - case DiscIO::Country::COUNTRY_WORLD: - return QObject::tr("World"); - default: - return QObject::tr("Unknown"); - } -} - -QString GameFile::GetLanguage(DiscIO::Language lang) const -{ - switch (lang) - { - case DiscIO::Language::LANGUAGE_JAPANESE: - return QObject::tr("Japanese"); - case DiscIO::Language::LANGUAGE_ENGLISH: - return QObject::tr("English"); - case DiscIO::Language::LANGUAGE_GERMAN: - return QObject::tr("German"); - case DiscIO::Language::LANGUAGE_FRENCH: - return QObject::tr("French"); - case DiscIO::Language::LANGUAGE_SPANISH: - return QObject::tr("Spanish"); - case DiscIO::Language::LANGUAGE_ITALIAN: - return QObject::tr("Italian"); - case DiscIO::Language::LANGUAGE_DUTCH: - return QObject::tr("Dutch"); - case DiscIO::Language::LANGUAGE_SIMPLIFIED_CHINESE: - return QObject::tr("Simplified Chinese"); - case DiscIO::Language::LANGUAGE_TRADITIONAL_CHINESE: - return QObject::tr("Traditional Chinese"); - case DiscIO::Language::LANGUAGE_KOREAN: - return QObject::tr("Korean"); - default: - return QObject::tr("Unknown"); - } -} - -QString GameFile::GetUniqueID() const -{ - std::vector info; - if (!GetGameID().isEmpty()) - info.push_back(GetGameID().toStdString()); - - if (GetRevision() != 0) - { - info.push_back("Revision " + std::to_string(GetRevision())); - } - - std::string name = m_long_names[DiscIO::Language::LANGUAGE_ENGLISH].toStdString(); - - if (name.empty()) - { - if (!m_long_names.isEmpty()) - name = m_long_names.begin().value().toStdString(); - else - { - std::string filename, extension; - name = SplitPath(m_path.toStdString(), nullptr, &filename, &extension); - name = filename + extension; - } - } - - int disc_number = GetDiscNumber() + 1; - - std::string lower_name = name; - std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower); - if (disc_number > 1 && - lower_name.find(std::string("disc ") + std::to_string(disc_number)) == std::string::npos && - lower_name.find(std::string("disc") + std::to_string(disc_number)) == std::string::npos) - { - info.push_back("Disc " + std::to_string(disc_number)); - } - - if (info.empty()) - return QString::fromStdString(name); - - std::ostringstream ss; - std::copy(info.begin(), info.end() - 1, std::ostream_iterator(ss, ", ")); - ss << info.back(); - return QString::fromStdString(name + " (" + ss.str() + ")"); -} - -bool GameFile::IsInstalled() const -{ - _assert_(m_platform == DiscIO::Platform::WII_WAD); - - const std::string content_dir = - Common::GetTitleContentPath(m_title_id, Common::FromWhichRoot::FROM_CONFIGURED_ROOT); - - if (!File::IsDirectory(content_dir)) - return false; - - // Since this isn't IOS and we only need a simple way to figure out if a title is installed, - // we make the (reasonable) assumption that having more than just the TMD in the content - // directory means that the title is installed. - const auto entries = File::ScanDirectoryTree(content_dir, false); - return std::any_of(entries.children.begin(), entries.children.end(), - [](const auto& file) { return file.virtualName != "title.tmd"; }); -} - -bool GameFile::Install() -{ - _assert_(m_platform == DiscIO::Platform::WII_WAD); - - bool installed = WiiUtils::InstallWAD(m_path.toStdString()); - - if (installed) - Settings::Instance().NANDRefresh(); - - return installed; -} - -bool GameFile::Uninstall() -{ - _assert_(m_platform == DiscIO::Platform::WII_WAD); - IOS::HLE::Kernel ios; - return ios.GetES()->DeleteTitleContent(m_title_id) == IOS::HLE::IPC_SUCCESS; -} - -bool GameFile::ExportWiiSave() -{ - return CWiiSaveCrypted::ExportWiiSave(m_title_id); -} - -QString GameFile::GetWiiFSPath() const -{ - _assert_(m_platform == DiscIO::Platform::WII_DISC || m_platform == DiscIO::Platform::WII_WAD); - - const std::string path = Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT); - - return QString::fromStdString(path); -} - -// Convert an integer size to a friendly string representation. -QString FormatSize(qint64 size) -{ - QStringList units{QStringLiteral("KB"), QStringLiteral("MB"), QStringLiteral("GB"), - QStringLiteral("TB")}; - QStringListIterator i(units); - QString unit = QStringLiteral("B"); - double num = (double)size; - while (num > 1024.0 && i.hasNext()) - { - unit = i.next(); - num /= 1024.0; - } - return QStringLiteral("%1 %2").arg(QString::number(num, 'f', 1)).arg(unit); -} - -template ::value>> -QDataStream& operator<<(QDataStream& out, const T& enum_value) -{ - out << static_cast>(enum_value); - return out; -} - -template ::value>> -QDataStream& operator>>(QDataStream& in, T& enum_value) -{ - std::underlying_type_t tmp; - in >> tmp; - enum_value = static_cast(tmp); - return in; -} - -// Some C++ implementations define uint64_t as an 'unsigned long', but QDataStream only has built-in -// overloads for quint64, which is an 'unsigned long long' on Unix -QDataStream& operator<<(QDataStream& out, const unsigned long& integer) -{ - out << static_cast(integer); - return out; -} -QDataStream& operator>>(QDataStream& in, unsigned long& integer) -{ - quint64 tmp; - in >> tmp; - integer = static_cast(tmp); - return in; -} - -QDataStream& operator<<(QDataStream& out, const GameFile& file) -{ - out << file.m_last_modified; - out << file.m_path; - out << file.m_title_id; - out << file.m_game_id; - out << file.m_maker_id; - out << file.m_maker; - out << file.m_long_makers; - out << file.m_short_makers; - out << file.m_internal_name; - out << file.m_long_names; - out << file.m_short_names; - out << file.m_platform; - out << file.m_region; - out << file.m_country; - out << file.m_blob_type; - out << file.m_size; - out << file.m_raw_size; - out << file.m_descriptions; - out << file.m_revision; - out << file.m_disc_number; - out << file.m_issues; - out << file.m_rating; - out << file.m_apploader_date; - out << file.m_banner; - - return out; -} - -QDataStream& operator>>(QDataStream& in, GameFile& file) -{ - in >> file.m_last_modified; - in >> file.m_path; - in >> file.m_title_id; - in >> file.m_game_id; - in >> file.m_maker_id; - in >> file.m_maker; - in >> file.m_long_makers; - in >> file.m_short_makers; - in >> file.m_internal_name; - in >> file.m_long_names; - in >> file.m_short_names; - in >> file.m_platform; - in >> file.m_region; - in >> file.m_country; - in >> file.m_blob_type; - in >> file.m_size; - in >> file.m_raw_size; - in >> file.m_descriptions; - in >> file.m_revision; - in >> file.m_disc_number; - in >> file.m_issues; - in >> file.m_rating; - in >> file.m_apploader_date; - in >> file.m_banner; - - file.m_valid = true; - - return in; -} diff --git a/Source/Core/DolphinQt2/GameList/GameFile.h b/Source/Core/DolphinQt2/GameList/GameFile.h deleted file mode 100644 index c5791ed7cc..0000000000 --- a/Source/Core/DolphinQt2/GameList/GameFile.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2015 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" - -namespace DiscIO -{ -enum class BlobType; -enum class Country; -enum class Language; -enum class Region; -enum class Platform; -class Volume; -} - -class GameFile final -{ -public: - GameFile(); - explicit GameFile(const QString& path); - - bool IsValid() const; - // These will be properly initialized before we try to load the file. - QString GetFilePath() const { return m_path; } - QString GetFileName() const; - QString GetFileExtension() const; - QString GetFileFolder() const; - qint64 GetFileSize() const { return m_size; } - QDateTime GetLastModified() const { return m_last_modified; } - // The rest will not. - QString GetGameID() const { return m_game_id; } - QString GetMakerID() const { return m_maker_id; } - QString GetMaker() const { return m_maker; } - u64 GetTitleID() const { return m_title_id; } - u16 GetRevision() const { return m_revision; } - QString GetInternalName() const { return m_internal_name; } - QString GetUniqueID() const; - u8 GetDiscNumber() const { return m_disc_number; } - u64 GetRawSize() const { return m_raw_size; } - QPixmap GetBanner() const { return m_banner; } - QString GetIssues() const { return m_issues; } - int GetRating() const { return m_rating; } - QString GetApploaderDate() const { return m_apploader_date; } - DiscIO::Platform GetPlatformID() const { return m_platform; } - QString GetPlatform() const; - DiscIO::Region GetRegion() const { return m_region; } - DiscIO::Country GetCountryID() const { return m_country; } - QString GetCountry() const; - DiscIO::BlobType GetBlobType() const { return m_blob_type; } - QString GetWiiFSPath() const; - bool IsInstalled() const; - // Banner details - QString GetLanguage(DiscIO::Language lang) const; - QList GetAvailableLanguages() const; - QString GetShortName() const { return GetBannerString(m_short_names); } - QString GetShortMaker() const { return GetBannerString(m_short_makers); } - QString GetLongName() const { return GetBannerString(m_long_names); } - QString GetLongMaker() const { return GetBannerString(m_long_makers); } - QString GetDescription() const { return GetBannerString(m_descriptions); } - QString GetShortName(DiscIO::Language lang) const { return m_short_names[lang]; } - QString GetShortMaker(DiscIO::Language lang) const { return m_short_makers[lang]; } - QString GetLongName(DiscIO::Language lang) const { return m_long_names[lang]; } - QString GetLongMaker(DiscIO::Language lang) const { return m_long_makers[lang]; } - QString GetDescription(DiscIO::Language lang) const { return m_descriptions[lang]; } - bool Install(); - bool Uninstall(); - bool ExportWiiSave(); - - friend QDataStream& operator<<(QDataStream& out, const GameFile& file); - friend QDataStream& operator>>(QDataStream& in, GameFile& file); - -private: - QString GetBannerString(const QMap& m) const; - - void ReadBanner(const DiscIO::Volume& volume); - bool LoadFileInfo(const QString& path); - void LoadState(); - bool IsElfOrDol(); - bool TryLoadElfDol(); - bool TryLoadVolume(); - - bool m_valid; - QString m_path; - QDateTime m_last_modified; - qint64 m_size = 0; - - QString m_game_id; - QString m_maker; - QString m_maker_id; - u16 m_revision = 0; - u64 m_title_id = 0; - QString m_internal_name; - QMap m_short_names; - QMap m_long_names; - QMap m_short_makers; - QMap m_long_makers; - QMap m_descriptions; - u8 m_disc_number = 0; - DiscIO::Region m_region; - DiscIO::Platform m_platform; - DiscIO::Country m_country; - DiscIO::BlobType m_blob_type; - u64 m_raw_size = 0; - QPixmap m_banner; - QString m_issues; - int m_rating = 0; - QString m_apploader_date; -}; - -QString FormatSize(qint64 size); - -QDataStream& operator<<(QDataStream& out, const GameFile& file); -QDataStream& operator>>(QDataStream& in, GameFile& file); - -QDataStream& operator<<(QDataStream& out, const unsigned long& file); -QDataStream& operator>>(QDataStream& in, unsigned long& file); diff --git a/Source/Core/DolphinQt2/GameList/GameFileCache.cpp b/Source/Core/DolphinQt2/GameList/GameFileCache.cpp deleted file mode 100644 index 167218c438..0000000000 --- a/Source/Core/DolphinQt2/GameList/GameFileCache.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "GameFileCache.h" - -#include -#include -#include -#include - -#include "Common/FileUtil.h" -#include "Core/ConfigManager.h" -#include "DolphinQt2/Settings.h" - -static const int CACHE_VERSION = 4; // Last changed in PR #6109 -static const int DATASTREAM_VERSION = QDataStream::Qt_5_0; - -GameFileCache::GameFileCache() - : m_path(QString::fromStdString(File::GetUserPath(D_CACHE_IDX) + "qt_gamefile.cache")) -{ -} - -bool GameFileCache::IsCached(const QString& path) const -{ - return m_gamefiles.contains(path); -} - -GameFile GameFileCache::GetFile(const QString& path) const -{ - return m_gamefiles[path]; -} - -void GameFileCache::Load() -{ - QFile file(m_path); - - if (!file.open(QIODevice::ReadOnly)) - return; - - QDataStream stream(&file); - stream.setVersion(DATASTREAM_VERSION); - - qint32 cache_version; - stream >> cache_version; - - // If the cache file is using an older version, ignore it and create it from scratch - if (cache_version != CACHE_VERSION) - return; - - stream >> m_gamefiles; -} - -void GameFileCache::Save() const -{ - QFile file(m_path); - - if (!file.open(QIODevice::WriteOnly)) - return; - - QDataStream stream(&file); - stream.setVersion(DATASTREAM_VERSION); - - stream << static_cast(CACHE_VERSION); - stream << m_gamefiles; -} - -void GameFileCache::Update(const GameFile& gamefile) -{ - m_gamefiles[gamefile.GetFilePath()] = gamefile; -} - -QList GameFileCache::GetCached() const -{ - return m_gamefiles.keys(); -} diff --git a/Source/Core/DolphinQt2/GameList/GameFileCache.h b/Source/Core/DolphinQt2/GameList/GameFileCache.h deleted file mode 100644 index df4cd75f16..0000000000 --- a/Source/Core/DolphinQt2/GameList/GameFileCache.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include "DolphinQt2/GameList/GameFile.h" - -class GameFileCache -{ -public: - explicit GameFileCache(); - - void Update(const GameFile& gamefile); - void Save() const; - void Load(); - bool IsCached(const QString& path) const; - GameFile GetFile(const QString& path) const; - QList GetCached() const; - -private: - QString m_path; - - QMap m_gamefiles; -}; diff --git a/Source/Core/DolphinQt2/GameList/GameList.cpp b/Source/Core/DolphinQt2/GameList/GameList.cpp index 62a4ba39f1..996d86fe4c 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.cpp +++ b/Source/Core/DolphinQt2/GameList/GameList.cpp @@ -21,6 +21,8 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/WiiSaveCrypted.h" +#include "Core/WiiUtils.h" #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" @@ -157,11 +159,11 @@ void GameList::MakeGridView() void GameList::ShowContextMenu(const QPoint&) { const auto game = GetSelectedGame(); - if (game.isNull()) + if (!game) return; QMenu* menu = new QMenu(this); - DiscIO::Platform platform = game->GetPlatformID(); + DiscIO::Platform platform = game->GetPlatform(); AddAction(menu, tr("&Properties"), this, &GameList::OpenProperties); AddAction(menu, tr("&Wiki"), this, &GameList::OpenWiki); menu->addSeparator(); @@ -188,7 +190,7 @@ void GameList::ShowContextMenu(const QPoint&) if (platform == DiscIO::Platform::WII_DISC) { auto* perform_disc_update = AddAction(menu, tr("Perform System Update"), this, [this] { - WiiUpdate::PerformDiscUpdate(GetSelectedGame()->GetFilePath().toStdString(), this); + WiiUpdate::PerformDiscUpdate(GetSelectedGame()->GetFilePath(), this); }); perform_disc_update->setEnabled(!Core::IsRunning() || !SConfig::GetInstance().bWii); } @@ -209,7 +211,8 @@ void GameList::ShowContextMenu(const QPoint&) connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) { wad_install_action->setEnabled(state == Core::State::Uninitialized); - wad_uninstall_action->setEnabled(state == Core::State::Uninitialized && game->IsInstalled()); + wad_uninstall_action->setEnabled(state == Core::State::Uninitialized && + WiiUtils::IsTitleInstalled(game->GetTitleID())); }); menu->addSeparator(); @@ -228,7 +231,7 @@ void GameList::ShowContextMenu(const QPoint&) QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu); connect(netplay_host, &QAction::triggered, - [this, game] { emit NetPlayHost(game->GetUniqueID()); }); + [this, game] { emit NetPlayHost(QString::fromStdString(game->GetUniqueIdentifier())); }); connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) { netplay_host->setEnabled(state == Core::State::Uninitialized); @@ -253,7 +256,7 @@ void GameList::ExportWiiSave() { QMessageBox result_dialog(this); - const bool success = GetSelectedGame()->ExportWiiSave(); + const bool success = CWiiSaveCrypted::ExportWiiSave(GetSelectedGame()->GetTitleID()); result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical); result_dialog.setText(success ? tr("Successfully exported save files") : @@ -263,7 +266,7 @@ void GameList::ExportWiiSave() void GameList::OpenWiki() { - QString game_id = GetSelectedGame()->GetGameID(); + QString game_id = QString::fromStdString(GetSelectedGame()->GetGameID()); QString url = QStringLiteral("https://wiki.dolphin-emu.org/index.php?title=").append(game_id); QDesktopServices::openUrl(QUrl(url)); } @@ -275,7 +278,7 @@ void GameList::CompressISO() const bool compressed = (file->GetBlobType() == DiscIO::BlobType::GCZ); - if (!compressed && file->GetPlatformID() == DiscIO::Platform::WII_DISC) + if (!compressed && file->GetPlatform() == DiscIO::Platform::WII_DISC) { QMessageBox wii_warning(this); wii_warning.setIcon(QMessageBox::Warning); @@ -292,9 +295,9 @@ void GameList::CompressISO() QString dst_path = QFileDialog::getSaveFileName( this, compressed ? tr("Select where you want to save the decompressed image") : tr("Select where you want to save the compressed image"), - QFileInfo(GetSelectedGame()->GetFilePath()) + QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath())) .dir() - .absoluteFilePath(file->GetGameID()) + .absoluteFilePath(QString::fromStdString(file->GetGameID())) .append(compressed ? QStringLiteral(".gcm") : QStringLiteral(".gcz")), compressed ? tr("Uncompressed GC/Wii images (*.iso *.gcm)") : tr("Compressed GC/Wii images (*.gcz)")); @@ -310,13 +313,13 @@ void GameList::CompressISO() if (compressed) { - good = DiscIO::DecompressBlobToFile(original_path.toStdString(), dst_path.toStdString(), - &CompressCB, &progress_dialog); + good = DiscIO::DecompressBlobToFile(original_path, dst_path.toStdString(), &CompressCB, + &progress_dialog); } else { - good = DiscIO::CompressFileToBlob(original_path.toStdString(), dst_path.toStdString(), - file->GetPlatformID() == DiscIO::Platform::WII_DISC ? 1 : 0, + good = DiscIO::CompressFileToBlob(original_path, dst_path.toStdString(), + file->GetPlatform() == DiscIO::Platform::WII_DISC ? 1 : 0, 16384, &CompressCB, &progress_dialog); } @@ -336,7 +339,7 @@ void GameList::InstallWAD() { QMessageBox result_dialog(this); - const bool success = GetSelectedGame()->Install(); + const bool success = WiiUtils::InstallWAD(GetSelectedGame()->GetFilePath()); result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical); result_dialog.setText(success ? tr("Successfully installed this title to the NAND.") : @@ -358,7 +361,7 @@ void GameList::UninstallWAD() QMessageBox result_dialog(this); - const bool success = GetSelectedGame()->Uninstall(); + const bool success = WiiUtils::UninstallTitle(GetSelectedGame()->GetTitleID()); result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical); result_dialog.setText(success ? tr("Successfully removed this title from the NAND.") : @@ -368,24 +371,25 @@ void GameList::UninstallWAD() void GameList::SetDefaultISO() { - Settings::Instance().SetDefaultGame(GetSelectedGame()->GetFilePath()); + SConfig::GetInstance().m_strDefaultISO = GetSelectedGame()->GetFilePath(); } void GameList::OpenContainingFolder() { - QUrl url = QUrl::fromLocalFile(QFileInfo(GetSelectedGame()->GetFilePath()).dir().absolutePath()); + QUrl url = QUrl::fromLocalFile( + QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath())).dir().absolutePath()); QDesktopServices::openUrl(url); } void GameList::OpenSaveFolder() { - QUrl url = QUrl::fromLocalFile(GetSelectedGame()->GetWiiFSPath()); + QUrl url = QUrl::fromLocalFile(QString::fromStdString(GetSelectedGame()->GetWiiFSPath())); QDesktopServices::openUrl(url); } void GameList::DeleteFile() { - const auto game = GetSelectedGame()->GetFilePath(); + const std::string game = GetSelectedGame()->GetFilePath(); QMessageBox confirm_dialog(this); confirm_dialog.setIcon(QMessageBox::Warning); @@ -399,7 +403,7 @@ void GameList::DeleteFile() while (!deletion_successful) { - deletion_successful = File::Delete(game.toStdString()); + deletion_successful = File::Delete(game); if (deletion_successful) { @@ -424,11 +428,10 @@ void GameList::DeleteFile() void GameList::ChangeDisc() { - Core::RunAsCPUThread( - [this] { DVDInterface::ChangeDisc(GetSelectedGame()->GetFilePath().toStdString()); }); + Core::RunAsCPUThread([this] { DVDInterface::ChangeDisc(GetSelectedGame()->GetFilePath()); }); } -QSharedPointer GameList::GetSelectedGame() const +std::shared_ptr GameList::GetSelectedGame() const { QAbstractItemView* view; QSortFilterProxyModel* proxy; diff --git a/Source/Core/DolphinQt2/GameList/GameList.h b/Source/Core/DolphinQt2/GameList/GameList.h index fb564cde92..d58827dac9 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.h +++ b/Source/Core/DolphinQt2/GameList/GameList.h @@ -4,22 +4,25 @@ #pragma once +#include + #include #include #include #include #include -#include "DolphinQt2/GameList/GameFile.h" #include "DolphinQt2/GameList/GameListModel.h" +#include "UICommon/GameFile.h" + class GameList final : public QStackedWidget { Q_OBJECT public: explicit GameList(QWidget* parent = nullptr); - QSharedPointer GetSelectedGame() const; + std::shared_ptr GetSelectedGame() const; void SetListView() { SetPreferredView(true); } void SetGridView() { SetPreferredView(false); } @@ -30,7 +33,7 @@ public: signals: void GameSelected(); void NetPlayHost(const QString& game_id); - void SelectionChanged(QSharedPointer game_file); + void SelectionChanged(std::shared_ptr game_file); void OpenGeneralSettings(); private: diff --git a/Source/Core/DolphinQt2/GameList/GameListModel.cpp b/Source/Core/DolphinQt2/GameList/GameListModel.cpp index 01744150a3..7dd5cf4d0b 100644 --- a/Source/Core/DolphinQt2/GameList/GameListModel.cpp +++ b/Source/Core/DolphinQt2/GameList/GameListModel.cpp @@ -3,17 +3,23 @@ // Refer to the license.txt file included. #include "DolphinQt2/GameList/GameListModel.h" + +#include + #include "Core/ConfigManager.h" #include "DiscIO/Enums.h" +#include "DolphinQt2/QtUtils/ImageConverter.h" #include "DolphinQt2/Resources.h" #include "DolphinQt2/Settings.h" +#include "UICommon/UICommon.h" const QSize GAMECUBE_BANNER_SIZE(96, 32); GameListModel::GameListModel(QObject* parent) : QAbstractTableModel(parent) { connect(&m_tracker, &GameTracker::GameLoaded, this, &GameListModel::UpdateGame); - connect(&m_tracker, &GameTracker::GameRemoved, this, &GameListModel::RemoveGame); + connect(&m_tracker, &GameTracker::GameRemoved, this, + [this](const QString& path) { RemoveGame(path.toStdString()); }); connect(&Settings::Instance(), &Settings::PathAdded, &m_tracker, &GameTracker::AddDirectory); connect(&Settings::Instance(), &Settings::PathRemoved, &m_tracker, &GameTracker::RemoveDirectory); @@ -26,8 +32,6 @@ GameListModel::GameListModel(QObject* parent) : QAbstractTableModel(parent) emit layoutAboutToBeChanged(); emit layoutChanged(); }); - - // TODO: Reload m_title_database when the language changes } QVariant GameListModel::data(const QModelIndex& index, int role) const @@ -35,78 +39,68 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const if (!index.isValid()) return QVariant(); - QSharedPointer game = m_games[index.row()]; + const UICommon::GameFile& game = *m_games[index.row()]; switch (index.column()) { case COL_PLATFORM: if (role == Qt::DecorationRole) - return Resources::GetPlatform(static_cast(game->GetPlatformID())); + return Resources::GetPlatform(static_cast(game.GetPlatform())); if (role == Qt::InitialSortOrderRole) - return static_cast(game->GetPlatformID()); + return static_cast(game.GetPlatform()); break; case COL_COUNTRY: if (role == Qt::DecorationRole) - return Resources::GetCountry(static_cast(game->GetCountryID())); + return Resources::GetCountry(static_cast(game.GetCountry())); if (role == Qt::InitialSortOrderRole) - return static_cast(game->GetCountryID()); + return static_cast(game.GetCountry()); break; case COL_RATING: if (role == Qt::DecorationRole) - return Resources::GetRating(game->GetRating()); + return Resources::GetRating(game.GetEmuState()); if (role == Qt::InitialSortOrderRole) - return game->GetRating(); + return game.GetEmuState(); break; case COL_BANNER: if (role == Qt::DecorationRole) { // GameCube banners are 96x32, but Wii banners are 192x64. - // TODO: use custom banners from rom directory like DolphinWX? - QPixmap banner = game->GetBanner(); + QPixmap banner = ToQPixmap(game.GetBannerImage()); if (banner.isNull()) banner = Resources::GetMisc(Resources::BANNER_MISSING); - banner.setDevicePixelRatio(std::max(banner.width() / GAMECUBE_BANNER_SIZE.width(), - banner.height() / GAMECUBE_BANNER_SIZE.height())); + + banner.setDevicePixelRatio( + std::max(static_cast(banner.width()) / GAMECUBE_BANNER_SIZE.width(), + static_cast(banner.height()) / GAMECUBE_BANNER_SIZE.height())); + return banner; } break; case COL_TITLE: if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) - { - QString display_name = QString::fromStdString(m_title_database.GetTitleName( - game->GetGameID().toStdString(), game->GetPlatformID() == DiscIO::Platform::WII_WAD ? - Core::TitleDatabase::TitleType::Channel : - Core::TitleDatabase::TitleType::Other)); - if (display_name.isEmpty()) - display_name = game->GetLongName(); - - if (display_name.isEmpty()) - display_name = game->GetFileName(); - - return display_name; - } + return QString::fromStdString(game.GetName()); break; case COL_ID: if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) - return game->GetGameID(); + return QString::fromStdString(game.GetGameID()); break; case COL_DESCRIPTION: if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) - return game->GetDescription(); + return QString::fromStdString(game.GetDescription()); break; case COL_MAKER: if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) - return game->GetMaker(); + return QString::fromStdString(game.GetMaker()); break; case COL_FILE_NAME: if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) - return game->GetFileName(); + return QString::fromStdString(game.GetFileName()); break; case COL_SIZE: if (role == Qt::DisplayRole) - return FormatSize(game->GetFileSize()); + return QString::fromStdString(UICommon::FormatSize(game.GetFileSize())); if (role == Qt::InitialSortOrderRole) - return game->GetFileSize(); + return static_cast(game.GetFileSize()); break; } @@ -154,10 +148,10 @@ int GameListModel::columnCount(const QModelIndex& parent) const bool GameListModel::ShouldDisplayGameListItem(int index) const { - QSharedPointer game = m_games[index]; + const UICommon::GameFile& game = *m_games[index]; const bool show_platform = [&game] { - switch (game->GetPlatformID()) + switch (game.GetPlatform()) { case DiscIO::Platform::GAMECUBE_DISC: return SConfig::GetInstance().m_ListGC; @@ -175,7 +169,7 @@ bool GameListModel::ShouldDisplayGameListItem(int index) const if (!show_platform) return false; - switch (game->GetCountryID()) + switch (game.GetCountry()) { case DiscIO::Country::COUNTRY_AUSTRALIA: return SConfig::GetInstance().m_ListAustralia; @@ -209,16 +203,14 @@ bool GameListModel::ShouldDisplayGameListItem(int index) const } } -QSharedPointer GameListModel::GetGameFile(int index) const +std::shared_ptr GameListModel::GetGameFile(int index) const { return m_games[index]; } -void GameListModel::UpdateGame(const QSharedPointer& game) +void GameListModel::UpdateGame(const std::shared_ptr& game) { - QString path = game->GetFilePath(); - - int index = FindGame(path); + int index = FindGame(game->GetFilePath()); if (index < 0) { beginInsertRows(QModelIndex(), m_games.size(), m_games.size()); @@ -232,7 +224,7 @@ void GameListModel::UpdateGame(const QSharedPointer& game) } } -void GameListModel::RemoveGame(const QString& path) +void GameListModel::RemoveGame(const std::string& path) { int entry = FindGame(path); if (entry < 0) @@ -243,7 +235,7 @@ void GameListModel::RemoveGame(const QString& path) endRemoveRows(); } -int GameListModel::FindGame(const QString& path) const +int GameListModel::FindGame(const std::string& path) const { for (int i = 0; i < m_games.size(); i++) { diff --git a/Source/Core/DolphinQt2/GameList/GameListModel.h b/Source/Core/DolphinQt2/GameList/GameListModel.h index 5781add1c7..8a8585da1a 100644 --- a/Source/Core/DolphinQt2/GameList/GameListModel.h +++ b/Source/Core/DolphinQt2/GameList/GameListModel.h @@ -4,12 +4,14 @@ #pragma once +#include +#include + #include #include -#include "Core/TitleDatabase.h" -#include "DolphinQt2/GameList/GameFile.h" #include "DolphinQt2/GameList/GameTracker.h" +#include "UICommon/GameFile.h" class GameListModel final : public QAbstractTableModel { @@ -25,11 +27,14 @@ public: int rowCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override; - QSharedPointer GetGameFile(int index) const; - // Path of the Game at the specified index. - QString GetPath(int index) const { return m_games[index]->GetFilePath(); } - // Unique ID of the Game at the specified index - QString GetUniqueID(int index) const { return m_games[index]->GetUniqueID(); } + std::shared_ptr GetGameFile(int index) const; + // Path of the game at the specified index. + QString GetPath(int index) const { return QString::fromStdString(m_games[index]->GetFilePath()); } + // Unique identifier of the game at the specified index. + QString GetUniqueIdentifier(int index) const + { + return QString::fromStdString(m_games[index]->GetUniqueIdentifier()); + } bool ShouldDisplayGameListItem(int index) const; enum { @@ -46,14 +51,13 @@ public: NUM_COLS }; - void UpdateGame(const QSharedPointer& game); - void RemoveGame(const QString& path); + void UpdateGame(const std::shared_ptr& game); + void RemoveGame(const std::string& path); private: // Index in m_games, or -1 if it isn't found - int FindGame(const QString& path) const; + int FindGame(const std::string& path) const; GameTracker m_tracker; - QList> m_games; - Core::TitleDatabase m_title_database; + QList> m_games; }; diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.cpp b/Source/Core/DolphinQt2/GameList/GameTracker.cpp index 85539f4d33..94000057b6 100644 --- a/Source/Core/DolphinQt2/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt2/GameList/GameTracker.cpp @@ -7,7 +7,6 @@ #include #include "DiscIO/DirectoryBlob.h" -#include "DolphinQt2/GameList/GameFileCache.h" #include "DolphinQt2/GameList/GameTracker.h" #include "DolphinQt2/QtUtils/QueueOnObject.h" #include "DolphinQt2/Settings.h" @@ -19,13 +18,15 @@ static const QStringList game_filters{ GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent) { - qRegisterMetaType>(); + qRegisterMetaType>(); connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory); connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile); - cache.Load(); + m_cache.Load(); m_load_thread.Reset([this](const QString& path) { LoadGame(path); }); + + // TODO: When language changes, reload m_title_database and call m_cache.UpdateAdditionalMetadata } void GameTracker::AddDirectory(const QString& dir) @@ -130,25 +131,12 @@ void GameTracker::UpdateFile(const QString& file) void GameTracker::LoadGame(const QString& path) { - if (!DiscIO::ShouldHideFromGameList(path.toStdString())) + const std::string converted_path = path.toStdString(); + if (!DiscIO::ShouldHideFromGameList(converted_path)) { - if (cache.IsCached(path)) - { - const QDateTime last_modified = QFileInfo(path).lastModified(); - auto cached_file = cache.GetFile(path); - if (cached_file.GetLastModified() >= last_modified) - { - emit GameLoaded(QSharedPointer::create(cached_file)); - return; - } - } - - auto game = QSharedPointer::create(path); - if (game->IsValid()) - { - emit GameLoaded(game); - cache.Update(*game); - cache.Save(); - } + bool cache_changed = false; + emit GameLoaded(m_cache.AddOrGet(converted_path, &cache_changed, m_title_database)); + if (cache_changed) + m_cache.Save(); } } diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.h b/Source/Core/DolphinQt2/GameList/GameTracker.h index b063b7dcbe..500f1b9aac 100644 --- a/Source/Core/DolphinQt2/GameList/GameTracker.h +++ b/Source/Core/DolphinQt2/GameList/GameTracker.h @@ -4,15 +4,17 @@ #pragma once +#include + #include #include #include -#include #include #include "Common/WorkQueueThread.h" -#include "DolphinQt2/GameList/GameFile.h" -#include "DolphinQt2/GameList/GameFileCache.h" +#include "Core/TitleDatabase.h" +#include "UICommon/GameFile.h" +#include "UICommon/GameFileCache.h" // Watches directories and loads GameFiles in a separate thread. // To use this, just add directories using AddDirectory, and listen for the @@ -28,7 +30,7 @@ public: void RemoveDirectory(const QString& dir); signals: - void GameLoaded(QSharedPointer game); + void GameLoaded(std::shared_ptr game); void GameRemoved(const QString& path); private: @@ -40,7 +42,8 @@ private: // game path -> directories that track it QMap> m_tracked_files; Common::WorkQueueThread m_load_thread; - GameFileCache cache; + UICommon::GameFileCache m_cache; + Core::TitleDatabase m_title_database; }; -Q_DECLARE_METATYPE(QSharedPointer) +Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/Source/Core/DolphinQt2/GameList/GridProxyModel.cpp b/Source/Core/DolphinQt2/GameList/GridProxyModel.cpp index e48d4a337b..e6af8114be 100644 --- a/Source/Core/DolphinQt2/GameList/GridProxyModel.cpp +++ b/Source/Core/DolphinQt2/GameList/GridProxyModel.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include #include #include "DolphinQt2/GameList/GameListModel.h" diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 811d4c79af..073e6341a6 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -386,7 +387,7 @@ void MainWindow::Play(const std::optional& savestate_path) } else { - QSharedPointer selection = m_game_list->GetSelectedGame(); + std::shared_ptr selection = m_game_list->GetSelectedGame(); if (selection) { StartGame(selection->GetFilePath(), savestate_path); @@ -394,7 +395,7 @@ void MainWindow::Play(const std::optional& savestate_path) } else { - auto default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO); + QString default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO); if (!default_path.isEmpty() && QFile::exists(default_path)) { StartGame(default_path, savestate_path); @@ -536,7 +537,13 @@ void MainWindow::ScreenShot() void MainWindow::StartGame(const QString& path, const std::optional& savestate_path) { - StartGame(BootParameters::GenerateFromFile(path.toStdString(), savestate_path)); + StartGame(path.toStdString(), savestate_path); +} + +void MainWindow::StartGame(const std::string& path, + const std::optional& savestate_path) +{ + StartGame(BootParameters::GenerateFromFile(path, savestate_path)); } void MainWindow::StartGame(std::unique_ptr&& parameters) diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 8c57a91e74..dadb6803b8 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -11,6 +11,7 @@ #include #include +#include #include "DolphinQt2/GameList/GameList.h" #include "DolphinQt2/MenuBar.h" @@ -94,6 +95,7 @@ private: void InitCoreCallbacks(); void StartGame(const QString& path, const std::optional& savestate_path = {}); + void StartGame(const std::string& path, const std::optional& savestate_path = {}); void StartGame(std::unique_ptr&& parameters); void ShowRenderWidget(); void HideRenderWidget(); diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index 2563160f25..c9bc84a1bd 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -34,10 +34,11 @@ #include "DiscIO/WiiSaveBanner.h" #include "DolphinQt2/AboutDialog.h" -#include "DolphinQt2/GameList/GameFile.h" #include "DolphinQt2/QtUtils/ActionHelper.h" #include "DolphinQt2/Settings.h" +#include "UICommon/GameFile.h" + MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent) { AddFileMenu(); @@ -593,8 +594,9 @@ void MenuBar::InstallWAD() QMessageBox result_dialog(this); - if (GameFile(wad_file).Install()) + if (WiiUtils::InstallWAD(wad_file.toStdString())) { + Settings::Instance().NANDRefresh(); result_dialog.setIcon(QMessageBox::Information); result_dialog.setText(tr("Successfully installed this title to the NAND.")); } @@ -698,12 +700,12 @@ void MenuBar::NANDExtractCertificates() } } -void MenuBar::OnSelectionChanged(QSharedPointer game_file) +void MenuBar::OnSelectionChanged(std::shared_ptr game_file) { - bool is_null = game_file.isNull(); + const bool game_selected = !!game_file; - m_recording_play->setEnabled(!Core::IsRunning() && !is_null); - m_recording_start->setEnabled(!Movie::IsPlayingInput() && !is_null); + m_recording_play->setEnabled(game_selected && !Core::IsRunning()); + m_recording_start->setEnabled(game_selected && !Movie::IsPlayingInput()); } void MenuBar::OnRecordingStatusChanged(bool recording) diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index 3e80ed8cf9..b2fb2d9275 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -5,13 +5,12 @@ #pragma once #include +#include #include #include #include -#include "DolphinQt2/GameList/GameFile.h" - namespace Core { enum class State; @@ -22,6 +21,11 @@ namespace DiscIO enum class Region; }; +namespace UICommon +{ +class GameFile; +} + class MenuBar final : public QMenuBar { Q_OBJECT @@ -89,7 +93,7 @@ signals: void ExportRecording(); void ShowTASInput(); - void SelectionChanged(QSharedPointer game_file); + void SelectionChanged(std::shared_ptr game_file); void RecordingStatusChanged(bool recording); void ReadOnlyModeChanged(bool read_only); @@ -120,7 +124,7 @@ private: void CheckNAND(); void NANDExtractCertificates(); - void OnSelectionChanged(QSharedPointer game_file); + void OnSelectionChanged(std::shared_ptr game_file); void OnRecordingStatusChanged(bool recording); void OnReadOnlyModeChanged(bool read_only); void OnDebugModeToggled(bool enabled); diff --git a/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp b/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp index 71155abab5..00c5a36ea4 100644 --- a/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp +++ b/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp @@ -51,7 +51,7 @@ void GameListDialog::PopulateGameList() for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++) { - auto* item = new QListWidgetItem(game_list_model->GetUniqueID(i)); + auto* item = new QListWidgetItem(game_list_model->GetUniqueIdentifier(i)); m_game_list->addItem(item); } diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp index 77a17486b5..5747b46837 100644 --- a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp @@ -532,7 +532,7 @@ std::string NetPlayDialog::FindGame(const std::string& game) return RunOnObject(this, [this, game] { for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) { - if (m_game_list_model->GetUniqueID(i).toStdString() == game) + if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game) return m_game_list_model->GetPath(i).toStdString(); } return std::string(""); diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp index 922186d9ca..89fe579d5f 100644 --- a/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp +++ b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp @@ -245,7 +245,7 @@ void NetPlaySetupDialog::PopulateGameList() m_host_games->clear(); for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) { - auto title = m_game_list_model->GetUniqueID(i); + auto title = m_game_list_model->GetUniqueIdentifier(i); auto path = m_game_list_model->GetPath(i); auto* item = new QListWidgetItem(title); diff --git a/Source/Core/DolphinQt2/QtUtils/ImageConverter.cpp b/Source/Core/DolphinQt2/QtUtils/ImageConverter.cpp new file mode 100644 index 0000000000..32fadf788e --- /dev/null +++ b/Source/Core/DolphinQt2/QtUtils/ImageConverter.cpp @@ -0,0 +1,33 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/QtUtils/ImageConverter.h" + +#include + +#include + +#include "Common/CommonTypes.h" +#include "UICommon/GameFile.h" + +QPixmap ToQPixmap(const UICommon::GameBanner& banner) +{ + return ToQPixmap(banner.buffer, banner.width, banner.height); +} + +QPixmap ToQPixmap(const std::vector& buffer, int width, int height) +{ + QImage image(width, height, QImage::Format_RGB888); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + const u32 color = buffer[y * width + x]; + image.setPixel( + x, y, qRgb((color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF) >> 0)); + } + } + + return QPixmap::fromImage(image); +} diff --git a/Source/Core/DolphinQt2/QtUtils/ImageConverter.h b/Source/Core/DolphinQt2/QtUtils/ImageConverter.h new file mode 100644 index 0000000000..98a07fc89c --- /dev/null +++ b/Source/Core/DolphinQt2/QtUtils/ImageConverter.h @@ -0,0 +1,19 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class QPixmap; + +namespace UICommon +{ +struct GameBanner; +} + +QPixmap ToQPixmap(const UICommon::GameBanner& banner); +QPixmap ToQPixmap(const std::vector& buffer, int width, int height); diff --git a/Source/Core/DolphinQt2/Resources.h b/Source/Core/DolphinQt2/Resources.h index 0d2909441b..bb93d4ce61 100644 --- a/Source/Core/DolphinQt2/Resources.h +++ b/Source/Core/DolphinQt2/Resources.h @@ -5,6 +5,7 @@ #pragma once #include +#include // Store for various QPixmaps that will be used repeatedly. class Resources final diff --git a/Source/Core/DolphinWX/CMakeLists.txt b/Source/Core/DolphinWX/CMakeLists.txt index d1ed79c781..8c101c0395 100644 --- a/Source/Core/DolphinWX/CMakeLists.txt +++ b/Source/Core/DolphinWX/CMakeLists.txt @@ -67,7 +67,6 @@ set(SRCS FrameAui.cpp FrameTools.cpp GameListCtrl.cpp - ISOFile.cpp LogConfigWindow.cpp LogWindow.cpp Main.cpp diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj b/Source/Core/DolphinWX/DolphinWX.vcxproj index 1904e297ff..417fe3be09 100644 --- a/Source/Core/DolphinWX/DolphinWX.vcxproj +++ b/Source/Core/DolphinWX/DolphinWX.vcxproj @@ -111,7 +111,6 @@ - @@ -194,7 +193,6 @@ - @@ -299,4 +297,4 @@ - + \ No newline at end of file diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters index 5555786156..9eaf714335 100644 --- a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters +++ b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters @@ -19,9 +19,6 @@ {80626e3b-e13b-41c3-bd63-4ef1faf92924} - - {4352dc64-398e-4a96-ba4a-824dffa2004c} - {d6bc4dd6-06ed-46ad-b327-04afb26e10ec} @@ -154,9 +151,6 @@ GUI\Video - - Misc - GUI @@ -381,9 +375,6 @@ GUI\Video - - Misc - Resources @@ -507,4 +498,4 @@ - + \ No newline at end of file diff --git a/Source/Core/DolphinWX/FrameTools.cpp b/Source/Core/DolphinWX/FrameTools.cpp index ded328f7bc..a61b98f9c2 100644 --- a/Source/Core/DolphinWX/FrameTools.cpp +++ b/Source/Core/DolphinWX/FrameTools.cpp @@ -77,7 +77,6 @@ #include "DolphinWX/Frame.h" #include "DolphinWX/GameListCtrl.h" #include "DolphinWX/Globals.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/Input/HotkeyInputConfigDiag.h" #include "DolphinWX/Input/InputConfigDiag.h" #include "DolphinWX/LogWindow.h" @@ -92,6 +91,7 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "UICommon/GameFile.h" #include "UICommon/UICommon.h" #include "VideoCommon/RenderBase.h" @@ -318,7 +318,7 @@ void CFrame::BootGame(const std::string& filename, const std::optionalGetSelectedISO() != nullptr) { if (m_game_list_ctrl->GetSelectedISO()->IsValid()) - bootfile = m_game_list_ctrl->GetSelectedISO()->GetFileName(); + bootfile = m_game_list_ctrl->GetSelectedISO()->GetFilePath(); } else if (!StartUp.m_strDefaultISO.empty() && File::Exists(StartUp.m_strDefaultISO)) { @@ -1229,10 +1229,10 @@ void CFrame::OnInstallWAD(wxCommandEvent& event) { case IDM_LIST_INSTALL_WAD: { - const GameListItem* iso = m_game_list_ctrl->GetSelectedISO(); + const UICommon::GameFile* iso = m_game_list_ctrl->GetSelectedISO(); if (!iso) return; - fileName = iso->GetFileName(); + fileName = iso->GetFilePath(); break; } case IDM_MENU_INSTALL_WAD: @@ -1258,7 +1258,7 @@ void CFrame::OnInstallWAD(wxCommandEvent& event) void CFrame::OnUninstallWAD(wxCommandEvent&) { - const GameListItem* file = m_game_list_ctrl->GetSelectedISO(); + const UICommon::GameFile* file = m_game_list_ctrl->GetSelectedISO(); if (!file) return; @@ -1268,9 +1268,8 @@ void CFrame::OnUninstallWAD(wxCommandEvent&) return; } - u64 title_id = file->GetTitleID(); - IOS::HLE::Kernel ios; - if (ios.GetES()->DeleteTitleContent(title_id) < 0) + const u64 title_id = file->GetTitleID(); + if (!WiiUtils::UninstallTitle(title_id)) { PanicAlertT("Failed to remove this title from the NAND."); return; @@ -1490,11 +1489,11 @@ void CFrame::OnPerformOnlineWiiUpdate(wxCommandEvent& event) void CFrame::OnPerformDiscWiiUpdate(wxCommandEvent&) { - const GameListItem* iso = m_game_list_ctrl->GetSelectedISO(); + const UICommon::GameFile* iso = m_game_list_ctrl->GetSelectedISO(); if (!iso) return; - const std::string file_name = iso->GetFileName(); + const std::string file_name = iso->GetFilePath(); const WiiUtils::UpdateResult result = ShowUpdateProgress(this, WiiUtils::DoDiscUpdate, file_name); ShowUpdateResult(result); diff --git a/Source/Core/DolphinWX/GameListCtrl.cpp b/Source/Core/DolphinWX/GameListCtrl.cpp index 99e66ba21e..3418e9c7f8 100644 --- a/Source/Core/DolphinWX/GameListCtrl.cpp +++ b/Source/Core/DolphinWX/GameListCtrl.cpp @@ -4,13 +4,11 @@ #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -39,9 +37,7 @@ #include "Common/CDUtils.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" -#include "Common/FileSearch.h" #include "Common/FileUtil.h" -#include "Common/MathUtil.h" #include "Common/StringUtil.h" #include "Common/SysConf.h" #include "Common/Thread.h" @@ -53,18 +49,20 @@ #include "Core/HW/WiiSaveCrypted.h" #include "Core/Movie.h" #include "Core/TitleDatabase.h" +#include "Core/WiiUtils.h" #include "DiscIO/Blob.h" -#include "DiscIO/DirectoryBlob.h" #include "DiscIO/Enums.h" #include "DiscIO/Volume.h" #include "DolphinWX/Frame.h" #include "DolphinWX/GameListCtrl.h" #include "DolphinWX/Globals.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/ISOProperties/ISOProperties.h" #include "DolphinWX/Main.h" #include "DolphinWX/NetPlay/NetPlayLauncher.h" #include "DolphinWX/WxUtils.h" +#include "UICommon/GameFile.h" +#include "UICommon/GameFileCache.h" +#include "UICommon/UICommon.h" struct CompressionProgress final { @@ -82,11 +80,9 @@ public: wxProgressDialog* dialog; }; -static constexpr u32 CACHE_REVISION = 6; // Last changed in PR 6109 - static bool sorted = false; -static int CompareGameListItems(const GameListItem* iso1, const GameListItem* iso2, +static int CompareGameListItems(const UICommon::GameFile* iso1, const UICommon::GameFile* iso2, long sortData = GameListCtrl::COLUMN_TITLE) { int t = 1; @@ -101,14 +97,14 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is { case GameListCtrl::COLUMN_MAKER: { - int maker_cmp = strcasecmp(iso1->GetCompany().c_str(), iso2->GetCompany().c_str()) * t; + int maker_cmp = strcasecmp(iso1->GetMaker().c_str(), iso2->GetMaker().c_str()) * t; if (maker_cmp != 0) return maker_cmp; break; } case GameListCtrl::COLUMN_FILENAME: - return wxStricmp(wxFileNameFromPath(iso1->GetFileName()), - wxFileNameFromPath(iso2->GetFileName())) * + return wxStricmp(wxFileNameFromPath(iso1->GetFilePath()), + wxFileNameFromPath(iso2->GetFilePath())) * t; case GameListCtrl::COLUMN_ID: { @@ -162,8 +158,8 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is if (iso1->GetDiscNumber() != iso2->GetDiscNumber()) return t * (iso1->GetDiscNumber() > iso2->GetDiscNumber() ? 1 : -1); - wxString iso1_filename = wxFileNameFromPath(iso1->GetFileName()); - wxString iso2_filename = wxFileNameFromPath(iso2->GetFileName()); + wxString iso1_filename = wxFileNameFromPath(iso1->GetFilePath()); + wxString iso2_filename = wxFileNameFromPath(iso2->GetFilePath()); if (iso1_filename != iso2_filename) return t * wxStricmp(iso1_filename, iso2_filename); @@ -171,7 +167,7 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is return 0; } -static bool ShouldDisplayGameListItem(const GameListItem& item) +static bool ShouldDisplayGameListItem(const UICommon::GameFile& item) { const bool show_platform = [&item] { switch (item.GetPlatform()) @@ -281,9 +277,9 @@ GameListCtrl::GameListCtrl(bool disable_scanning, wxWindow* parent, const wxWind if (!disable_scanning) { m_scan_thread = std::thread([&] { - Common::SetCurrentThreadName("gamelist scanner"); + Common::SetCurrentThreadName("Game list scanner"); - if (SyncCacheFile(false)) + if (m_cache.Load()) QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST)); // Always do an initial scan to catch new files and perform the more expensive per-file @@ -422,11 +418,10 @@ void GameListCtrl::RefreshList() m_shown_files.clear(); { std::unique_lock lk(m_cache_mutex); - for (auto& item : m_cached_files) - { - if (ShouldDisplayGameListItem(*item)) - m_shown_files.push_back(item); - } + m_cache.ForEach([this](const std::shared_ptr& game_file) { + if (ShouldDisplayGameListItem(*game_file)) + m_shown_files.push_back(game_file); + }); } // Drives are not cached. Not sure if this is required, but better to err on the @@ -436,7 +431,7 @@ void GameListCtrl::RefreshList() std::unique_lock lk(m_title_database_mutex); for (const auto& drive : cdio_get_devices()) { - auto file = std::make_shared(drive); + auto file = std::make_shared(drive); if (file->IsValid()) { if (file->EmuStateChanged()) @@ -538,24 +533,6 @@ void GameListCtrl::RefreshList() SetFocus(); } -static wxString NiceSizeFormat(u64 size) -{ - // Return a pretty filesize string from byte count. - // e.g. 1134278 -> "1.08 MiB" - - const char* const unit_symbols[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}; - - // Find largest power of 2 less than size. - // div 10 to get largest named unit less than size - // 10 == log2(1024) (number of B in a KiB, KiB in a MiB, etc) - // Max value is 63 / 10 = 6 - const int unit = IntLog2(std::max(size, 1)) / 10; - - // Don't need exact values, only 5 most significant digits - double unit_size = std::pow(2, unit * 10); - return wxString::Format("%.2f %s", size / unit_size, unit_symbols[unit]); -} - // Update the column content of the item at index void GameListCtrl::UpdateItemAtColumn(long index, int column) { @@ -573,11 +550,11 @@ void GameListCtrl::UpdateItemAtColumn(long index, int column) { int image_index = m_image_indexes.utility_banner[0]; // nobanner - if (iso_file.GetBannerImage().IsOk()) + wxImage banner = WxUtils::ToWxImage(iso_file.GetBannerImage()); + if (banner.IsOk()) { wxImageList* img_list = GetImageList(wxIMAGE_LIST_SMALL); - image_index = img_list->Add( - WxUtils::ScaleImageToBitmap(iso_file.GetBannerImage(), this, img_list->GetSize())); + image_index = img_list->Add(WxUtils::ScaleImageToBitmap(banner, this, img_list->GetSize())); } SetItemColumnImage(index, COLUMN_BANNER, image_index); @@ -599,10 +576,10 @@ void GameListCtrl::UpdateItemAtColumn(long index, int column) break; } case COLUMN_MAKER: - SetItem(index, COLUMN_MAKER, StrToWxStr(iso_file.GetCompany()), -1); + SetItem(index, COLUMN_MAKER, StrToWxStr(iso_file.GetMaker()), -1); break; case COLUMN_FILENAME: - SetItem(index, COLUMN_FILENAME, wxFileNameFromPath(StrToWxStr(iso_file.GetFileName())), -1); + SetItem(index, COLUMN_FILENAME, wxFileNameFromPath(StrToWxStr(iso_file.GetFilePath())), -1); break; case COLUMN_EMULATION_STATE: SetItemColumnImage(index, COLUMN_EMULATION_STATE, @@ -613,7 +590,7 @@ void GameListCtrl::UpdateItemAtColumn(long index, int column) m_image_indexes.flag[static_cast(iso_file.GetCountry())]); break; case COLUMN_SIZE: - SetItem(index, COLUMN_SIZE, NiceSizeFormat(iso_file.GetFileSize()), -1); + SetItem(index, COLUMN_SIZE, UICommon::FormatSize(iso_file.GetFileSize()), -1); break; case COLUMN_ID: SetItem(index, COLUMN_ID, iso_file.GetGameID(), -1); @@ -676,76 +653,6 @@ void GameListCtrl::SetColors() } } -void GameListCtrl::DoState(PointerWrap* p, u32 size) -{ - struct - { - u32 Revision; - u32 ExpectedSize; - } header = {CACHE_REVISION, size}; - p->Do(header); - if (p->GetMode() == PointerWrap::MODE_READ) - { - if (header.Revision != CACHE_REVISION || header.ExpectedSize != size) - { - p->SetMode(PointerWrap::MODE_MEASURE); - return; - } - } - p->DoEachElement(m_cached_files, [](PointerWrap& state, std::shared_ptr& elem) { - if (state.GetMode() == PointerWrap::MODE_READ) - { - elem = std::make_shared(); - } - elem->DoState(state); - }); -} - -bool GameListCtrl::SyncCacheFile(bool write) -{ - std::string filename(File::GetUserPath(D_CACHE_IDX) + "wx_gamelist.cache"); - const char* open_mode = write ? "wb" : "rb"; - File::IOFile f(filename, open_mode); - if (!f) - return false; - bool success = false; - if (write) - { - // Measure the size of the buffer. - u8* ptr = nullptr; - PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); - DoState(&p); - const size_t buffer_size = reinterpret_cast(ptr); - - // Then actually do the write. - std::vector buffer(buffer_size); - ptr = &buffer[0]; - p.SetMode(PointerWrap::MODE_WRITE); - DoState(&p, buffer_size); - if (f.WriteBytes(buffer.data(), buffer.size())) - success = true; - } - else - { - std::vector buffer(f.GetSize()); - if (buffer.size() && f.ReadBytes(buffer.data(), buffer.size())) - { - u8* ptr = buffer.data(); - PointerWrap p(&ptr, PointerWrap::MODE_READ); - DoState(&p, buffer.size()); - if (p.GetMode() == PointerWrap::MODE_READ) - success = true; - } - } - if (!success) - { - // If some file operation failed, try to delete the probably-corrupted cache - f.Close(); - File::Delete(filename); - } - return success; -} - void GameListCtrl::RescanList() { auto post_status = [&](const wxString& status) { @@ -757,107 +664,39 @@ void GameListCtrl::RescanList() post_status(_("Scanning...")); - const std::vector search_extensions = {".gcm", ".tgc", ".iso", ".ciso", ".gcz", - ".wbfs", ".wad", ".dol", ".elf"}; - // TODO This could process paths iteratively as they are found - const std::vector search_results_vector = - Common::DoFileSearch(SConfig::GetInstance().m_ISOFolder, search_extensions, - SConfig::GetInstance().m_RecursiveISOFolder); - - // Copy search results into a set, except ones that match DiscIO::ShouldHideFromGameList. - // TODO Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all? - // TODO Make DoFileSearch support filter predicates so we don't have remove things afterwards? - std::unordered_set search_results; - search_results.reserve(search_results_vector.size()); - for (const std::string& path : search_results_vector) - { - if (!DiscIO::ShouldHideFromGameList(path)) - search_results.insert(path); - } + const std::vector game_paths = UICommon::FindAllGamePaths( + SConfig::GetInstance().m_ISOFolder, SConfig::GetInstance().m_RecursiveISOFolder); // Reload the TitleDatabase { - std::unique_lock lk(m_title_database_mutex); + std::unique_lock lock(m_title_database_mutex); m_title_database = {}; } bool cache_changed = false; + { std::unique_lock lk(m_cache_mutex); - - // Delete paths that aren't in search_results from m_cached_files, - // while simultaneously deleting paths that aren't in m_cached_files from search_results. - // For the sake of speed, we don't care about maintaining the order of m_cached_files. + if (m_cache.Update(game_paths)) { - auto it = m_cached_files.begin(); - auto end = m_cached_files.end(); - while (it != end) - { - if (search_results.erase((*it)->GetFileName())) - { - ++it; - } - else - { - cache_changed = true; - --end; - *it = std::move(*end); - } - } - m_cached_files.erase(it, m_cached_files.end()); - } - - // Now that the previous loop has run, search_results only contains paths that - // aren't in m_cached_files, so we simply add all of them to m_cached_files. - for (const auto& path : search_results) - { - auto file = std::make_shared(path); - if (file->IsValid()) - { - cache_changed = true; - m_cached_files.push_back(std::move(file)); - } + cache_changed = true; + QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST)); } } - // The common case is that just a file has been added/removed, so trigger a refresh ASAP with the - // assumption that other properties of files will not change at the same time (which will be fine - // and just causes a double refresh). - if (cache_changed) - QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST)); - // If any cached files need updates, apply the updates to a copy and delete the original - this - // makes the UI thread's use of cached files safe. Note however, it is assumed that RefreshList - // will not iterate m_cached_files while the scan thread is modifying the list itself. - bool refresh_needed = false; { std::unique_lock lk(m_cache_mutex); - for (auto& file : m_cached_files) + if (m_cache.UpdateAdditionalMetadata(m_title_database)) { - bool emu_state_changed = file->EmuStateChanged(); - bool banner_changed = file->BannerChanged(); - bool custom_title_changed = file->CustomNameChanged(m_title_database); - if (emu_state_changed || banner_changed || custom_title_changed) - { - cache_changed = refresh_needed = true; - auto copy = std::make_shared(*file); - if (emu_state_changed) - copy->EmuStateCommit(); - if (banner_changed) - copy->BannerCommit(); - if (custom_title_changed) - copy->CustomNameCommit(); - file = std::move(copy); - } + cache_changed = true; + QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST)); } } - // Only post UI event to update the displayed list if something actually changed - if (refresh_needed) - QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST)); post_status(""); if (cache_changed) - SyncCacheFile(true); + m_cache.Save(); } void GameListCtrl::OnRefreshGameList(wxCommandEvent& WXUNUSED(event)) @@ -871,7 +710,7 @@ void GameListCtrl::OnRescanGameList(wxCommandEvent& event) { // Knock out the cache on a purge event std::unique_lock lk(m_cache_mutex); - m_cached_files.clear(); + m_cache.Clear(); } m_scan_trigger.Set(); } @@ -893,7 +732,7 @@ void GameListCtrl::OnColBeginDrag(wxListEvent& event) event.Veto(); } -const GameListItem* GameListCtrl::GetISO(size_t index) const +const UICommon::GameFile* GameListCtrl::GetISO(size_t index) const { if (index < m_shown_files.size()) return m_shown_files[index].get(); @@ -907,8 +746,8 @@ static int wxCALLBACK wxListCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sor // return 1 if item1 > item2 // return -1 if item1 < item2 // return 0 for identity - const GameListItem* iso1 = caller->GetISO(item1); - const GameListItem* iso2 = caller->GetISO(item2); + const UICommon::GameFile* iso1 = caller->GetISO(item1); + const UICommon::GameFile* iso2 = caller->GetISO(item2); if (iso1 == iso2) return 0; @@ -1034,7 +873,7 @@ void GameListCtrl::OnMouseMotion(wxMouseEvent& event) // Emulation status static const char* const emuState[] = {"Broken", "Intro", "In-Game", "Playable", "Perfect"}; - const GameListItem* iso = GetISO(GetItemData(item)); + const UICommon::GameFile* iso = GetISO(GetItemData(item)); const int emu_state = iso->GetEmuState(); const std::string& issues = iso->GetIssues(); @@ -1093,22 +932,6 @@ void GameListCtrl::OnLeftClick(wxMouseEvent& event) event.Skip(); } -static bool IsWADInstalled(const GameListItem& wad) -{ - const std::string content_dir = - Common::GetTitleContentPath(wad.GetTitleID(), Common::FromWhichRoot::FROM_CONFIGURED_ROOT); - - if (!File::IsDirectory(content_dir)) - return false; - - // Since this isn't IOS and we only need a simple way to figure out if a title is installed, - // we make the (reasonable) assumption that having more than just the TMD in the content - // directory means that the title is installed. - const auto entries = File::ScanDirectoryTree(content_dir, false); - return std::any_of(entries.children.begin(), entries.children.end(), - [](const auto& file) { return file.virtualName != "title.tmd"; }); -} - void GameListCtrl::OnRightClick(wxMouseEvent& event) { // Focus the clicked item. @@ -1125,7 +948,7 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event) } if (GetSelectedItemCount() == 1) { - const GameListItem* selected_iso = GetSelectedISO(); + const UICommon::GameFile* selected_iso = GetSelectedISO(); if (selected_iso) { wxMenu popupMenu; @@ -1160,7 +983,7 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event) popupMenu.AppendCheckItem(IDM_SET_DEFAULT_ISO, _("Set as &default ISO")); // First we have to decide a starting value when we append it - if (selected_iso->GetFileName() == SConfig::GetInstance().m_strDefaultISO) + if (selected_iso->GetFilePath() == SConfig::GetInstance().m_strDefaultISO) popupMenu.FindItem(IDM_SET_DEFAULT_ISO)->Check(); popupMenu.AppendSeparator(); @@ -1194,7 +1017,7 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event) for (auto* menu_item : {install_wad_item, uninstall_wad_item}) menu_item->Enable(!Core::IsRunning() || !SConfig::GetInstance().bWii); - if (!IsWADInstalled(*selected_iso)) + if (!WiiUtils::IsTitleInstalled(selected_iso->GetTitleID())) uninstall_wad_item->Enable(false); } @@ -1214,7 +1037,7 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event) } } -const GameListItem* GameListCtrl::GetSelectedISO() const +const UICommon::GameFile* GameListCtrl::GetSelectedISO() const { if (m_shown_files.empty()) return nullptr; @@ -1229,9 +1052,9 @@ const GameListItem* GameListCtrl::GetSelectedISO() const return GetISO(GetItemData(item)); } -std::vector GameListCtrl::GetAllSelectedISOs() const +std::vector GameListCtrl::GetAllSelectedISOs() const { - std::vector result; + std::vector result; long item = -1; while (true) { @@ -1257,18 +1080,18 @@ bool GameListCtrl::IsHidingItems() void GameListCtrl::OnOpenContainingFolder(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; - wxFileName path = wxFileName::FileName(StrToWxStr(iso->GetFileName())); + wxFileName path = wxFileName::FileName(StrToWxStr(iso->GetFilePath())); path.MakeAbsolute(); WxUtils::Explore(WxStrToStr(path.GetPath())); } void GameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; std::string path = iso->GetWiiFSPath(); @@ -1278,7 +1101,7 @@ void GameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED(event)) void GameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (iso) CWiiSaveCrypted::ExportWiiSave(iso->GetTitleID()); } @@ -1286,14 +1109,14 @@ void GameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED(event)) // Save this file as the default file void GameListCtrl::OnSetDefaultISO(wxCommandEvent& event) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; if (event.IsChecked()) { // Write the new default value and save it the ini file - SConfig::GetInstance().m_strDefaultISO = iso->GetFileName(); + SConfig::GetInstance().m_strDefaultISO = iso->GetFilePath(); SConfig::GetInstance().SaveSettings(); } else @@ -1313,15 +1136,15 @@ void GameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED(event)) if (wxMessageBox(message, _("Warning"), wxYES_NO | wxICON_EXCLAMATION) == wxYES) { - for (const GameListItem* iso : GetAllSelectedISOs()) - File::Delete(iso->GetFileName()); + for (const UICommon::GameFile* iso : GetAllSelectedISOs()) + File::Delete(iso->GetFilePath()); m_scan_trigger.Set(); } } void GameListCtrl::OnProperties(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; @@ -1331,7 +1154,7 @@ void GameListCtrl::OnProperties(wxCommandEvent& WXUNUSED(event)) void GameListCtrl::OnWiki(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; @@ -1342,7 +1165,7 @@ void GameListCtrl::OnWiki(wxCommandEvent& WXUNUSED(event)) void GameListCtrl::OnNetPlayHost(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; @@ -1380,9 +1203,9 @@ void GameListCtrl::OnMultiDecompressISO(wxCommandEvent& /*event*/) void GameListCtrl::CompressSelection(bool _compress) { - std::vector items_to_compress; + std::vector items_to_compress; bool wii_compression_warning_accepted = false; - for (const GameListItem* iso : GetAllSelectedISOs()) + for (const UICommon::GameFile* iso : GetAllSelectedISOs()) { // Don't include items that we can't do anything with if (iso->GetPlatform() != DiscIO::Platform::GAMECUBE_DISC && @@ -1425,12 +1248,12 @@ void GameListCtrl::CompressSelection(bool _compress) CompressionProgress progress(0, items_to_compress.size(), "", &progressDialog); - for (const GameListItem* iso : items_to_compress) + for (const UICommon::GameFile* iso : items_to_compress) { if (iso->GetBlobType() != DiscIO::BlobType::GCZ && _compress) { std::string FileName; - SplitPath(iso->GetFileName(), nullptr, &FileName, nullptr); + SplitPath(iso->GetFilePath(), nullptr, &FileName, nullptr); progress.current_filename = FileName; FileName.append(".gcz"); @@ -1445,14 +1268,14 @@ void GameListCtrl::CompressSelection(bool _compress) continue; all_good &= - DiscIO::CompressFileToBlob(iso->GetFileName(), OutputFileName, + DiscIO::CompressFileToBlob(iso->GetFilePath(), OutputFileName, (iso->GetPlatform() == DiscIO::Platform::WII_DISC) ? 1 : 0, 16384, &MultiCompressCB, &progress); } else if (iso->GetBlobType() == DiscIO::BlobType::GCZ && !_compress) { std::string FileName; - SplitPath(iso->GetFileName(), nullptr, &FileName, nullptr); + SplitPath(iso->GetFilePath(), nullptr, &FileName, nullptr); progress.current_filename = FileName; if (iso->GetPlatform() == DiscIO::Platform::WII_DISC) FileName.append(".iso"); @@ -1469,7 +1292,7 @@ void GameListCtrl::CompressSelection(bool _compress) _("Confirm File Overwrite"), wxYES_NO) == wxNO) continue; - all_good &= DiscIO::DecompressBlobToFile(iso->GetFileName().c_str(), OutputFileName.c_str(), + all_good &= DiscIO::DecompressBlobToFile(iso->GetFilePath().c_str(), OutputFileName.c_str(), &MultiCompressCB, &progress); } @@ -1490,7 +1313,7 @@ bool GameListCtrl::CompressCB(const std::string& text, float percent, void* arg) void GameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso) return; @@ -1498,7 +1321,7 @@ void GameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) wxString path; std::string FileName, FilePath, FileExtension; - SplitPath(iso->GetFileName(), &FilePath, &FileName, &FileExtension); + SplitPath(iso->GetFilePath(), &FilePath, &FileName, &FileExtension); do { @@ -1543,10 +1366,10 @@ void GameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) if (is_compressed) all_good = - DiscIO::DecompressBlobToFile(iso->GetFileName(), WxStrToStr(path), &CompressCB, &dialog); + DiscIO::DecompressBlobToFile(iso->GetFilePath(), WxStrToStr(path), &CompressCB, &dialog); else all_good = DiscIO::CompressFileToBlob( - iso->GetFileName(), WxStrToStr(path), + iso->GetFilePath(), WxStrToStr(path), (iso->GetPlatform() == DiscIO::Platform::WII_DISC) ? 1 : 0, 16384, &CompressCB, &dialog); } @@ -1558,10 +1381,10 @@ void GameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) void GameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event)) { - const GameListItem* iso = GetSelectedISO(); + const UICommon::GameFile* iso = GetSelectedISO(); if (!iso || !Core::IsRunning()) return; - Core::RunAsCPUThread([&iso] { DVDInterface::ChangeDisc(WxStrToStr(iso->GetFileName())); }); + Core::RunAsCPUThread([&iso] { DVDInterface::ChangeDisc(WxStrToStr(iso->GetFilePath())); }); } void GameListCtrl::OnSize(wxSizeEvent& event) diff --git a/Source/Core/DolphinWX/GameListCtrl.h b/Source/Core/DolphinWX/GameListCtrl.h index 0f39eb3b6d..6ada590d0e 100644 --- a/Source/Core/DolphinWX/GameListCtrl.h +++ b/Source/Core/DolphinWX/GameListCtrl.h @@ -16,7 +16,12 @@ #include "Common/ChunkFile.h" #include "Common/Event.h" #include "Common/Flag.h" -#include "DolphinWX/ISOFile.h" +#include "UICommon/GameFileCache.h" + +namespace UICommon +{ +class GameFile; +} class wxEmuStateTip : public wxTipWindow { @@ -46,8 +51,8 @@ public: ~GameListCtrl(); void BrowseForDirectory(); - const GameListItem* GetISO(size_t index) const; - const GameListItem* GetSelectedISO() const; + const UICommon::GameFile* GetISO(size_t index) const; + const UICommon::GameFile* GetSelectedISO() const; static bool IsHidingItems(); @@ -80,9 +85,7 @@ private: void SetColors(); void RefreshList(); void RescanList(); - void DoState(PointerWrap* p, u32 size = 0); - bool SyncCacheFile(bool write); - std::vector GetAllSelectedISOs() const; + std::vector GetAllSelectedISOs() const; // events void OnRefreshGameList(wxCommandEvent& event); @@ -124,9 +127,9 @@ private: std::vector emu_state; } m_image_indexes; - // Actual backing GameListItems are maintained in a background thread and cached to file - std::vector> m_cached_files; - // Locks the list, not the contents + // Actual backing GameFiles are maintained in a background thread and cached to file + UICommon::GameFileCache m_cache; + // Locks the cache object, not the shared_ptrs obtained from it std::mutex m_cache_mutex; Core::TitleDatabase m_title_database; std::mutex m_title_database_mutex; @@ -134,7 +137,7 @@ private: Common::Event m_scan_trigger; Common::Flag m_scan_exiting; // UI thread's view into the cache - std::vector> m_shown_files; + std::vector> m_shown_files; int m_last_column; int m_last_sort; diff --git a/Source/Core/DolphinWX/ISOFile.cpp b/Source/Core/DolphinWX/ISOFile.cpp deleted file mode 100644 index 65e5201dd6..0000000000 --- a/Source/Core/DolphinWX/ISOFile.cpp +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Common/ChunkFile.h" -#include "Common/CommonPaths.h" -#include "Common/CommonTypes.h" -#include "Common/FileUtil.h" -#include "Common/Hash.h" -#include "Common/IniFile.h" -#include "Common/NandPaths.h" -#include "Common/StringUtil.h" - -#include "Core/Boot/Boot.h" -#include "Core/ConfigManager.h" -#include "Core/IOS/ES/Formats.h" -#include "Core/TitleDatabase.h" - -#include "DiscIO/Blob.h" -#include "DiscIO/Enums.h" -#include "DiscIO/Volume.h" -#include "DiscIO/WiiSaveBanner.h" - -#include "DolphinWX/ISOFile.h" -#include "DolphinWX/WxUtils.h" - -static std::string GetLanguageString(DiscIO::Language language, - std::map strings) -{ - auto end = strings.end(); - auto it = strings.find(language); - if (it != end) - return it->second; - - // English tends to be a good fallback when the requested language isn't available - if (language != DiscIO::Language::LANGUAGE_ENGLISH) - { - it = strings.find(DiscIO::Language::LANGUAGE_ENGLISH); - if (it != end) - return it->second; - } - - // If English isn't available either, just pick something - if (!strings.empty()) - return strings.cbegin()->second; - - return ""; -} - -GameListItem::GameListItem(const std::string& filename) - : m_file_name(filename), m_region(DiscIO::Region::UNKNOWN_REGION), - m_country(DiscIO::Country::COUNTRY_UNKNOWN) -{ - { - std::unique_ptr volume(DiscIO::CreateVolumeFromFilename(m_file_name)); - if (volume != nullptr) - { - m_platform = volume->GetVolumeType(); - - m_descriptions = volume->GetDescriptions(); - m_names = volume->GetLongNames(); - if (m_names.empty()) - m_names = volume->GetShortNames(); - m_company = GetLanguageString(DiscIO::Language::LANGUAGE_ENGLISH, volume->GetLongMakers()); - if (m_company.empty()) - m_company = GetLanguageString(DiscIO::Language::LANGUAGE_ENGLISH, volume->GetShortMakers()); - - m_region = volume->GetRegion(); - m_country = volume->GetCountry(); - m_blob_type = volume->GetBlobType(); - m_file_size = volume->GetRawSize(); - m_volume_size = volume->GetSize(); - - m_game_id = volume->GetGameID(); - m_title_id = volume->GetTitleID().value_or(0); - m_disc_number = volume->GetDiscNumber().value_or(0); - m_revision = volume->GetRevision().value_or(0); - - auto& banner = m_volume_banner; - std::vector buffer = volume->GetBanner(&banner.width, &banner.height); - ReadVolumeBanner(&banner.buffer, buffer, banner.width, banner.height); - - m_valid = true; - } - } - - if (m_company.empty() && m_game_id.size() >= 6) - m_company = DiscIO::GetCompanyFromID(m_game_id.substr(4, 2)); - - if (!IsValid() && IsElfOrDol()) - { - m_valid = true; - m_file_size = File::GetSize(m_file_name); - m_platform = DiscIO::Platform::ELF_DOL; - m_blob_type = DiscIO::BlobType::DIRECTORY; - - std::string path, name; - SplitPath(m_file_name, &path, &name, nullptr); - - // A bit like the Homebrew Channel icon, except there can be multiple files - // in a folder with their own icons. Useful for those who don't want to have - // a Homebrew Channel-style folder structure. - if (SetWxBannerFromPNGFile(path + name + ".png")) - return; - - // Homebrew Channel icon. The most typical icon format for DOLs and ELFs. - if (SetWxBannerFromPNGFile(path + "icon.png")) - return; - } - else - { - // Volume banner. Typical for everything that isn't a DOL or ELF. - SetWxBannerFromRaw(m_volume_banner); - } -} - -bool GameListItem::IsValid() const -{ - if (!m_valid) - return false; - - if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id)) - return false; - - return true; -} - -bool GameListItem::CustomNameChanged(const Core::TitleDatabase& title_database) -{ - const auto type = m_platform == DiscIO::Platform::WII_WAD ? - Core::TitleDatabase::TitleType::Channel : - Core::TitleDatabase::TitleType::Other; - m_pending.custom_name = title_database.GetTitleName(m_game_id, type); - return m_custom_name != m_pending.custom_name; -} - -void GameListItem::CustomNameCommit() -{ - m_custom_name = std::move(m_pending.custom_name); -} - -bool GameListItem::EmuStateChanged() -{ - IniFile ini = SConfig::LoadGameIni(m_game_id, m_revision); - ini.GetIfExists("EmuState", "EmulationStateId", &m_pending.emu_state.rating, 0); - ini.GetIfExists("EmuState", "EmulationIssues", &m_pending.emu_state.issues, std::string()); - return m_emu_state != m_pending.emu_state; -} - -void GameListItem::EmuStateCommit() -{ - m_emu_state = std::move(m_pending.emu_state); -} - -void GameListItem::EmuState::DoState(PointerWrap& p) -{ - p.Do(rating); - p.Do(issues); -} - -void GameListItem::Banner::DoState(PointerWrap& p) -{ - p.Do(buffer); - p.Do(width); - p.Do(height); -} - -void GameListItem::DoState(PointerWrap& p) -{ - p.Do(m_valid); - p.Do(m_file_name); - p.Do(m_file_size); - p.Do(m_volume_size); - p.Do(m_names); - p.Do(m_descriptions); - p.Do(m_company); - p.Do(m_game_id); - p.Do(m_title_id); - p.Do(m_region); - p.Do(m_country); - p.Do(m_platform); - p.Do(m_blob_type); - p.Do(m_revision); - p.Do(m_disc_number); - m_volume_banner.DoState(p); - m_emu_state.DoState(p); - p.Do(m_custom_name); - if (p.GetMode() == PointerWrap::MODE_READ) - { - SetWxBannerFromRaw(m_volume_banner); - } -} - -bool GameListItem::IsElfOrDol() const -{ - if (m_file_name.size() < 4) - return false; - - std::string name_end = m_file_name.substr(m_file_name.size() - 4); - std::transform(name_end.begin(), name_end.end(), name_end.begin(), ::tolower); - return name_end == ".elf" || name_end == ".dol"; -} - -void GameListItem::ReadVolumeBanner(std::vector* image, const std::vector& buffer, - int width, int height) -{ - image->resize(width * height * 3); - for (int i = 0; i < width * height; i++) - { - (*image)[i * 3 + 0] = (buffer[i] & 0xFF0000) >> 16; - (*image)[i * 3 + 1] = (buffer[i] & 0x00FF00) >> 8; - (*image)[i * 3 + 2] = (buffer[i] & 0x0000FF) >> 0; - } -} - -bool GameListItem::SetWxBannerFromPNGFile(const std::string& path) -{ - if (!File::Exists(path)) - return false; - - wxImage image(StrToWxStr(path), wxBITMAP_TYPE_PNG); - if (!image.IsOk()) - return false; - - m_banner_wx = image; - return true; -} - -void GameListItem::SetWxBannerFromRaw(const Banner& banner) -{ - if (banner.empty()) - return; - - // Need to make explicit copy as wxImage uses reference counting for copies combined with only - // taking a pointer, not the content, when given a buffer to its constructor. - m_banner_wx.Create(banner.width, banner.height, false); - std::memcpy(m_banner_wx.GetData(), banner.buffer.data(), banner.buffer.size()); -} - -bool GameListItem::BannerChanged() -{ - // Wii banners can only be read if there is a savefile, - // so sometimes caches don't contain banners. Let's check - // if a banner has become available after the cache was made. - - if (!m_volume_banner.empty()) - return false; - if (!DiscIO::IsWii(m_platform)) - return false; - - auto& banner = m_pending.volume_banner; - std::vector buffer = - DiscIO::WiiSaveBanner(m_title_id).GetBanner(&banner.width, &banner.height); - if (buffer.empty()) - return false; - - ReadVolumeBanner(&banner.buffer, buffer, banner.width, banner.height); - // We only reach here if m_volume_banner was empty, so we don't need to explicitly - // compare to see if they are different - return true; -} - -void GameListItem::BannerCommit() -{ - m_volume_banner = std::move(m_pending.volume_banner); - SetWxBannerFromRaw(m_volume_banner); -} - -std::string GameListItem::GetDescription(DiscIO::Language language) const -{ - return GetLanguageString(language, m_descriptions); -} - -std::string GameListItem::GetDescription() const -{ - const bool wii = DiscIO::IsWii(m_platform); - return GetDescription(SConfig::GetInstance().GetCurrentLanguage(wii)); -} - -std::string GameListItem::GetName(DiscIO::Language language) const -{ - return GetLanguageString(language, m_names); -} - -std::string GameListItem::GetName() const -{ - if (!m_custom_name.empty()) - return m_custom_name; - - const bool wii = DiscIO::IsWii(m_platform); - std::string name = GetName(SConfig::GetInstance().GetCurrentLanguage(wii)); - if (!name.empty()) - return name; - - // No usable name, return filename (better than nothing) - std::string ext; - SplitPath(GetFileName(), nullptr, &name, &ext); - return name + ext; -} - -std::string GameListItem::GetUniqueIdentifier() const -{ - const DiscIO::Language lang = DiscIO::Language::LANGUAGE_ENGLISH; - std::vector info; - if (!GetGameID().empty()) - info.push_back(GetGameID()); - if (GetRevision() != 0) - { - std::string rev_str = "Revision "; - info.push_back(rev_str + std::to_string((long long)GetRevision())); - } - - std::string name(GetName(lang)); - if (name.empty()) - name = GetName(); - - int disc_number = GetDiscNumber() + 1; - - std::string lower_name = name; - std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower); - if (disc_number > 1 && - lower_name.find(std::string(wxString::Format("disc %i", disc_number))) == std::string::npos && - lower_name.find(std::string(wxString::Format("disc%i", disc_number))) == std::string::npos) - { - std::string disc_text = "Disc "; - info.push_back(disc_text + std::to_string(disc_number)); - } - if (info.empty()) - return name; - std::ostringstream ss; - std::copy(info.begin(), info.end() - 1, std::ostream_iterator(ss, ", ")); - ss << info.back(); - return name + " (" + ss.str() + ")"; -} - -std::vector GameListItem::GetLanguages() const -{ - std::vector languages; - for (std::pair name : m_names) - languages.push_back(name.first); - return languages; -} - -const std::string GameListItem::GetWiiFSPath() const -{ - if (!DiscIO::IsWii(m_platform)) - return ""; - - const std::string path = Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT); - - if (path[0] == '.') - return WxStrToStr(wxGetCwd()) + path.substr(strlen(ROOT_DIR)); - - return path; -} diff --git a/Source/Core/DolphinWX/ISOFile.h b/Source/Core/DolphinWX/ISOFile.h deleted file mode 100644 index aa9893eaf3..0000000000 --- a/Source/Core/DolphinWX/ISOFile.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include "Common/Common.h" - -#include -#include - -namespace Core -{ -class TitleDatabase; -} - -namespace DiscIO -{ -enum class BlobType; -enum class Country; -enum class Language; -enum class Region; -enum class Platform; -} - -class PointerWrap; - -class GameListItem -{ -public: - GameListItem() = default; - explicit GameListItem(const std::string& file_name); - ~GameListItem() = default; - - bool IsValid() const; - const std::string& GetFileName() const { return m_file_name; } - std::string GetName(DiscIO::Language language) const; - std::string GetName() const; - std::string GetUniqueIdentifier() const; - std::string GetDescription(DiscIO::Language language) const; - std::string GetDescription() const; - std::vector GetLanguages() const; - std::string GetCompany() const { return m_company; } - u16 GetRevision() const { return m_revision; } - const std::string& GetGameID() const { return m_game_id; } - u64 GetTitleID() const { return m_title_id; } - const std::string GetWiiFSPath() const; - DiscIO::Region GetRegion() const { return m_region; } - DiscIO::Country GetCountry() const { return m_country; } - DiscIO::Platform GetPlatform() const { return m_platform; } - DiscIO::BlobType GetBlobType() const { return m_blob_type; } - const std::string& GetIssues() const { return m_emu_state.issues; } - int GetEmuState() const { return m_emu_state.rating; } - u64 GetFileSize() const { return m_file_size; } - u64 GetVolumeSize() const { return m_volume_size; } - // 0 is the first disc, 1 is the second disc - u8 GetDiscNumber() const { return m_disc_number; } - // NOTE: Banner image is at the original resolution, use WxUtils::ScaleImageToBitmap - // to display it - const wxImage& GetBannerImage() const { return m_banner_wx; } - void DoState(PointerWrap& p); - bool BannerChanged(); - void BannerCommit(); - bool EmuStateChanged(); - void EmuStateCommit(); - bool CustomNameChanged(const Core::TitleDatabase& title_database); - void CustomNameCommit(); - -private: - struct EmuState - { - int rating{}; - std::string issues{}; - bool operator!=(const EmuState& rhs) const - { - return rating != rhs.rating || issues != rhs.issues; - } - void DoState(PointerWrap& p); - }; - struct Banner - { - std::vector buffer{}; - int width{}; - int height{}; - bool empty() const { return buffer.empty(); } - void DoState(PointerWrap& p); - }; - - bool IsElfOrDol() const; - void ReadVolumeBanner(std::vector* image, const std::vector& buffer, int width, - int height); - // Outputs to m_banner_wx - bool SetWxBannerFromPNGFile(const std::string& path); - // Outputs to m_banner_wx - void SetWxBannerFromRaw(const Banner& banner); - - // IMPORTANT: Nearly all data members must be save/restored in DoState. - // If anything is changed, make sure DoState handles it properly and - // GameListCtrl::CACHE_REVISION is incremented. - - bool m_valid{}; - std::string m_file_name{}; - - u64 m_file_size{}; - u64 m_volume_size{}; - - std::map m_names{}; - std::map m_descriptions{}; - std::string m_company{}; - std::string m_game_id{}; - u64 m_title_id{}; - - DiscIO::Region m_region{}; - DiscIO::Country m_country{}; - DiscIO::Platform m_platform{}; - DiscIO::BlobType m_blob_type{}; - u16 m_revision{}; - u8 m_disc_number{}; - - Banner m_volume_banner{}; - EmuState m_emu_state{}; - // Overridden name from TitleDatabase - std::string m_custom_name{}; - - // wxImage is not handled in DoState - wxImage m_banner_wx{}; - - // The following data members allow GameListCtrl to construct new GameListItems in a threadsafe - // way. They should not be handled in DoState. - struct - { - EmuState emu_state; - Banner volume_banner; - std::string custom_name; - } m_pending{}; -}; diff --git a/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp b/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp index 8b3d815af0..7107bd3c3c 100644 --- a/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp +++ b/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp @@ -28,7 +28,6 @@ #include "DiscIO/Enums.h" #include "DiscIO/Filesystem.h" #include "DiscIO/Volume.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/WxUtils.h" namespace diff --git a/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp b/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp index 4ee22bcb38..909d988365 100644 --- a/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp +++ b/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp @@ -54,12 +54,12 @@ #include "DolphinWX/DolphinSlider.h" #include "DolphinWX/Frame.h" #include "DolphinWX/Globals.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/ISOProperties/FilesystemPanel.h" #include "DolphinWX/ISOProperties/InfoPanel.h" #include "DolphinWX/Main.h" #include "DolphinWX/PatchAddEdit.h" #include "DolphinWX/WxUtils.h" +#include "UICommon/GameFile.h" // A warning message displayed on the ARCodes and GeckoCodes pages when cheats are // disabled globally to explain why turning cheats on does not work. @@ -190,15 +190,15 @@ EVT_BUTTON(ID_ADDPATCH, CISOProperties::PatchButtonClicked) EVT_BUTTON(ID_REMOVEPATCH, CISOProperties::PatchButtonClicked) END_EVENT_TABLE() -CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id, - const wxString& title, const wxPoint& position, const wxSize& size, - long style) +CISOProperties::CISOProperties(const UICommon::GameFile& game_list_item, wxWindow* parent, + wxWindowID id, const wxString& title, const wxPoint& position, + const wxSize& size, long style) : wxDialog(parent, id, title, position, size, style), m_open_gamelist_item(game_list_item) { Bind(DOLPHIN_EVT_CHANGE_ISO_PROPERTIES_TITLE, &CISOProperties::OnChangeTitle, this); // Load ISO data - m_open_iso = DiscIO::CreateVolumeFromFilename(m_open_gamelist_item.GetFileName()); + m_open_iso = DiscIO::CreateVolumeFromFilename(m_open_gamelist_item.GetFilePath()); m_game_id = m_open_iso->GetGameID(); diff --git a/Source/Core/DolphinWX/ISOProperties/ISOProperties.h b/Source/Core/DolphinWX/ISOProperties/ISOProperties.h index 92d268aa5d..f8be3bc5be 100644 --- a/Source/Core/DolphinWX/ISOProperties/ISOProperties.h +++ b/Source/Core/DolphinWX/ISOProperties/ISOProperties.h @@ -15,8 +15,8 @@ #include #include "Common/IniFile.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/PatchAddEdit.h" +#include "UICommon/GameFile.h" class ActionReplayCodesPanel; class CheatWarningMessage; @@ -52,9 +52,9 @@ wxDECLARE_EVENT(DOLPHIN_EVT_CHANGE_ISO_PROPERTIES_TITLE, wxCommandEvent); class CISOProperties : public wxDialog { public: - CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id = wxID_ANY, - const wxString& title = _("Properties"), const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, + CISOProperties(const UICommon::GameFile& game_list_item, wxWindow* parent, + wxWindowID id = wxID_ANY, const wxString& title = _("Properties"), + const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); virtual ~CISOProperties(); @@ -141,7 +141,7 @@ private: void OnCheatCodeToggled(wxCommandEvent& event); void OnChangeTitle(wxCommandEvent& event); - const GameListItem m_open_gamelist_item; + const UICommon::GameFile m_open_gamelist_item; IniFile m_gameini_default; IniFile m_gameini_local; diff --git a/Source/Core/DolphinWX/ISOProperties/InfoPanel.cpp b/Source/Core/DolphinWX/ISOProperties/InfoPanel.cpp index 45a6121813..ffac4846ac 100644 --- a/Source/Core/DolphinWX/ISOProperties/InfoPanel.cpp +++ b/Source/Core/DolphinWX/ISOProperties/InfoPanel.cpp @@ -32,9 +32,9 @@ #include "Core/IOS/ES/Formats.h" #include "DiscIO/Enums.h" #include "DiscIO/Volume.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/ISOProperties/ISOProperties.h" #include "DolphinWX/WxUtils.h" +#include "UICommon/GameFile.h" namespace { @@ -142,7 +142,7 @@ int FindPreferredLanguageIndex(DiscIO::Language preferred_language, } } // Anonymous namespace -InfoPanel::InfoPanel(wxWindow* parent, wxWindowID id, const GameListItem& item, +InfoPanel::InfoPanel(wxWindow* parent, wxWindowID id, const UICommon::GameFile& item, const std::unique_ptr& opened_iso) : wxPanel{parent, id}, m_game_list_item{item}, m_opened_iso{opened_iso} { @@ -209,8 +209,8 @@ void InfoPanel::LoadBannerDetails() void InfoPanel::LoadBannerImage() { - const auto& banner_image = m_game_list_item.GetBannerImage(); - const auto banner_min_size = m_banner->GetMinSize(); + const wxImage banner_image = WxUtils::ToWxImage(m_game_list_item.GetBannerImage()); + const wxSize banner_min_size = m_banner->GetMinSize(); if (banner_image.IsOk()) { @@ -337,7 +337,7 @@ void InfoPanel::OnComputeMD5(wxCommandEvent& WXUNUSED(event)) wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH); - const auto result = MD5::MD5Sum(m_game_list_item.GetFileName(), [&progress_dialog](int progress) { + const auto result = MD5::MD5Sum(m_game_list_item.GetFilePath(), [&progress_dialog](int progress) { return progress_dialog.Update(progress); }); @@ -367,7 +367,7 @@ void InfoPanel::OnSaveBannerImage(wxCommandEvent& WXUNUSED(event)) if (dialog.ShowModal() == wxID_OK) { - m_game_list_item.GetBannerImage().SaveFile(dialog.GetPath()); + WxUtils::ToWxImage(m_game_list_item.GetBannerImage()).SaveFile(dialog.GetPath()); } Raise(); @@ -375,16 +375,16 @@ void InfoPanel::OnSaveBannerImage(wxCommandEvent& WXUNUSED(event)) void InfoPanel::ChangeBannerDetails(DiscIO::Language language) { - const auto name = StrToWxStr(m_game_list_item.GetName(language)); + const auto name = StrToWxStr(m_game_list_item.GetLongName(language)); const auto comment = StrToWxStr(m_game_list_item.GetDescription(language)); - const auto maker = StrToWxStr(m_game_list_item.GetCompany()); + const auto maker = StrToWxStr(m_game_list_item.GetLongMaker(language)); m_name->SetValue(name); m_comment->SetValue(comment); m_maker->SetValue(maker); std::string path, filename, extension; - SplitPath(m_game_list_item.GetFileName(), &path, &filename, &extension); + SplitPath(m_game_list_item.GetFilePath(), &path, &filename, &extension); // Real disk drives don't have filenames on Windows if (filename.empty() && extension.empty()) diff --git a/Source/Core/DolphinWX/ISOProperties/InfoPanel.h b/Source/Core/DolphinWX/ISOProperties/InfoPanel.h index 827764bb8d..1ec8c31db5 100644 --- a/Source/Core/DolphinWX/ISOProperties/InfoPanel.h +++ b/Source/Core/DolphinWX/ISOProperties/InfoPanel.h @@ -7,7 +7,6 @@ #include #include -class GameListItem; class wxButton; class wxChoice; class wxStaticBitmap; @@ -16,14 +15,19 @@ class wxTextCtrl; namespace DiscIO { -class Volume; enum class Language; +class Volume; +} + +namespace UICommon +{ +class GameFile; } class InfoPanel final : public wxPanel { public: - InfoPanel(wxWindow* parent, wxWindowID id, const GameListItem& item, + InfoPanel(wxWindow* parent, wxWindowID id, const UICommon::GameFile& item, const std::unique_ptr& opened_iso); private: @@ -37,6 +41,7 @@ private: void LoadGUIData(); void LoadISODetails(); void LoadBannerDetails(); + wxImage ConvertBannerImage(); void LoadBannerImage(); wxStaticBoxSizer* CreateISODetailsSizer(); @@ -52,7 +57,7 @@ private: void EmitTitleChangeEvent(const wxString& new_title); - const GameListItem& m_game_list_item; + const UICommon::GameFile& m_game_list_item; const std::unique_ptr& m_opened_iso; wxTextCtrl* m_internal_name; diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp index ec6b6bba0b..cd4bba69a6 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp @@ -47,11 +47,12 @@ #include "DolphinWX/Frame.h" #include "DolphinWX/GameListCtrl.h" -#include "DolphinWX/ISOFile.h" #include "DolphinWX/NetPlay/ChangeGameDialog.h" +#include "DolphinWX/NetPlay/MD5Dialog.h" #include "DolphinWX/NetPlay/PadMapDialog.h" #include "DolphinWX/WxUtils.h" -#include "MD5Dialog.h" + +#include "UICommon/GameFile.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoConfig.h" @@ -335,7 +336,7 @@ std::string NetPlayDialog::FindGame(const std::string& target_game) // find path for selected game, sloppy.. for (u32 i = 0; auto game = m_game_list->GetISO(i); ++i) if (target_game == game->GetUniqueIdentifier()) - return game->GetFileName(); + return game->GetFilePath(); return ""; } diff --git a/Source/Core/DolphinWX/WxUtils.cpp b/Source/Core/DolphinWX/WxUtils.cpp index 703518da3f..f6d160fe01 100644 --- a/Source/Core/DolphinWX/WxUtils.cpp +++ b/Source/Core/DolphinWX/WxUtils.cpp @@ -28,6 +28,8 @@ #include "DolphinWX/WxUtils.h" +#include "UICommon/GameFile.h" + #ifdef _WIN32 #include #endif @@ -285,6 +287,27 @@ wxSize GetTextWidgetMinSize(const wxSpinCtrl* spinner) return size; } +wxImage ToWxImage(const UICommon::GameBanner& banner) +{ + return ToWxImage(banner.buffer, banner.width, banner.height); +} + +wxImage ToWxImage(const std::vector& buffer, int width, int height) +{ + wxImage image(width, height, false); + if (buffer.empty()) + return image; + + unsigned char* data = image.GetData(); + for (int i = 0; i < width * height; i++) + { + data[i * 3 + 0] = (buffer[i] & 0xFF0000) >> 16; + data[i * 3 + 1] = (buffer[i] & 0x00FF00) >> 8; + data[i * 3 + 2] = (buffer[i] & 0x0000FF) >> 0; + } + return image; +} + static wxImage LoadScaledImage(const std::string& file_path, const wxWindow* context, const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags, const wxColour& fill_color) diff --git a/Source/Core/DolphinWX/WxUtils.h b/Source/Core/DolphinWX/WxUtils.h index 7dc5051751..1c8deff38b 100644 --- a/Source/Core/DolphinWX/WxUtils.h +++ b/Source/Core/DolphinWX/WxUtils.h @@ -5,10 +5,14 @@ #pragma once #include +#include + #include #include #include +#include "Common/CommonTypes.h" + class wxControl; class wxBitmap; class wxImage; @@ -18,6 +22,11 @@ class wxToolBar; class wxTopLevelWindow; class wxWindow; +namespace UICommon +{ +struct GameBanner; +} + namespace WxUtils { // Launch a file according to its mime type @@ -95,6 +104,9 @@ constexpr LSIFlags operator&(LSIFlags left, LSIFlags right) return static_cast(static_cast(left) & right); } +wxImage ToWxImage(const UICommon::GameBanner& banner); +wxImage ToWxImage(const std::vector& buffer, int width, int height); + // Swiss army knife loader function for preparing a scaled resource image file. // Only the path and context are mandatory, other parameters can be ignored. // NOTE: All size parameters are in window pixels, not DIPs or framebuffer pixels. diff --git a/Source/Core/UICommon/CMakeLists.txt b/Source/Core/UICommon/CMakeLists.txt index 838dfb8775..bb5439badd 100644 --- a/Source/Core/UICommon/CMakeLists.txt +++ b/Source/Core/UICommon/CMakeLists.txt @@ -1,6 +1,8 @@ set(SRCS CommandLineParse.cpp Disassembler.cpp + GameFile.cpp + GameFileCache.cpp UICommon.cpp USBUtils.cpp VideoUtils.cpp diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp new file mode 100644 index 0000000000..1a6b0717c2 --- /dev/null +++ b/Source/Core/UICommon/GameFile.cpp @@ -0,0 +1,312 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "UICommon/GameFile.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Common/Hash.h" +#include "Common/IniFile.h" +#include "Common/NandPaths.h" +#include "Common/StringUtil.h" + +#include "Core/Boot/Boot.h" +#include "Core/ConfigManager.h" +#include "Core/IOS/ES/Formats.h" +#include "Core/TitleDatabase.h" + +#include "DiscIO/Blob.h" +#include "DiscIO/Enums.h" +#include "DiscIO/Volume.h" +#include "DiscIO/WiiSaveBanner.h" + +namespace UICommon +{ +static const std::string EMPTY_STRING; + +const std::string& GameFile::Lookup(DiscIO::Language language, + const std::map& strings) +{ + auto end = strings.end(); + auto it = strings.find(language); + if (it != end) + return it->second; + + // English tends to be a good fallback when the requested language isn't available + if (language != DiscIO::Language::LANGUAGE_ENGLISH) + { + it = strings.find(DiscIO::Language::LANGUAGE_ENGLISH); + if (it != end) + return it->second; + } + + // If English isn't available either, just pick something + if (!strings.empty()) + return strings.cbegin()->second; + + return EMPTY_STRING; +} + +const std::string& +GameFile::LookupUsingConfigLanguage(const std::map& strings) const +{ + const bool wii = DiscIO::IsWii(m_platform); + return Lookup(SConfig::GetInstance().GetCurrentLanguage(wii), strings); +} + +GameFile::GameFile(const std::string& path) + : m_file_path(path), m_region(DiscIO::Region::UNKNOWN_REGION), + m_country(DiscIO::Country::COUNTRY_UNKNOWN) +{ + { + std::string name, extension; + SplitPath(m_file_path, nullptr, &name, &extension); + m_file_name = name + extension; + + std::unique_ptr volume(DiscIO::CreateVolumeFromFilename(m_file_path)); + if (volume != nullptr) + { + m_platform = volume->GetVolumeType(); + + m_short_names = volume->GetShortNames(); + m_long_names = volume->GetLongNames(); + m_short_makers = volume->GetShortMakers(); + m_long_makers = volume->GetLongMakers(); + m_descriptions = volume->GetDescriptions(); + + m_region = volume->GetRegion(); + m_country = volume->GetCountry(); + m_blob_type = volume->GetBlobType(); + m_file_size = volume->GetRawSize(); + m_volume_size = volume->GetSize(); + + m_internal_name = volume->GetInternalName(); + m_game_id = volume->GetGameID(); + m_title_id = volume->GetTitleID().value_or(0); + m_maker_id = volume->GetMakerID(); + m_revision = volume->GetRevision().value_or(0); + m_disc_number = volume->GetDiscNumber().value_or(0); + m_apploader_date = volume->GetApploaderDate(); + + m_volume_banner.buffer = volume->GetBanner(&m_volume_banner.width, &m_volume_banner.height); + + m_valid = true; + } + } + + if (!IsValid() && IsElfOrDol()) + { + m_valid = true; + m_file_size = File::GetSize(m_file_path); + m_platform = DiscIO::Platform::ELF_DOL; + m_blob_type = DiscIO::BlobType::DIRECTORY; + } +} + +bool GameFile::IsValid() const +{ + if (!m_valid) + return false; + + if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id)) + return false; + + return true; +} + +bool GameFile::CustomNameChanged(const Core::TitleDatabase& title_database) +{ + const auto type = m_platform == DiscIO::Platform::WII_WAD ? + Core::TitleDatabase::TitleType::Channel : + Core::TitleDatabase::TitleType::Other; + m_pending.custom_name = title_database.GetTitleName(m_game_id, type); + return m_custom_name != m_pending.custom_name; +} + +void GameFile::CustomNameCommit() +{ + m_custom_name = std::move(m_pending.custom_name); +} + +bool GameFile::EmuStateChanged() +{ + IniFile ini = SConfig::LoadGameIni(m_game_id, m_revision); + ini.GetIfExists("EmuState", "EmulationStateId", &m_pending.emu_state.rating, 0); + ini.GetIfExists("EmuState", "EmulationIssues", &m_pending.emu_state.issues, std::string()); + return m_emu_state != m_pending.emu_state; +} + +void GameFile::EmuStateCommit() +{ + m_emu_state = std::move(m_pending.emu_state); +} + +void GameFile::EmuState::DoState(PointerWrap& p) +{ + p.Do(rating); + p.Do(issues); +} + +void GameBanner::DoState(PointerWrap& p) +{ + p.Do(buffer); + p.Do(width); + p.Do(height); +} + +void GameFile::DoState(PointerWrap& p) +{ + p.Do(m_valid); + p.Do(m_file_path); + p.Do(m_file_name); + + p.Do(m_file_size); + p.Do(m_volume_size); + + p.Do(m_short_names); + p.Do(m_long_names); + p.Do(m_short_makers); + p.Do(m_long_makers); + p.Do(m_descriptions); + p.Do(m_internal_name); + p.Do(m_game_id); + p.Do(m_title_id); + p.Do(m_maker_id); + + p.Do(m_region); + p.Do(m_country); + p.Do(m_platform); + p.Do(m_blob_type); + p.Do(m_revision); + p.Do(m_disc_number); + p.Do(m_apploader_date); + + m_volume_banner.DoState(p); + m_emu_state.DoState(p); + p.Do(m_custom_name); +} + +bool GameFile::IsElfOrDol() const +{ + if (m_file_path.size() < 4) + return false; + + std::string name_end = m_file_path.substr(m_file_path.size() - 4); + std::transform(name_end.begin(), name_end.end(), name_end.begin(), ::tolower); + return name_end == ".elf" || name_end == ".dol"; +} + +bool GameFile::BannerChanged() +{ + // Wii banners can only be read if there is a save file. + // In case the cache was created without a save file existing, + // let's try reading the save file again, because it might exist now. + + if (!m_volume_banner.empty()) + return false; + if (!DiscIO::IsWii(m_platform)) + return false; + + m_volume_banner.buffer = + DiscIO::WiiSaveBanner(m_title_id).GetBanner(&m_volume_banner.width, &m_volume_banner.height); + if (m_volume_banner.buffer.empty()) + return false; + + // We only reach here if m_volume_banner was empty, so we can always return true + // without needing any extra check to know whether the banners are different + return true; +} + +void GameFile::BannerCommit() +{ + m_volume_banner = std::move(m_pending.volume_banner); +} + +const std::string& GameFile::GetName(bool long_name) const +{ + if (!m_custom_name.empty()) + return m_custom_name; + + const std::string& name = long_name ? GetLongName() : GetShortName(); + if (!name.empty()) + return name; + + // No usable name, return filename (better than nothing) + return m_file_name; +} + +const std::string& GameFile::GetMaker(bool long_maker) const +{ + const std::string& maker = long_maker ? GetLongMaker() : GetShortMaker(); + if (!maker.empty()) + return maker; + + if (m_game_id.size() >= 6) + return DiscIO::GetCompanyFromID(m_maker_id); + + return EMPTY_STRING; +} + +std::vector GameFile::GetLanguages() const +{ + std::vector languages; + // TODO: What if some languages don't have long names but have other strings? + for (std::pair name : m_long_names) + languages.push_back(name.first); + return languages; +} + +std::string GameFile::GetUniqueIdentifier() const +{ + const DiscIO::Language lang = DiscIO::Language::LANGUAGE_ENGLISH; + std::vector info; + if (!GetGameID().empty()) + info.push_back(GetGameID()); + if (GetRevision() != 0) + info.push_back("Revision " + std::to_string(GetRevision())); + + std::string name(GetLongName(lang)); + if (name.empty()) + name = GetName(); + + int disc_number = GetDiscNumber() + 1; + + std::string lower_name = name; + std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower); + if (disc_number > 1 && + lower_name.find(StringFromFormat("disc %i", disc_number)) == std::string::npos && + lower_name.find(StringFromFormat("disc%i", disc_number)) == std::string::npos) + { + std::string disc_text = "Disc "; + info.push_back(disc_text + std::to_string(disc_number)); + } + if (info.empty()) + return name; + std::ostringstream ss; + std::copy(info.begin(), info.end() - 1, std::ostream_iterator(ss, ", ")); + ss << info.back(); + return name + " (" + ss.str() + ")"; +} + +std::string GameFile::GetWiiFSPath() const +{ + _assert_(DiscIO::IsWii(m_platform)); + return Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT); +} + +} // namespace UICommon diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h new file mode 100644 index 0000000000..76a0be4fee --- /dev/null +++ b/Source/Core/UICommon/GameFile.h @@ -0,0 +1,153 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" + +class PointerWrap; + +namespace Core +{ +class TitleDatabase; +} + +namespace DiscIO +{ +enum class BlobType; +enum class Country; +enum class Language; +enum class Region; +enum class Platform; +} + +namespace UICommon +{ +struct GameBanner +{ + std::vector buffer{}; + int width{}; + int height{}; + bool empty() const { return buffer.empty(); } + void DoState(PointerWrap& p); +}; + +// This class caches the metadata of a DiscIO::Volume (or a DOL/ELF file). +class GameFile final +{ +public: + GameFile() = default; + explicit GameFile(const std::string& path); + ~GameFile() = default; + + bool IsValid() const; + const std::string& GetFilePath() const { return m_file_path; } + const std::string& GetFileName() const { return m_file_name; } + const std::string& GetName(bool long_name = true) const; + const std::string& GetMaker(bool long_maker = true) const; + const std::string& GetShortName(DiscIO::Language l) const { return Lookup(l, m_short_names); } + const std::string& GetShortName() const { return LookupUsingConfigLanguage(m_short_names); } + const std::string& GetLongName(DiscIO::Language l) const { return Lookup(l, m_long_names); } + const std::string& GetLongName() const { return LookupUsingConfigLanguage(m_long_names); } + const std::string& GetShortMaker(DiscIO::Language l) const { return Lookup(l, m_short_makers); } + const std::string& GetShortMaker() const { return LookupUsingConfigLanguage(m_short_makers); } + const std::string& GetLongMaker(DiscIO::Language l) const { return Lookup(l, m_long_makers); } + const std::string& GetLongMaker() const { return LookupUsingConfigLanguage(m_long_makers); } + const std::string& GetDescription(DiscIO::Language l) const { return Lookup(l, m_descriptions); } + const std::string& GetDescription() const { return LookupUsingConfigLanguage(m_descriptions); } + std::vector GetLanguages() const; + const std::string& GetInternalName() const { return m_internal_name; } + const std::string& GetGameID() const { return m_game_id; } + u64 GetTitleID() const { return m_title_id; } + const std::string& GetMakerID() const { return m_maker_id; } + u16 GetRevision() const { return m_revision; } + // 0 is the first disc, 1 is the second disc + u8 GetDiscNumber() const { return m_disc_number; } + std::string GetUniqueIdentifier() const; + std::string GetWiiFSPath() const; + DiscIO::Region GetRegion() const { return m_region; } + DiscIO::Country GetCountry() const { return m_country; } + DiscIO::Platform GetPlatform() const { return m_platform; } + DiscIO::BlobType GetBlobType() const { return m_blob_type; } + const std::string& GetApploaderDate() const { return m_apploader_date; } + const std::string& GetIssues() const { return m_emu_state.issues; } + int GetEmuState() const { return m_emu_state.rating; } + u64 GetFileSize() const { return m_file_size; } + u64 GetVolumeSize() const { return m_volume_size; } + const GameBanner& GetBannerImage() const { return m_volume_banner; } + void DoState(PointerWrap& p); + bool BannerChanged(); + void BannerCommit(); + bool EmuStateChanged(); + void EmuStateCommit(); + bool CustomNameChanged(const Core::TitleDatabase& title_database); + void CustomNameCommit(); + +private: + struct EmuState + { + int rating{}; + std::string issues{}; + bool operator!=(const EmuState& rhs) const + { + return rating != rhs.rating || issues != rhs.issues; + } + void DoState(PointerWrap& p); + }; + + static const std::string& Lookup(DiscIO::Language language, + const std::map& strings); + const std::string& + LookupUsingConfigLanguage(const std::map& strings) const; + bool IsElfOrDol() const; + + // IMPORTANT: Nearly all data members must be save/restored in DoState. + // If anything is changed, make sure DoState handles it properly and + // CACHE_REVISION in GameFileCache.cpp is incremented. + + bool m_valid{}; + std::string m_file_path{}; + std::string m_file_name{}; + + u64 m_file_size{}; + u64 m_volume_size{}; + + std::map m_short_names{}; + std::map m_long_names{}; + std::map m_short_makers{}; + std::map m_long_makers{}; + std::map m_descriptions{}; + std::string m_internal_name{}; + std::string m_game_id{}; + u64 m_title_id{}; + std::string m_maker_id{}; + + DiscIO::Region m_region{}; + DiscIO::Country m_country{}; + DiscIO::Platform m_platform{}; + DiscIO::BlobType m_blob_type{}; + u16 m_revision{}; + u8 m_disc_number{}; + std::string m_apploader_date{}; + + GameBanner m_volume_banner{}; + EmuState m_emu_state{}; + // Overridden name from TitleDatabase + std::string m_custom_name{}; + + // The following data members allow GameFileCache to construct updated versions + // of GameFiles in a threadsafe way. They should not be handled in DoState. + struct + { + EmuState emu_state; + GameBanner volume_banner; + std::string custom_name; + } m_pending{}; +}; + +} // namespace UICommon diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp new file mode 100644 index 0000000000..a0e1253c18 --- /dev/null +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -0,0 +1,235 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "UICommon/GameFileCache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" +#include "Common/File.h" +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" + +#include "Core/TitleDatabase.h" + +#include "DiscIO/DirectoryBlob.h" + +#include "UICommon/GameFile.h" + +namespace UICommon +{ +static constexpr u32 CACHE_REVISION = 7; // Last changed in PR 6281 + +std::vector FindAllGamePaths(const std::vector& directories_to_scan, + bool recursive_scan) +{ + static const std::vector search_extensions = { + ".gcm", ".tgc", ".iso", ".ciso", ".gcz", ".wbfs", ".wad", ".dol", ".elf"}; + + // TODO: We could process paths iteratively as they are found + return Common::DoFileSearch(directories_to_scan, search_extensions, recursive_scan); +} + +void GameFileCache::ForEach(std::function&)> f) const +{ + for (const std::shared_ptr& item : m_cached_files) + f(item); +} + +void GameFileCache::Clear() +{ + m_cached_files.clear(); +} + +std::shared_ptr GameFileCache::AddOrGet(const std::string& path, + bool* cache_changed, + const Core::TitleDatabase& title_database) +{ + auto it = std::find_if( + m_cached_files.begin(), m_cached_files.end(), + [&path](const std::shared_ptr& file) { return file->GetFilePath() == path; }); + const bool found = it != m_cached_files.cend(); + if (!found) + m_cached_files.emplace_back(std::make_shared(path)); + std::shared_ptr& result = found ? *it : m_cached_files.back(); + if (UpdateAdditionalMetadata(&result, title_database) || !found) + *cache_changed = true; + + return result; +} + +bool GameFileCache::Update(const std::vector& all_game_paths) +{ + // Copy game paths into a set, except ones that match DiscIO::ShouldHideFromGameList. + // TODO: Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all? + // TODO: Make DoFileSearch support filter predicates so we don't have remove things afterwards? + std::unordered_set game_paths; + game_paths.reserve(all_game_paths.size()); + for (const std::string& path : all_game_paths) + { + if (!DiscIO::ShouldHideFromGameList(path)) + game_paths.insert(path); + } + + bool cache_changed = false; + + // Delete paths that aren't in game_paths from m_cached_files, + // while simultaneously deleting paths that aren't in m_cached_files from game_paths. + // For the sake of speed, we don't care about maintaining the order of m_cached_files. + { + auto it = m_cached_files.begin(); + auto end = m_cached_files.end(); + while (it != end) + { + if (game_paths.erase((*it)->GetFilePath())) + { + ++it; + } + else + { + cache_changed = true; + --end; + *it = std::move(*end); + } + } + m_cached_files.erase(it, m_cached_files.end()); + } + + // Now that the previous loop has run, game_paths only contains paths that + // aren't in m_cached_files, so we simply add all of them to m_cached_files. + for (const auto& path : game_paths) + { + auto file = std::make_shared(path); + if (file->IsValid()) + { + cache_changed = true; + m_cached_files.push_back(std::move(file)); + } + } + + return cache_changed; +} + +bool GameFileCache::UpdateAdditionalMetadata(const Core::TitleDatabase& title_database) +{ + bool cache_changed = false; + + for (auto& file : m_cached_files) + cache_changed |= UpdateAdditionalMetadata(&file, title_database); + + return cache_changed; +} + +bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr* game_file, + const Core::TitleDatabase& title_database) +{ + const bool emu_state_changed = (*game_file)->EmuStateChanged(); + const bool banner_changed = (*game_file)->BannerChanged(); + const bool custom_title_changed = (*game_file)->CustomNameChanged(title_database); + if (!emu_state_changed && !banner_changed && !custom_title_changed) + return false; + + // If a cached file needs an update, apply the updates to a copy and delete the original. + // This makes the usage of cached files in other threads safe. + + std::shared_ptr copy = std::make_shared(**game_file); + if (emu_state_changed) + copy->EmuStateCommit(); + if (banner_changed) + copy->BannerCommit(); + if (custom_title_changed) + copy->CustomNameCommit(); + *game_file = std::move(copy); + + return true; +} + +bool GameFileCache::Load() +{ + return SyncCacheFile(false); +} + +bool GameFileCache::Save() +{ + return SyncCacheFile(true); +} + +bool GameFileCache::SyncCacheFile(bool save) +{ + std::string filename(File::GetUserPath(D_CACHE_IDX) + "gamelist.cache"); + const char* open_mode = save ? "wb" : "rb"; + File::IOFile f(filename, open_mode); + if (!f) + return false; + bool success = false; + if (save) + { + // Measure the size of the buffer. + u8* ptr = nullptr; + PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); + DoState(&p); + const size_t buffer_size = reinterpret_cast(ptr); + + // Then actually do the write. + std::vector buffer(buffer_size); + ptr = &buffer[0]; + p.SetMode(PointerWrap::MODE_WRITE); + DoState(&p, buffer_size); + if (f.WriteBytes(buffer.data(), buffer.size())) + success = true; + } + else + { + std::vector buffer(f.GetSize()); + if (buffer.size() && f.ReadBytes(buffer.data(), buffer.size())) + { + u8* ptr = buffer.data(); + PointerWrap p(&ptr, PointerWrap::MODE_READ); + DoState(&p, buffer.size()); + if (p.GetMode() == PointerWrap::MODE_READ) + success = true; + } + } + if (!success) + { + // If some file operation failed, try to delete the probably-corrupted cache + f.Close(); + File::Delete(filename); + } + return success; +} + +void GameFileCache::DoState(PointerWrap* p, u64 size) +{ + struct + { + u32 revision; + u64 expected_size; + } header = {CACHE_REVISION, size}; + p->Do(header); + if (p->GetMode() == PointerWrap::MODE_READ) + { + if (header.revision != CACHE_REVISION || header.expected_size != size) + { + p->SetMode(PointerWrap::MODE_MEASURE); + return; + } + } + p->DoEachElement(m_cached_files, [](PointerWrap& state, std::shared_ptr& elem) { + if (state.GetMode() == PointerWrap::MODE_READ) + elem = std::make_shared(); + elem->DoState(state); + }); +} + +} // namespace DiscIO diff --git a/Source/Core/UICommon/GameFileCache.h b/Source/Core/UICommon/GameFileCache.h new file mode 100644 index 0000000000..acfdc3410f --- /dev/null +++ b/Source/Core/UICommon/GameFileCache.h @@ -0,0 +1,58 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +class PointerWrap; + +namespace Core +{ +class TitleDatabase; +} + +namespace UICommon +{ +class GameFile; + +std::vector FindAllGamePaths(const std::vector& directories_to_scan, + bool recursive_scan); + +class GameFileCache +{ +public: + void ForEach(std::function&)> f) const; + + void Clear(); + + std::shared_ptr AddOrGet(const std::string& path, bool* cache_changed, + const Core::TitleDatabase& title_database); + + // These functions return true if the call modified the cache. + bool Update(const std::vector& all_game_paths); + bool UpdateAdditionalMetadata(const Core::TitleDatabase& title_database); + + bool Load(); + bool Save(); + +private: + bool UpdateAdditionalMetadata(std::shared_ptr* game_file, + const Core::TitleDatabase& title_database); + + bool SyncCacheFile(bool save); + void DoState(PointerWrap* p, u64 size = 0); + + std::vector> m_cached_files; +}; + +} // namespace UICommon diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 06d4db5850..eb060b27a3 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -2,16 +2,20 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include #include #ifdef _WIN32 #include // for SHGetFolderPath #endif +#include "Common/Common.h" #include "Common/CommonPaths.h" #include "Common/Config/Config.h" #include "Common/FileUtil.h" #include "Common/Logging/LogManager.h" +#include "Common/MathUtil.h" #include "Common/MsgHandler.h" +#include "Common/StringUtil.h" #include "Core/ConfigLoaders/BaseConfigLoader.h" #include "Core/ConfigManager.h" @@ -313,4 +317,21 @@ void EnableScreenSaver(bool enable) #endif } +std::string FormatSize(u64 bytes) +{ + // i18n: The symbol for the unit "bytes" + const char* const unit_symbols[] = {_trans("B"), _trans("KiB"), _trans("MiB"), _trans("GiB"), + _trans("TiB"), _trans("PiB"), _trans("EiB")}; + + // Find largest power of 2 less than size. + // div 10 to get largest named unit less than size + // 10 == log2(1024) (number of B in a KiB, KiB in a MiB, etc) + // Max value is 63 / 10 = 6 + const int unit = IntLog2(std::max(bytes, 1)) / 10; + + // Don't need exact values, only 5 most significant digits + const double unit_size = std::pow(2, unit * 10); + return StringFromFormat("%.2f %s", bytes / unit_size, GetStringT(unit_symbols[unit]).c_str()); +} + } // namespace UICommon diff --git a/Source/Core/UICommon/UICommon.h b/Source/Core/UICommon/UICommon.h index 5e11c9bfc2..8bbd864ec0 100644 --- a/Source/Core/UICommon/UICommon.h +++ b/Source/Core/UICommon/UICommon.h @@ -25,4 +25,8 @@ void SetUserDirectory(const std::string& custom_path); bool TriggerSTMPowerEvent(); void SaveWiimoteSources(); + +// Return a pretty file size string from byte count. +// e.g. 1134278 -> "1.08 MiB" +std::string FormatSize(u64 bytes); } // namespace UICommon diff --git a/Source/Core/UICommon/UICommon.vcxproj b/Source/Core/UICommon/UICommon.vcxproj index 643a5e9829..164cb4f884 100644 --- a/Source/Core/UICommon/UICommon.vcxproj +++ b/Source/Core/UICommon/UICommon.vcxproj @@ -51,12 +51,16 @@ 4200;%(DisableSpecificWarnings) + + + +