Initial support for title switching + better Wii U menu compatibility (#907)

This commit is contained in:
Exzap 2023-07-21 13:54:07 +02:00 committed by GitHub
parent bfbeeae6f6
commit 2200cc0ddf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
95 changed files with 2549 additions and 746 deletions

View file

@ -1,68 +0,0 @@
#include "BaseInfo.h"
#include "config/CemuConfig.h"
#include "Cafe/Filesystem/fsc.h"
#include "Cafe/Filesystem/FST/FST.h"
sint32 BaseInfo::GetLanguageIndex(std::string_view language)
{
if (language == "ja")
return (sint32)CafeConsoleLanguage::JA;
else if (language == "en")
return (sint32)CafeConsoleLanguage::EN;
else if (language == "fr")
return (sint32)CafeConsoleLanguage::FR;
else if (language == "de")
return (sint32)CafeConsoleLanguage::DE;
else if (language == "it")
return (sint32)CafeConsoleLanguage::IT;
else if (language == "es")
return (sint32)CafeConsoleLanguage::ES;
else if (language == "zhs")
return (sint32)CafeConsoleLanguage::ZH;
else if (language == "ko")
return (sint32)CafeConsoleLanguage::KO;
else if (language == "nl")
return (sint32)CafeConsoleLanguage::NL;
else if (language == "pt")
return (sint32)CafeConsoleLanguage::PT;
else if (language == "ru")
return (sint32)CafeConsoleLanguage::RU;
else if (language == "zht")
return (sint32)CafeConsoleLanguage::ZH;
return -1;
}
std::unique_ptr<uint8[]> BaseInfo::ReadFSCFile(std::string_view filename, uint32& size) const
{
size = 0;
sint32 fscStatus = 0;
// load and parse meta.xml
FSCVirtualFile* file = fsc_open(const_cast<char*>(std::string(filename).c_str()), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
if (file)
{
size = fsc_getFileSize(file);
auto buffer = std::make_unique<uint8[]>(size);
fsc_readFile(file, buffer.get(), size);
fsc_close(file);
return buffer;
}
return nullptr;
}
std::unique_ptr<uint8[]> BaseInfo::ReadVirtualFile(FSTVolume* volume, std::string_view filename, uint32& size) const
{
size = 0;
FSTFileHandle fileHandle;
if (!volume->OpenFile(filename, fileHandle, true))
return nullptr;
size = volume->GetFileSize(fileHandle);
auto buffer = std::make_unique<uint8[]>(size);
volume->ReadFile(fileHandle, 0, size, buffer.get());
return buffer;
}

View file

@ -1,37 +0,0 @@
#pragma once
namespace pugi
{
struct xml_parse_result;
class xml_document;
}
class BaseInfo
{
public:
enum class GameType
{
FSC, // using fsc API
Directory, // rpx/meta
Image, // wud/wux
};
virtual ~BaseInfo() = default;
[[nodiscard]] const fs::path& GetPath() const { return m_type_path; }
[[nodiscard]] GameType GetGameType() const { return m_type; }
protected:
GameType m_type;
fs::path m_type_path; // empty / base dir / wud path
virtual void ParseDirectory(const fs::path& filename) = 0;
virtual bool ParseFile(const fs::path& filename) = 0;
[[nodiscard]] std::unique_ptr<uint8[]> ReadFSCFile(std::string_view filename, uint32& size) const;
[[nodiscard]] std::unique_ptr<uint8[]> ReadVirtualFile(class FSTVolume* volume, std::string_view filename, uint32& size) const;
[[nodiscard]] static sint32 GetLanguageIndex(std::string_view language);
};

View file

@ -20,6 +20,11 @@ public:
return m_base.IsValid(); // at least the base must be valid for this to be a runnable title
}
bool IsSystemDataTitle() const
{
return m_base.IsSystemDataTitle();
}
void SetBase(const TitleInfo& titleInfo)
{
m_base = titleInfo;
@ -84,7 +89,7 @@ public:
std::string GetTitleName()
{
cemu_assert_debug(m_base.IsValid());
return m_base.GetTitleName(); // long name
return m_base.GetMetaTitleName(); // long name
}
uint16 GetVersion() const
@ -94,6 +99,13 @@ public:
return m_base.GetAppTitleVersion();
}
uint32 GetSDKVersion() const
{
if (m_update.IsValid())
return m_update.GetAppSDKVersion();
return m_base.GetAppSDKVersion();
}
CafeConsoleRegion GetRegion() const
{
if (m_update.IsValid())

View file

@ -127,7 +127,8 @@ TitleInfo::CachedInfo TitleInfo::MakeCacheEntry()
e.subPath = m_subPath;
e.titleId = GetAppTitleId();
e.titleVersion = GetAppTitleVersion();
e.titleName = GetTitleName();
e.sdkVersion = GetAppSDKVersion();
e.titleName = GetMetaTitleName();
e.region = GetMetaRegion();
e.group_id = GetAppGroup();
e.app_type = GetAppType();
@ -427,7 +428,7 @@ void TitleInfo::Unmount(std::string_view virtualPath)
continue;
fsc_unmount(itr.second.c_str(), itr.first);
std::erase(m_mountpoints, itr);
// if the last mount point got unmounted, delete any open devices
// if the last mount point got unmounted, close any open devices
if (m_mountpoints.empty())
{
if (m_wudVolume)
@ -436,13 +437,12 @@ void TitleInfo::Unmount(std::string_view virtualPath)
delete m_wudVolume;
m_wudVolume = nullptr;
}
}
// wua files use reference counting
if (m_zarchive)
{
_ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive);
if (m_mountpoints.empty())
m_zarchive = nullptr;
if (m_zarchive)
{
_ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive);
if (m_mountpoints.empty())
m_zarchive = nullptr;
}
}
return;
}
@ -467,7 +467,7 @@ bool TitleInfo::ParseXmlInfo()
{
cemu_assert(m_isValid);
if (m_hasParsedXmlFiles)
return m_parsedMetaXml && m_parsedAppXml && m_parsedCosXml;
return m_isValid;
m_hasParsedXmlFiles = true;
std::string mountPath = GetUniqueTempMountingPath();
@ -489,10 +489,16 @@ bool TitleInfo::ParseXmlInfo()
Unmount(mountPath);
bool hasAnyXml = m_parsedMetaXml || m_parsedAppXml || m_parsedCosXml;
if (!m_parsedMetaXml || !m_parsedAppXml || !m_parsedCosXml)
// some system titles dont have a meta.xml file
bool allowMissingMetaXml = false;
if(m_parsedAppXml && this->IsSystemDataTitle())
{
allowMissingMetaXml = true;
}
if ((allowMissingMetaXml == false && !m_parsedMetaXml) || !m_parsedAppXml || !m_parsedCosXml)
{
bool hasAnyXml = m_parsedMetaXml || m_parsedAppXml || m_parsedCosXml;
if (hasAnyXml)
cemuLog_log(LogType::Force, "Title has missing meta .xml files. Title path: {}", _pathToUtf8(m_fullPath));
delete m_parsedMetaXml;
@ -531,6 +537,8 @@ bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData)
m_parsedAppXml->app_type = (uint32)std::stoull(child.text().as_string(), nullptr, 16);
else if (name == "group_id")
m_parsedAppXml->group_id = (uint32)std::stoull(child.text().as_string(), nullptr, 16);
else if (name == "sdk_version")
m_parsedAppXml->sdk_version = (uint32)std::stoull(child.text().as_string(), nullptr, 16);
}
return true;
}
@ -557,6 +565,17 @@ uint16 TitleInfo::GetAppTitleVersion() const
return 0;
}
uint32 TitleInfo::GetAppSDKVersion() const
{
cemu_assert_debug(m_isValid);
if (m_parsedAppXml)
return m_parsedAppXml->sdk_version;
if (m_cachedInfo)
return m_cachedInfo->sdkVersion;
cemu_assert_suspicious();
return 0;
}
uint32 TitleInfo::GetAppGroup() const
{
cemu_assert_debug(m_isValid);
@ -585,7 +604,7 @@ TitleIdParser::TITLE_TYPE TitleInfo::GetTitleType()
return tip.GetType();
}
std::string TitleInfo::GetTitleName() const
std::string TitleInfo::GetMetaTitleName() const
{
cemu_assert_debug(m_isValid);
if (m_parsedMetaXml)
@ -600,7 +619,6 @@ std::string TitleInfo::GetTitleName() const
}
if (m_cachedInfo)
return m_cachedInfo->titleName;
cemu_assert_suspicious();
return "";
}
@ -611,7 +629,6 @@ CafeConsoleRegion TitleInfo::GetMetaRegion() const
return m_parsedMetaXml->GetRegion();
if (m_cachedInfo)
return m_cachedInfo->region;
cemu_assert_suspicious();
return CafeConsoleRegion::JPN;
}

