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
This commit is contained in:
JosJuice
2019-08-13 18:01:27 +02:00
parent edfb0f66b6
commit 59f27ae4e1
9 changed files with 109 additions and 17 deletions

View File

@ -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;