mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-25 18:46:55 +01:00
Add support for games in NUS format (.app)
Requires title.tmd and title.tik in same directory
This commit is contained in:
parent
f9f6206929
commit
5ad57bb0c9
@ -78,13 +78,6 @@ struct RPLRegionMappingTable
|
|||||||
void RPLLoader_UnloadModule(RPLModule* rpl);
|
void RPLLoader_UnloadModule(RPLModule* rpl);
|
||||||
void RPLLoader_RemoveDependency(const char* name);
|
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)
|
uint8* RPLLoader_AllocateTrampolineCodeSpace(RPLModule* rplLoaderContext, sint32 size)
|
||||||
{
|
{
|
||||||
if (rplLoaderContext)
|
if (rplLoaderContext)
|
||||||
|
@ -99,6 +99,7 @@ TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo)
|
|||||||
if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS &&
|
if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS &&
|
||||||
cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE &&
|
cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE &&
|
||||||
cachedInfo.titleDataFormat != TitleDataFormat::WUD &&
|
cachedInfo.titleDataFormat != TitleDataFormat::WUD &&
|
||||||
|
cachedInfo.titleDataFormat != TitleDataFormat::NUS &&
|
||||||
cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE)
|
cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE)
|
||||||
return;
|
return;
|
||||||
if (cachedInfo.path.empty())
|
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") ||
|
else if (boost::iends_with(filenameStr, ".wud") ||
|
||||||
boost::iends_with(filenameStr, ".wux") ||
|
boost::iends_with(filenameStr, ".wux") ||
|
||||||
boost::iends_with(filenameStr, ".iso"))
|
boost::iends_with(filenameStr, ".iso"))
|
||||||
{
|
{
|
||||||
formatOut = TitleDataFormat::WUD;
|
formatOut = TitleDataFormat::WUD;
|
||||||
pathOut = path;
|
pathOut = path;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (boost::iequals(filenameStr, "title.tmd"))
|
||||||
|
{
|
||||||
|
formatOut = TitleDataFormat::NUS;
|
||||||
|
pathOut = path;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
else if (boost::iends_with(filenameStr, ".wua"))
|
else if (boost::iends_with(filenameStr, ".wua"))
|
||||||
{
|
{
|
||||||
formatOut = TitleDataFormat::WIIU_ARCHIVE;
|
formatOut = TitleDataFormat::WIIU_ARCHIVE;
|
||||||
@ -378,12 +385,15 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_titleFormat == TitleDataFormat::WUD)
|
else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS)
|
||||||
{
|
{
|
||||||
if (m_mountpoints.empty())
|
if (m_mountpoints.empty())
|
||||||
{
|
{
|
||||||
cemu_assert_debug(!m_wudVolume);
|
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)
|
if (!m_wudVolume)
|
||||||
return false;
|
return false;
|
||||||
@ -433,7 +443,7 @@ void TitleInfo::Unmount(std::string_view virtualPath)
|
|||||||
{
|
{
|
||||||
if (m_wudVolume)
|
if (m_wudVolume)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD);
|
cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS);
|
||||||
delete m_wudVolume;
|
delete m_wudVolume;
|
||||||
m_wudVolume = nullptr;
|
m_wudVolume = nullptr;
|
||||||
}
|
}
|
||||||
@ -664,6 +674,9 @@ std::string TitleInfo::GetPrintPath() const
|
|||||||
case TitleDataFormat::WUD:
|
case TitleDataFormat::WUD:
|
||||||
tmp.append(" [WUD]");
|
tmp.append(" [WUD]");
|
||||||
break;
|
break;
|
||||||
|
case TitleDataFormat::NUS:
|
||||||
|
tmp.append(" [NUS]");
|
||||||
|
break;
|
||||||
case TitleDataFormat::WIIU_ARCHIVE:
|
case TitleDataFormat::WIIU_ARCHIVE:
|
||||||
tmp.append(" [WUA]");
|
tmp.append(" [WUA]");
|
||||||
break;
|
break;
|
||||||
|
@ -60,6 +60,7 @@ public:
|
|||||||
HOST_FS = 1, // host filesystem directory (fullPath points to root with content/code/meta subfolders)
|
HOST_FS = 1, // host filesystem directory (fullPath points to root with content/code/meta subfolders)
|
||||||
WUD = 2, // WUD or WUX
|
WUD = 2, // WUD or WUX
|
||||||
WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua)
|
WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua)
|
||||||
|
NUS = 4, // NUS format. Directory with .app files, title.tik and title.tmd
|
||||||
// error
|
// error
|
||||||
INVALID_STRUCTURE = 0,
|
INVALID_STRUCTURE = 0,
|
||||||
};
|
};
|
||||||
|
@ -324,17 +324,25 @@ bool CafeTitleList::RefreshWorkerThread()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _IsKnownFileExtension(std::string fileExtension)
|
bool _IsKnownFileNameOrExtension(const fs::path& path)
|
||||||
{
|
{
|
||||||
|
std::string fileExtension = _pathToUtf8(path.extension());
|
||||||
for (auto& it : fileExtension)
|
for (auto& it : fileExtension)
|
||||||
if (it >= 'A' && it <= 'Z')
|
it = _ansiToLower(it);
|
||||||
it -= ('A' - 'a');
|
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
|
return
|
||||||
fileExtension == ".wud" ||
|
fileExtension == ".wud" ||
|
||||||
fileExtension == ".wux" ||
|
fileExtension == ".wux" ||
|
||||||
fileExtension == ".iso" ||
|
fileExtension == ".iso" ||
|
||||||
fileExtension == ".wua";
|
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)
|
void CafeTitleList::ScanGamePath(const fs::path& path)
|
||||||
@ -353,7 +361,6 @@ void CafeTitleList::ScanGamePath(const fs::path& path)
|
|||||||
else if (it.is_directory(ec))
|
else if (it.is_directory(ec))
|
||||||
{
|
{
|
||||||
dirsInDirectory.emplace_back(it.path());
|
dirsInDirectory.emplace_back(it.path());
|
||||||
|
|
||||||
std::string dirName = _pathToUtf8(it.path().filename());
|
std::string dirName = _pathToUtf8(it.path().filename());
|
||||||
if (boost::iequals(dirName, "content"))
|
if (boost::iequals(dirName, "content"))
|
||||||
hasContentFolder = true;
|
hasContentFolder = true;
|
||||||
@ -366,10 +373,10 @@ void CafeTitleList::ScanGamePath(const fs::path& path)
|
|||||||
// always check individual files
|
// always check individual files
|
||||||
for (auto& it : filesInDirectory)
|
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())
|
if (!it.has_extension())
|
||||||
continue;
|
continue;
|
||||||
if (!_IsKnownFileExtension(_pathToUtf8(it.extension())))
|
if (!_IsKnownFileNameOrExtension(it))
|
||||||
continue;
|
continue;
|
||||||
AddTitleFromPath(it);
|
AddTitleFromPath(it);
|
||||||
}
|
}
|
||||||
|
@ -468,6 +468,14 @@ inline fs::path _utf8ToPath(std::string_view input)
|
|||||||
return fs::path(v);
|
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
|
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:
|
public:
|
||||||
|
@ -639,13 +639,15 @@ void MainWindow::OnFileMenu(wxCommandEvent& event)
|
|||||||
if (menuId == MAINFRAME_MENU_ID_FILE_LOAD)
|
if (menuId == MAINFRAME_MENU_ID_FILE_LOAD)
|
||||||
{
|
{
|
||||||
const auto wildcard = formatWxString(
|
const auto wildcard = formatWxString(
|
||||||
"{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf"
|
"{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf;title.tmd"
|
||||||
"|{}|*.wud;*.wux;*.iso"
|
"|{}|*.wud;*.wux;*.iso"
|
||||||
|
"|{}|title.tmd"
|
||||||
"|{}|*.wua"
|
"|{}|*.wua"
|
||||||
"|{}|*.rpx;*.elf"
|
"|{}|*.rpx;*.elf"
|
||||||
"|{}|*",
|
"|{}|*",
|
||||||
_("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"),
|
_("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"),
|
||||||
_("Wii U image (*.wud, *.wux, *.iso, *.wad)"),
|
_("Wii U image (*.wud, *.wux, *.iso, *.wad)"),
|
||||||
|
_("Wii U NUS content"),
|
||||||
_("Wii U archive (*.wua)"),
|
_("Wii U archive (*.wua)"),
|
||||||
_("Wii U executable (*.rpx, *.elf)"),
|
_("Wii U executable (*.rpx, *.elf)"),
|
||||||
_("All files (*.*)")
|
_("All files (*.*)")
|
||||||
|
@ -941,6 +941,8 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu
|
|||||||
return _("Folder");
|
return _("Folder");
|
||||||
case wxTitleManagerList::EntryFormat::WUD:
|
case wxTitleManagerList::EntryFormat::WUD:
|
||||||
return _("WUD");
|
return _("WUD");
|
||||||
|
case wxTitleManagerList::EntryFormat::NUS:
|
||||||
|
return _("NUS");
|
||||||
case wxTitleManagerList::EntryFormat::WUA:
|
case wxTitleManagerList::EntryFormat::WUA:
|
||||||
return _("WUA");
|
return _("WUA");
|
||||||
}
|
}
|
||||||
@ -1010,16 +1012,19 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt
|
|||||||
wxTitleManagerList::EntryFormat entryFormat;
|
wxTitleManagerList::EntryFormat entryFormat;
|
||||||
switch (titleInfo.GetFormat())
|
switch (titleInfo.GetFormat())
|
||||||
{
|
{
|
||||||
case TitleInfo::TitleDataFormat::HOST_FS:
|
|
||||||
default:
|
|
||||||
entryFormat = EntryFormat::Folder;
|
|
||||||
break;
|
|
||||||
case TitleInfo::TitleDataFormat::WUD:
|
case TitleInfo::TitleDataFormat::WUD:
|
||||||
entryFormat = EntryFormat::WUD;
|
entryFormat = EntryFormat::WUD;
|
||||||
break;
|
break;
|
||||||
|
case TitleInfo::TitleDataFormat::NUS:
|
||||||
|
entryFormat = EntryFormat::NUS;
|
||||||
|
break;
|
||||||
case TitleInfo::TitleDataFormat::WIIU_ARCHIVE:
|
case TitleInfo::TitleDataFormat::WIIU_ARCHIVE:
|
||||||
entryFormat = EntryFormat::WUA;
|
entryFormat = EntryFormat::WUA;
|
||||||
break;
|
break;
|
||||||
|
case TitleInfo::TitleDataFormat::HOST_FS:
|
||||||
|
default:
|
||||||
|
entryFormat = EntryFormat::Folder;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED)
|
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED)
|
||||||
|
@ -42,6 +42,7 @@ public:
|
|||||||
{
|
{
|
||||||
Folder,
|
Folder,
|
||||||
WUD,
|
WUD,
|
||||||
|
NUS,
|
||||||
WUA,
|
WUA,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user