View file

@ -22,6 +22,7 @@ struct ParsedAppXml
uint16 title_version;
uint32 app_type;
uint32 group_id;
uint32 sdk_version;
};
struct ParsedCosXml
@ -69,6 +70,7 @@ public:
std::string subPath; // for WUA
uint64 titleId;
uint16 titleVersion;
uint32 sdkVersion;
std::string titleName;
CafeConsoleRegion region;
uint32 group_id;
@ -115,12 +117,23 @@ public:
return m_uid == rhs.m_uid;
}
bool IsSystemDataTitle() const
{
if(!IsValid())
return false;
uint32 appType = GetAppType();
if(appType == 0)
return false; // not a valid app_type, but handle this in case some users use placeholder .xml data with fields zeroed-out
return ((appType>>24)&0x80) == 0;
}
// API which requires parsed meta data or cached info
TitleId GetAppTitleId() const; // from app.xml
uint16 GetAppTitleVersion() const; // from app.xml
uint32 GetAppSDKVersion() const; // from app.xml
uint32 GetAppGroup() const; // from app.xml
uint32 GetAppType() const; // from app.xml
std::string GetTitleName() const; // from meta.xml
std::string GetMetaTitleName() const; // from meta.xml
CafeConsoleRegion GetMetaRegion() const; // from meta.xml
uint32 GetOlvAccesskey() const;

