From 5ad57bb0c9f6d0966d8ff04696aa16ba384c3ed0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:05:40 +0200 Subject: [PATCH] Add support for games in NUS format (.app) Requires title.tmd and title.tik in same directory --- src/Cafe/OS/RPL/rpl.cpp | 7 ------- src/Cafe/TitleList/TitleInfo.cpp | 23 ++++++++++++++++++----- src/Cafe/TitleList/TitleInfo.h | 1 + src/Cafe/TitleList/TitleList.cpp | 21 ++++++++++++++------- src/Common/precompiled.h | 8 ++++++++ src/gui/MainWindow.cpp | 4 +++- src/gui/components/wxTitleManagerList.cpp | 13 +++++++++---- src/gui/components/wxTitleManagerList.h | 1 + 8 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp index 48c7acc4..0e6d153f 100644 --- a/src/Cafe/OS/RPL/rpl.cpp +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -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) diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 8bbb940d..867e0a7f 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -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; diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index da430adc..536e9ccb 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -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, }; diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 2e50cbf9..03fd0855 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -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); } diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 580aeb23..60495f53 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -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: diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d0ec6e9f..e8e90f02 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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 (*.*)") diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index aad46c52..c65459aa 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -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) diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 07556068..cab531c4 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -42,6 +42,7 @@ public: { Folder, WUD, + NUS, WUA, };