Add support for games in NUS format (.app)

Requires title.tmd and title.tik in same directory
This commit is contained in:
Exzap 2023-09-27 11:05:40 +02:00
parent f9f6206929
commit 5ad57bb0c9
8 changed files with 54 additions and 24 deletions

View file

@ -78,13 +78,6 @@ struct RPLRegionMappingTable
void RPLLoader_UnloadModule(RPLModule* rpl);
void RPLLoader_RemoveDependency(const char* name);
char _ansiToLower(char c)
{
if (c >= 'A' && c <= 'Z')
c -= ('A' - 'a');
return c;
}
uint8* RPLLoader_AllocateTrampolineCodeSpace(RPLModule* rplLoaderContext, sint32 size)
{
if (rplLoaderContext)

View file

@ -99,6 +99,7 @@ TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo)
if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS &&
cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE &&
cachedInfo.titleDataFormat != TitleDataFormat::WUD &&
cachedInfo.titleDataFormat != TitleDataFormat::NUS &&
cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE)
return;
if (cachedInfo.path.empty())
@ -197,13 +198,19 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF
}
}
else if (boost::iends_with(filenameStr, ".wud") ||
boost::iends_with(filenameStr, ".wux") ||
boost::iends_with(filenameStr, ".iso"))
boost::iends_with(filenameStr, ".wux") ||
boost::iends_with(filenameStr, ".iso"))
{
formatOut = TitleDataFormat::WUD;
pathOut = path;
return true;
}
else if (boost::iequals(filenameStr, "title.tmd"))
{
formatOut = TitleDataFormat::NUS;
pathOut = path;
return true;
}
else if (boost::iends_with(filenameStr, ".wua"))
{
formatOut = TitleDataFormat::WIIU_ARCHIVE;
@ -378,12 +385,15 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder,
return false;
}
}
else if (m_titleFormat == TitleDataFormat::WUD)
else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS)
{
if (m_mountpoints.empty())
{
cemu_assert_debug(!m_wudVolume);
m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath);
if(m_titleFormat == TitleDataFormat::WUD)
m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath); // open wud/wux
else
m_wudVolume = FSTVolume::OpenFromContentFolder(m_fullPath.parent_path()); // open from .app files directory, the path points to /title.tmd
}
if (!m_wudVolume)
return false;
@ -433,7 +443,7 @@ void TitleInfo::Unmount(std::string_view virtualPath)
{
if (m_wudVolume)
{
cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD);
cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS);
delete m_wudVolume;
m_wudVolume = nullptr;
}
@ -664,6 +674,9 @@ std::string TitleInfo::GetPrintPath() const
case TitleDataFormat::WUD:
tmp.append(" [WUD]");
break;
case TitleDataFormat::NUS:
tmp.append(" [NUS]");
break;
case TitleDataFormat::WIIU_ARCHIVE:
tmp.append(" [WUA]");
break;

View file

@ -60,6 +60,7 @@ public:
HOST_FS = 1, // host filesystem directory (fullPath points to root with content/code/meta subfolders)
WUD = 2, // WUD or WUX
WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua)
NUS = 4, // NUS format. Directory with .app files, title.tik and title.tmd
// error
INVALID_STRUCTURE = 0,
};

View file

@ -324,17 +324,25 @@ bool CafeTitleList::RefreshWorkerThread()
return true;
}
bool _IsKnownFileExtension(std::string fileExtension)
bool _IsKnownFileNameOrExtension(const fs::path& path)
{
std::string fileExtension = _pathToUtf8(path.extension());
for (auto& it : fileExtension)
if (it >= 'A' && it <= 'Z')
it -= ('A' - 'a');
it = _ansiToLower(it);
if(fileExtension == ".tmd")
{
// must be "title.tmd"
std::string fileName = _pathToUtf8(path.filename());
for (auto& it : fileName)
it = _ansiToLower(it);
return fileName == "title.tmd";
}
return
fileExtension == ".wud" ||
fileExtension == ".wux" ||
fileExtension == ".iso" ||
fileExtension == ".wua";
// note: To detect extracted titles with RPX we use the content/code/meta folder structure
// note: To detect extracted titles with RPX we rely on the presence of the content,code,meta directory structure
}
void CafeTitleList::ScanGamePath(const fs::path& path)
@ -353,7 +361,6 @@ void CafeTitleList::ScanGamePath(const fs::path& path)
else if (it.is_directory(ec))
{
dirsInDirectory.emplace_back(it.path());
std::string dirName = _pathToUtf8(it.path().filename());
if (boost::iequals(dirName, "content"))
hasContentFolder = true;
@ -366,10 +373,10 @@ void CafeTitleList::ScanGamePath(const fs::path& path)
// always check individual files
for (auto& it : filesInDirectory)
{
// since checking files is slow, we only do it for known file extensions
// since checking individual files is slow, we limit it to known file names or extensions
if (!it.has_extension())
continue;
if (!_IsKnownFileExtension(_pathToUtf8(it.extension())))
if (!_IsKnownFileNameOrExtension(it))
continue;
AddTitleFromPath(it);
}

View file

@ -468,6 +468,14 @@ inline fs::path _utf8ToPath(std::string_view input)
return fs::path(v);
}
// locale-independent variant of tolower() which also matches Wii U behavior
inline char _ansiToLower(char c)
{
if (c >= 'A' && c <= 'Z')
c -= ('A' - 'a');
return c;
}
class RunAtCemuBoot // -> replaces this with direct function calls. Linkers other than MSVC may optimize way object files entirely if they are not referenced from outside. So a source file self-registering using this would be causing issues
{
public:

View file

@ -639,13 +639,15 @@ void MainWindow::OnFileMenu(wxCommandEvent& event)
if (menuId == MAINFRAME_MENU_ID_FILE_LOAD)
{
const auto wildcard = formatWxString(
"{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf"
"{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf;title.tmd"
"|{}|*.wud;*.wux;*.iso"
"|{}|title.tmd"
"|{}|*.wua"
"|{}|*.rpx;*.elf"
"|{}|*",
_("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"),
_("Wii U image (*.wud, *.wux, *.iso, *.wad)"),
_("Wii U NUS content"),
_("Wii U archive (*.wua)"),
_("Wii U executable (*.rpx, *.elf)"),
_("All files (*.*)")

View file

@ -941,6 +941,8 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu
return _("Folder");
case wxTitleManagerList::EntryFormat::WUD:
return _("WUD");
case wxTitleManagerList::EntryFormat::NUS:
return _("NUS");
case wxTitleManagerList::EntryFormat::WUA:
return _("WUA");
}
@ -1010,16 +1012,19 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt
wxTitleManagerList::EntryFormat entryFormat;
switch (titleInfo.GetFormat())
{
case TitleInfo::TitleDataFormat::HOST_FS:
default:
entryFormat = EntryFormat::Folder;
break;
case TitleInfo::TitleDataFormat::WUD:
entryFormat = EntryFormat::WUD;
break;
case TitleInfo::TitleDataFormat::NUS:
entryFormat = EntryFormat::NUS;
break;
case TitleInfo::TitleDataFormat::WIIU_ARCHIVE:
entryFormat = EntryFormat::WUA;
break;
case TitleInfo::TitleDataFormat::HOST_FS:
default:
entryFormat = EntryFormat::Folder;
break;
}
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED)

View file

@ -42,6 +42,7 @@ public:
{
Folder,
WUD,
NUS,
WUA,
};