mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-14 18:58:29 +12:00
Initial support for title switching + better Wii U menu compatibility (#907)
This commit is contained in:
parent
bfbeeae6f6
commit
2200cc0ddf
95 changed files with 2549 additions and 746 deletions
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue