summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2019-08-13 18:01:27 +0200
committerJosJuice <josjuice@gmail.com>2019-09-25 12:25:33 +0200
commit59f27ae4e1713d922fe71fc6ba95fbc399aac814 (patch)
tree06200f016eba6dd38fbee1067232490b6d5af14d /Source/Core
parentedfb0f66b6685fe1d4314ca27cce9492a7937683 (diff)
GameFile: Support HBC-style XML metadata
This feature was originally exclusive to the previous iteration of DolphinQt (the one that was the reason for the current iteration being named DolphinQt2 initially). https://bugs.dolphin-emu.org/issues/8949
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/DolphinQt/Config/InfoWidget.cpp2
-rw-r--r--Source/Core/DolphinQt/GameList/GameList.cpp2
-rw-r--r--Source/Core/DolphinQt/GameList/GameListModel.cpp4
-rw-r--r--Source/Core/UICommon/CMakeLists.txt1
-rw-r--r--Source/Core/UICommon/GameFile.cpp84
-rw-r--r--Source/Core/UICommon/GameFile.h15
-rw-r--r--Source/Core/UICommon/GameFileCache.cpp11
-rw-r--r--Source/Core/UICommon/UICommon.vcxproj3
8 files changed, 107 insertions, 15 deletions
diff --git a/Source/Core/DolphinQt/Config/InfoWidget.cpp b/Source/Core/DolphinQt/Config/InfoWidget.cpp
index e7915636bb..ad96cc25f9 100644
--- a/Source/Core/DolphinQt/Config/InfoWidget.cpp
+++ b/Source/Core/DolphinQt/Config/InfoWidget.cpp
@@ -73,7 +73,7 @@ QGroupBox* InfoWidget::CreateISODetails()
QLineEdit* country = CreateValueDisplay(DiscIO::GetName(m_game.GetCountry(), true));
- const std::string game_maker = m_game.GetMaker();
+ const std::string game_maker = m_game.GetMaker(false);
QLineEdit* maker =
CreateValueDisplay((game_maker.empty() ? UNKNOWN_NAME.toStdString() : game_maker) + " (" +
diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp
index e1a953a5dd..f75422f992 100644
--- a/Source/Core/DolphinQt/GameList/GameList.cpp
+++ b/Source/Core/DolphinQt/GameList/GameList.cpp
@@ -442,7 +442,7 @@ void GameList::ExportWiiSave()
for (const auto& game : GetSelectedGames())
{
if (!WiiSave::Export(game->GetTitleID(), export_dir.toStdString()))
- failed.push_back(game->GetName());
+ failed.push_back(game->GetName(true));
}
if (!failed.isEmpty())
diff --git a/Source/Core/DolphinQt/GameList/GameListModel.cpp b/Source/Core/DolphinQt/GameList/GameListModel.cpp
index ba060ad705..923befda47 100644
--- a/Source/Core/DolphinQt/GameList/GameListModel.cpp
+++ b/Source/Core/DolphinQt/GameList/GameListModel.cpp
@@ -124,13 +124,13 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
case COL_DESCRIPTION:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
{
- return QString::fromStdString(game.GetDescription())
+ return QString::fromStdString(game.GetDescription(true))
.replace(QLatin1Char('\n'), QLatin1Char(' '));
}
break;
case COL_MAKER:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
- return QString::fromStdString(game.GetMaker());
+ return QString::fromStdString(game.GetMaker(true));
break;
case COL_FILE_NAME:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
diff --git a/Source/Core/UICommon/CMakeLists.txt b/Source/Core/UICommon/CMakeLists.txt
index fe5f8f1599..cea2560394 100644
--- a/Source/Core/UICommon/CMakeLists.txt
+++ b/Source/Core/UICommon/CMakeLists.txt
@@ -32,6 +32,7 @@ PUBLIC
common
cpp-optparse
minizip
+ pugixml
PRIVATE
$<$<BOOL:APPLE>:${IOK_LIBRARY}>
diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp
index f5aefd7f01..d29f5ad2ae 100644
--- a/Source/Core/UICommon/GameFile.cpp
+++ b/Source/Core/UICommon/GameFile.cpp
@@ -17,6 +17,8 @@
#include <utility>
#include <vector>
+#include <pugixml.hpp>
+
#include "Common/ChunkFile.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
@@ -317,6 +319,9 @@ void GameFile::DoState(PointerWrap& p)
p.Do(m_disc_number);
p.Do(m_apploader_date);
+ p.Do(m_custom_name);
+ p.Do(m_custom_description);
+ p.Do(m_custom_maker);
m_volume_banner.DoState(p);
m_custom_banner.DoState(p);
m_default_cover.DoState(p);
@@ -333,6 +338,58 @@ bool GameFile::IsElfOrDol() const
return name_end == ".elf" || name_end == ".dol";
}
+bool GameFile::ReadXMLMetadata(const std::string& path)
+{
+ std::string data;
+ if (!File::ReadFileToString(path, data))
+ return false;
+
+ pugi::xml_document doc;
+ // We use load_buffer instead of load_file to avoid path encoding problems on Windows
+ if (!doc.load_buffer(data.data(), data.size()))
+ return false;
+
+ const pugi::xml_node app_node = doc.child("app");
+ m_pending.custom_name = app_node.child("name").text().as_string();
+ m_pending.custom_maker = app_node.child("coder").text().as_string();
+ m_pending.custom_description = app_node.child("short_description").text().as_string();
+
+ // Elements that we aren't using:
+ // version (can be written in any format)
+ // release_date (YYYYmmddHHMMSS format)
+ // long_description (can be several screens long!)
+
+ return true;
+}
+
+bool GameFile::XMLMetadataChanged()
+{
+ std::string path, name;
+ SplitPath(m_file_path, &path, &name, nullptr);
+
+ // This XML file naming format is intended as an alternative to the Homebrew Channel naming
+ // for those who don't want to have a Homebrew Channel style folder structure.
+ if (!ReadXMLMetadata(path + name + ".xml"))
+ {
+ // Homebrew Channel naming. Typical for DOLs and ELFs, but we also support it for volumes.
+ if (!ReadXMLMetadata(path + "meta.xml"))
+ {
+ // If no XML metadata is found, remove any old XML metadata from memory.
+ m_pending.custom_banner = {};
+ }
+ }
+
+ return m_pending.custom_name != m_custom_name && m_pending.custom_maker != m_custom_maker &&
+ m_pending.custom_description != m_custom_description;
+}
+
+void GameFile::XMLMetadataCommit()
+{
+ m_custom_name = std::move(m_pending.custom_name);
+ m_custom_description = std::move(m_pending.custom_description);
+ m_custom_maker = std::move(m_pending.custom_maker);
+}
+
bool GameFile::WiiBannerChanged()
{
// Wii banners can only be read if there is a save file.
@@ -389,7 +446,7 @@ bool GameFile::CustomBannerChanged()
std::string path, name;
SplitPath(m_file_path, &path, &name, nullptr);
- // This icon naming format is intended as an alternative to Homebrew Channel icons
+ // This icon naming format is intended as an alternative to the Homebrew Channel naming
// for those who don't want to have a Homebrew Channel style folder structure.
if (!ReadPNGBanner(path + name + ".png"))
{
@@ -411,12 +468,18 @@ void GameFile::CustomBannerCommit()
const std::string& GameFile::GetName(const Core::TitleDatabase& title_database) const
{
- const std::string& custom_name = title_database.GetTitleName(m_gametdb_id, GetConfigLanguage());
- return custom_name.empty() ? GetName() : custom_name;
+ if (!m_custom_name.empty())
+ return m_custom_name;
+
+ const std::string& database_name = title_database.GetTitleName(m_gametdb_id, GetConfigLanguage());
+ return database_name.empty() ? GetName(true) : database_name;
}
-const std::string& GameFile::GetName(bool long_name) const
+const std::string& GameFile::GetName(bool allow_custom_name, bool long_name) const
{
+ if (allow_custom_name && !m_custom_name.empty())
+ return m_custom_name;
+
const std::string& name = long_name ? GetLongName() : GetShortName();
if (!name.empty())
return name;
@@ -425,8 +488,11 @@ const std::string& GameFile::GetName(bool long_name) const
return m_file_name;
}
-const std::string& GameFile::GetMaker(bool long_maker) const
+const std::string& GameFile::GetMaker(bool allow_custom_maker, bool long_maker) const
{
+ if (allow_custom_maker && !m_custom_maker.empty())
+ return m_custom_maker;
+
const std::string& maker = long_maker ? GetLongMaker() : GetShortMaker();
if (!maker.empty())
return maker;
@@ -437,6 +503,14 @@ const std::string& GameFile::GetMaker(bool long_maker) const
return EMPTY_STRING;
}
+const std::string& GameFile::GetDescription(bool allow_custom_description) const
+{
+ if (allow_custom_description && !m_custom_description.empty())
+ return m_custom_description;
+
+ return LookupUsingConfigLanguage(m_descriptions);
+}
+
std::vector<DiscIO::Language> GameFile::GetLanguages() const
{
std::vector<DiscIO::Language> languages;
diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h
index aced331685..f783d9cac0 100644
--- a/Source/Core/UICommon/GameFile.h
+++ b/Source/Core/UICommon/GameFile.h
@@ -52,8 +52,8 @@ public:
const std::string& GetFilePath() const { return m_file_path; }
const std::string& GetFileName() const { return m_file_name; }
const std::string& GetName(const Core::TitleDatabase& title_database) const;
- const std::string& GetName(bool long_name = true) const;
- const std::string& GetMaker(bool long_maker = true) const;
+ const std::string& GetName(bool allow_custom_name, bool long_name = true) const;
+ const std::string& GetMaker(bool allow_custom_maker, 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); }
@@ -63,7 +63,7 @@ public:
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); }
+ const std::string& GetDescription(bool allow_custom_description) const;
std::vector<DiscIO::Language> GetLanguages() const;
const std::string& GetInternalName() const { return m_internal_name; }
const std::string& GetGameID() const { return m_game_id; }
@@ -85,6 +85,8 @@ public:
const GameBanner& GetBannerImage() const;
const GameCover& GetCoverImage() const;
void DoState(PointerWrap& p);
+ bool XMLMetadataChanged();
+ void XMLMetadataCommit();
bool WiiBannerChanged();
void WiiBannerCommit();
bool CustomBannerChanged();
@@ -102,6 +104,7 @@ private:
const std::string&
LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const;
bool IsElfOrDol() const;
+ bool ReadXMLMetadata(const std::string& path);
bool ReadPNGBanner(const std::string& path);
// IMPORTANT: Nearly all data members must be save/restored in DoState.
@@ -134,6 +137,9 @@ private:
u8 m_disc_number{};
std::string m_apploader_date;
+ std::string m_custom_name;
+ std::string m_custom_description;
+ std::string m_custom_maker;
GameBanner m_volume_banner{};
GameBanner m_custom_banner{};
GameCover m_default_cover{};
@@ -143,6 +149,9 @@ private:
// of GameFiles in a threadsafe way. They should not be handled in DoState.
struct
{
+ std::string custom_name;
+ std::string custom_description;
+ std::string custom_maker;
GameBanner volume_banner;
GameBanner custom_banner;
GameCover default_cover;
diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp
index fa83ed66e0..07cbf726c4 100644
--- a/Source/Core/UICommon/GameFileCache.cpp
+++ b/Source/Core/UICommon/GameFileCache.cpp
@@ -27,7 +27,7 @@
namespace UICommon
{
-static constexpr u32 CACHE_REVISION = 15; // Last changed in PR 7816
+static constexpr u32 CACHE_REVISION = 16; // Last changed in PR 8313
std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
bool recursive_scan)
@@ -166,6 +166,7 @@ bool GameFileCache::UpdateAdditionalMetadata(
bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file)
{
+ const bool xml_metadata_changed = (*game_file)->XMLMetadataChanged();
const bool wii_banner_changed = (*game_file)->WiiBannerChanged();
const bool custom_banner_changed = (*game_file)->CustomBannerChanged();
@@ -174,14 +175,18 @@ bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_fil
const bool default_cover_changed = (*game_file)->DefaultCoverChanged();
const bool custom_cover_changed = (*game_file)->CustomCoverChanged();
- if (!wii_banner_changed && !custom_banner_changed && !default_cover_changed &&
- !custom_cover_changed)
+ if (!xml_metadata_changed && !wii_banner_changed && !custom_banner_changed &&
+ !default_cover_changed && !custom_cover_changed)
+ {
return false;
+ }
// If a cached file needs an update, apply the updates to a copy and delete the original.
// This makes the usage of cached files in other threads safe.
std::shared_ptr<GameFile> copy = std::make_shared<GameFile>(**game_file);
+ if (xml_metadata_changed)
+ copy->XMLMetadataCommit();
if (wii_banner_changed)
copy->WiiBannerCommit();
if (custom_banner_changed)
diff --git a/Source/Core/UICommon/UICommon.vcxproj b/Source/Core/UICommon/UICommon.vcxproj
index 7526d92b0b..5a76b39501 100644
--- a/Source/Core/UICommon/UICommon.vcxproj
+++ b/Source/Core/UICommon/UICommon.vcxproj
@@ -48,6 +48,9 @@
<ProjectReference Include="$(ExternalsDir)discord-rpc\src\discord-rpc.vcxproj">
<Project>{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}</Project>
</ProjectReference>
+ <ProjectReference Include="$(ExternalsDir)pugixml\pugixml.vcxproj">
+ <Project>{38fee76f-f347-484b-949c-b4649381cffb}</Project>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AutoUpdate.cpp" />