Cemu/src/Cafe/TitleList/MetaInfo.cpp

179 lines
4.9 KiB
C++

#include "MetaInfo.h"
#include "Cafe/Filesystem/fsc.h"
#include "pugixml.hpp"
#include "Cafe/Filesystem/FST/FST.h"
MetaInfo::MetaInfo()
{
m_type = GameType::FSC;
uint32 meta_size;
const auto meta_data = ReadFSCFile("vol/meta/meta.xml", meta_size);
if (meta_size == 0 || !meta_data)
throw std::runtime_error("meta.xml missing");
pugi::xml_document meta_doc;
ParseMetaFile(meta_doc, meta_doc.load_buffer_inplace(meta_data.get(), meta_size));
}
MetaInfo::MetaInfo(const fs::path& filename)
{
if (!fs::exists(filename))
throw std::invalid_argument("filename doesn't exist");
if (fs::is_directory(filename))
{
MetaInfo::ParseDirectory(filename);
m_type = GameType::Directory;
m_type_path = filename;
}
else
MetaInfo::ParseFile(filename);
}
std::string MetaInfo::GetName(CafeConsoleLanguage language) const
{
std::string long_name{ GetLongName(language) };
const auto nl = long_name.find(L'\n');
if (nl != std::string::npos)
long_name.replace(nl, 1, " - ");
return long_name;
}
const std::string& MetaInfo::GetLongName(CafeConsoleLanguage language) const
{
return m_long_name[(int)language].empty() ? m_long_name[(int)CafeConsoleLanguage::EN] : m_long_name[(int)language];
}
const std::string& MetaInfo::GetShortName(CafeConsoleLanguage language) const
{
return m_short_name[(int)language].empty() ? m_short_name[(int)CafeConsoleLanguage::EN] : m_short_name[(int)language];
}
const std::string& MetaInfo::GetPublisher(CafeConsoleLanguage language) const
{
return m_publisher[(int)language].empty() ? m_publisher[(int)CafeConsoleLanguage::EN] : m_publisher[(int)language];
}
void MetaInfo::ParseDirectory(const fs::path& filename)
{
const auto meta_dir = fs::path(filename).append(L"meta");
if (!fs::exists(meta_dir) || !fs::is_directory(meta_dir))
throw std::invalid_argument("meta directory missing");
const auto meta_file = meta_dir / L"meta.xml";
if (!fs::exists(meta_file) || !fs::is_regular_file(meta_file))
throw std::invalid_argument("meta.xml missing");
ParseMetaFile(meta_file.wstring());
}
bool MetaInfo::ParseFile(const fs::path& filename)
{
if (filename.filename() != "meta.xml")
return false;
const auto base_dir = filename.parent_path().parent_path();
ParseMetaFile(filename);
m_type = GameType::Directory;
m_type_path = base_dir;
return true;
}
void MetaInfo::ParseMetaFile(const fs::path& meta_file)
{
pugi::xml_document doc;
const auto result = doc.load_file(meta_file.wstring().c_str());
ParseMetaFile(doc, result);
}
void MetaInfo::ParseMetaFile(const pugi::xml_document& doc, const pugi::xml_parse_result& result)
{
if (!result)
throw std::invalid_argument(fmt::format("error when parsing the meta.xml: {}", result.description()));
const auto root = doc.child("menu");
if (!root)
throw std::invalid_argument("meta.xml invalid");
for (const auto& child : root.children())
{
std::string_view name = child.name();
if (name == "title_version")
m_version = child.text().as_uint();
else if (name == "product_code")
m_product_code = child.text().as_string();
else if (name == "company_code")
m_company_code = child.text().as_string();
else if (name == "content_platform")
m_content_platform = child.text().as_string();
else if (name == "title_id")
m_title_id = std::stoull(child.text().as_string(), nullptr, 16);
else if (name == "region")
m_region = (CafeConsoleRegion)child.text().as_uint();
else if (boost::starts_with(name, "longname_"))
{
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
if (index != -1)
m_long_name[index] = child.text().as_string();
}
else if (boost::starts_with(name, L"shortname_"))
{
const sint32 index = GetLanguageIndex(name.substr(std::size("shortname_") - 1));
if (index != -1)
m_short_name[index] = child.text().as_string();
}
else if (boost::starts_with(name, L"publisher_"))
{
const sint32 index = GetLanguageIndex(name.substr(std::size("publisher_") - 1));
if (index != -1)
m_publisher[index] = child.text().as_string();
}
}
}
std::unique_ptr<uint8[]> MetaInfo::GetIcon(uint32& size) const
{
size = 0;
switch (m_type)
{
case GameType::FSC:
return ReadFSCFile("vol/meta/iconTex.tga", size);
case GameType::Directory:
{
cemu_assert_debug(!m_type_path.empty());
const auto icon = fs::path(m_type_path).append(L"meta").append(L"iconTex.tga");
std::ifstream file(icon, std::ios::binary | std::ios::ate);
if (file.is_open())
{
size = file.tellg();
if (size > 0)
{
file.seekg(0, std::ios::beg);
auto result = std::make_unique<uint8[]>(size);
file.read((char*)result.get(), size);
return result;
}
}
return nullptr;
}
case GameType::Image:
{
cemu_assert_debug(!m_type_path.empty());
FSTVolume* volume = FSTVolume::OpenFromDiscImage(m_type_path);
if (volume)
{
auto result = ReadVirtualFile(volume, "meta/iconTex.tga", size);
delete volume;
return result;
}
return nullptr;
}
default:
UNREACHABLE;
}
return nullptr;
}