View file

@ -65,6 +65,7 @@ void CafeTitleList::LoadCacheFile()
if( !TitleIdParser::ParseFromStr(titleInfoNode.attribute("titleId").as_string(), titleId))
continue;
uint16 titleVersion = titleInfoNode.attribute("version").as_uint();
uint32 sdkVersion = titleInfoNode.attribute("sdk_version").as_uint();
TitleInfo::TitleDataFormat format = (TitleInfo::TitleDataFormat)ConvertString<uint32>(titleInfoNode.child_value("format"));
CafeConsoleRegion region = (CafeConsoleRegion)ConvertString<uint32>(titleInfoNode.child_value("region"));
std::string name = titleInfoNode.child_value("name");
@ -76,6 +77,7 @@ void CafeTitleList::LoadCacheFile()
TitleInfo::CachedInfo cacheEntry;
cacheEntry.titleId = titleId;
cacheEntry.titleVersion = titleVersion;
cacheEntry.sdkVersion = sdkVersion;
cacheEntry.titleDataFormat = format;
cacheEntry.region = region;
cacheEntry.titleName = std::move(name);
@ -115,6 +117,7 @@ void CafeTitleList::StoreCacheFile()
auto titleInfoNode = title_list_node.append_child("title");
titleInfoNode.append_attribute("titleId").set_value(fmt::format("{:016x}", info.titleId).c_str());
titleInfoNode.append_attribute("version").set_value(fmt::format("{:}", info.titleVersion).c_str());
titleInfoNode.append_attribute("sdk_version").set_value(fmt::format("{:}", info.sdkVersion).c_str());
titleInfoNode.append_attribute("group_id").set_value(fmt::format("{:08x}", info.group_id).c_str());
titleInfoNode.append_attribute("app_type").set_value(fmt::format("{:08x}", info.app_type).c_str());
titleInfoNode.append_child("region").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (uint32)info.region).c_str());
@ -638,12 +641,15 @@ GameInfo2 CafeTitleList::GetGameInfo(TitleId titleId)
{
TitleId appTitleId = it->GetAppTitleId();
if (appTitleId == baseTitleId)
{
gameInfo.SetBase(*it);
}
if (hasSeparateUpdateTitleId && appTitleId == updateTitleId)
{
gameInfo.SetUpdate(*it);
}
}
// if this title can have AOC content then do a second scan
// todo - get a list of all AOC title ids from the base/update meta information
// for now we assume there is a direct match between the base titleId and the aoc titleId