#include "Cafe/OS/common/OSCommon.h" #include "gui/wxgui.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "audio/IAudioAPI.h" #include "audio/IAudioInputAPI.h" #include "config/ActiveSettings.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "util/helpers/SystemException.h" #include "Common/cpu_features.h" #include "input/InputManager.h" #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/Filesystem/FST/FST.h" #include "Common/FileStream.h" #include "GamePatch.h" #include "HW/Espresso/Debugger/GDBStub.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/IOSU/legacy/iosu_fpd.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/IOSU/legacy/iosu_mcp.h" #include "Cafe/IOSU/legacy/iosu_acp.h" #include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/IOSU/fsa/iosu_fsa.h" #include "Cafe/IOSU/ODM/iosu_odm.h" // Cafe OS initializer and shutdown functions #include "Cafe/OS/libs/avm/avm.h" #include "Cafe/OS/libs/drmapp/drmapp.h" #include "Cafe/OS/libs/TCL/TCL.h" #include "Cafe/OS/libs/snd_user/snd_user.h" #include "Cafe/OS/libs/h264_avc/h264dec.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/mic/mic.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" #include "Cafe/OS/libs/nn_ccr/nn_ccr.h" #include "Cafe/OS/libs/nn_temp/nn_temp.h" #include "Cafe/OS/libs/nn_save/nn_save.h" // HW interfaces #include "Cafe/HW/SI/si.h" // dependency to be removed #include "gui/guiWrapper.h" #include #if BOOST_OS_LINUX #include #elif BOOST_OS_MACOS #include #include #endif std::string _pathToExecutable; std::string _pathToBaseExecutable; RPLModule* applicationRPX = nullptr; uint32 currentBaseApplicationHash = 0; uint32 currentUpdatedApplicationHash = 0; bool isLaunchTypeELF = false; MPTR _entryPoint = MPTR_NULL; uint32 generateHashFromRawRPXData(uint8* rpxData, sint32 size) { uint32 h = 0x3416DCBF; for (sint32 i = 0; i < size; i++) { uint32 c = rpxData[i]; h = (h << 3) | (h >> 29); h += c; } return h; } bool ScanForRPX() { bool rpxFound = false; sint32 fscStatus = 0; FSCVirtualFile* fscDirItr = fsc_openDirIterator("/internal/current_title/code/", &fscStatus); if (fscDirItr) { FSCDirEntry dirEntry; while (fsc_nextDir(fscDirItr, &dirEntry)) { sint32 dirItrPathLen = strlen(dirEntry.path); if (dirItrPathLen < 4) continue; if (boost::iequals(dirEntry.path + dirItrPathLen - 4, ".rpx")) { rpxFound = true; _pathToExecutable = fmt::format("/internal/current_title/code/{}", dirEntry.path); break; } } fsc_close(fscDirItr); } return rpxFound; } void SetEntryPoint(MPTR entryPoint) { _entryPoint = entryPoint; } // load executable into virtual memory and set entrypoint void LoadMainExecutable() { isLaunchTypeELF = false; // when launching from a disc image _pathToExecutable is initially empty if (_pathToExecutable.empty()) { // try to get the RPX path from the meta files // todo // otherwise search for first file with .rpx extension in the code folder if (!ScanForRPX()) { cemuLog_log(LogType::Force, "Unable to find RPX executable"); cemuLog_waitForFlush(); cemu_assert(false); } } // extract and load RPX uint32 rpxSize = 0; uint8* rpxData = fsc_extractFile(_pathToExecutable.c_str(), &rpxSize); if (rpxData == nullptr) { cemuLog_log(LogType::Force, "Failed to load \"{}\"", _pathToExecutable); cemuLog_waitForFlush(); cemu_assert(false); } currentUpdatedApplicationHash = generateHashFromRawRPXData(rpxData, rpxSize); // determine if this file is an ELF const uint8 elfHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0x00,0x00 }; if (rpxSize >= 10 && memcmp(rpxData, elfHeaderMagic, sizeof(elfHeaderMagic)) == 0) { // ELF SetEntryPoint(ELF_LoadFromMemory(rpxData, rpxSize, _pathToExecutable.c_str())); isLaunchTypeELF = true; } else { // RPX RPLLoader_AddDependency(_pathToExecutable.c_str()); applicationRPX = RPLLoader_LoadFromMemory(rpxData, rpxSize, (char*)_pathToExecutable.c_str()); if (!applicationRPX) { wxMessageBox(_("Failed to run this title because the executable is damaged")); cemuLog_createLogFile(false); cemuLog_waitForFlush(); exit(0); } RPLLoader_SetMainModule(applicationRPX); SetEntryPoint(RPLLoader_GetModuleEntrypoint(applicationRPX)); } free(rpxData); // get RPX hash of game without update uint32 baseRpxSize = 0; uint8* baseRpxData = fsc_extractFile(!_pathToBaseExecutable.empty() ? _pathToBaseExecutable.c_str() : _pathToExecutable.c_str(), &baseRpxSize, FSC_PRIORITY_BASE); if (baseRpxData == nullptr) { currentBaseApplicationHash = currentUpdatedApplicationHash; } else { currentBaseApplicationHash = generateHashFromRawRPXData(baseRpxData, baseRpxSize); } free(baseRpxData); debug_printf("RPXHash: 0x%08x\n", currentBaseApplicationHash); } fs::path getTitleSavePath() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); return ActiveSettings::GetMlcPath("usr/save/{:08X}/{:08X}/user/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } void InfoLog_TitleLoaded() { cemuLog_createLogFile(false); uint64 titleId = CafeSystem::GetForegroundTitleId(); cemuLog_log(LogType::Force, "------- Loaded title -------"); cemuLog_log(LogType::Force, "TitleId: {:08x}-{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); cemuLog_log(LogType::Force, "TitleVersion: v{}", CafeSystem::GetForegroundTitleVersion()); CafeConsoleRegion region = CafeSystem::GetForegroundTitleRegion(); if(region == CafeConsoleRegion::JPN) cemuLog_log(LogType::Force, "TitleRegion: JP"); else if (region == CafeConsoleRegion::EUR) cemuLog_log(LogType::Force, "TitleRegion: EU"); else if (region == CafeConsoleRegion::USA) cemuLog_log(LogType::Force, "TitleRegion: US"); fs::path effectiveSavePath = getTitleSavePath(); std::error_code ec; const bool saveDirExists = fs::exists(effectiveSavePath, ec); cemuLog_log(LogType::Force, "Save path: {}{}", _pathToUtf8(effectiveSavePath), saveDirExists ? "" : " (not present)"); // log shader cache name cemuLog_log(LogType::Force, "Shader cache file: shaderCache/transferable/{:016x}.bin", titleId); // game profile info std::string gameProfilePath; if(g_current_game_profile->IsDefaultProfile()) gameProfilePath = fmt::format("gameProfiles/default/{:016x}.ini", titleId); else gameProfilePath = fmt::format("gameProfiles/{:016x}.ini", titleId); cemuLog_log(LogType::Force, "gameprofile path: {}", g_current_game_profile->IsLoaded() ? gameProfilePath : std::string(" (not present)")); // rpx hash of updated game cemuLog_log(LogType::Force, "RPX hash (updated): {:08x}", currentUpdatedApplicationHash); cemuLog_log(LogType::Force, "RPX hash (base): {:08x}", currentBaseApplicationHash); memory_logModifiedMemoryRanges(); } void InfoLog_PrintActiveSettings() { const auto& config = GetConfig(); cemuLog_log(LogType::Force, "------- Active settings -------"); // settings to log: cemuLog_log(LogType::Force, "CPU-Mode: {}{}", fmt::format("{}", ActiveSettings::GetCPUMode()).c_str(), g_current_game_profile->GetCPUMode().has_value() ? " (gameprofile)" : ""); cemuLog_log(LogType::Force, "Load shared libraries: {}{}", ActiveSettings::LoadSharedLibrariesEnabled() ? "true" : "false", g_current_game_profile->ShouldLoadSharedLibraries().has_value() ? " (gameprofile)" : ""); cemuLog_log(LogType::Force, "Use precompiled shaders: {}{}", fmt::format("{}", ActiveSettings::GetPrecompiledShadersOption()), g_current_game_profile->GetPrecompiledShadersState().has_value() ? " (gameprofile)" : ""); cemuLog_log(LogType::Force, "Full sync at GX2DrawDone: {}", ActiveSettings::WaitForGX2DrawDoneEnabled() ? "true" : "false"); cemuLog_log(LogType::Force, "Strict shader mul: {}", g_current_game_profile->GetAccurateShaderMul() == AccurateShaderMulOption::True ? "true" : "false"); if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kVulkan) { cemuLog_log(LogType::Force, "Async compile: {}", GetConfig().async_compile.GetValue() ? "true" : "false"); if(!GetConfig().vk_accurate_barriers.GetValue()) cemuLog_log(LogType::Force, "Accurate barriers are disabled!"); } cemuLog_log(LogType::Force, "Console language: {}", config.console_language); } struct SharedDataEntry { /* +0x00 */ uint32be name; /* +0x04 */ uint32be fileType; // 2 = font /* +0x08 */ uint32be kernelFilenamePtr; /* +0x0C */ MEMPTR data; /* +0x10 */ uint32be size; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; }; struct { uint32 name; uint32 fileType; const char* fileName; const char* resourcePath; const char* mlcPath; }shareddataDef[] = { 0xFFCAFE01, 2, "CafeCn.ttf", "resources/sharedFonts/CafeCn.ttf", "sys/title/0005001b/10042400/content/CafeCn.ttf", 0xFFCAFE02, 2, "CafeKr.ttf", "resources/sharedFonts/CafeKr.ttf", "sys/title/0005001b/10042400/content/CafeKr.ttf", 0xFFCAFE03, 2, "CafeStd.ttf", "resources/sharedFonts/CafeStd.ttf", "sys/title/0005001b/10042400/content/CafeStd.ttf", 0xFFCAFE04, 2, "CafeTw.ttf", "resources/sharedFonts/CafeTw.ttf", "sys/title/0005001b/10042400/content/CafeTw.ttf" }; static_assert(sizeof(SharedDataEntry) == 0x1C); uint32 LoadSharedData() { // check if font files are dumped bool hasAllShareddataFiles = true; for (sint32 i = 0; i < sizeof(shareddataDef) / sizeof(shareddataDef[0]); i++) { bool existsInMLC = fs::exists(ActiveSettings::GetMlcPath(shareddataDef[i].mlcPath)); bool existsInResources = fs::exists(ActiveSettings::GetDataPath(shareddataDef[i].resourcePath)); if (!existsInMLC && !existsInResources) { cemuLog_log(LogType::Force, "Shared font {} is not present", shareddataDef[i].fileName); hasAllShareddataFiles = false; break; } } sint32 numEntries = sizeof(shareddataDef) / sizeof(shareddataDef[0]); if (hasAllShareddataFiles) { // all shareddata font files are present -> load them SharedDataEntry* shareddataTable = (SharedDataEntry*)memory_getPointerFromVirtualOffset(0xF8000000); memset(shareddataTable, 0, sizeof(SharedDataEntry) * numEntries); uint8* dataWritePtr = memory_getPointerFromVirtualOffset(0xF8000000 + sizeof(SharedDataEntry) * numEntries); // setup entries for (sint32 i = 0; i < numEntries; i++) { // try to read font from MLC first auto path = ActiveSettings::GetMlcPath(shareddataDef[i].mlcPath); FileStream* fontFile = FileStream::openFile2(path); // alternatively fall back to our shared fonts if (!fontFile) { path = ActiveSettings::GetDataPath(shareddataDef[i].resourcePath); fontFile = FileStream::openFile2(path); } if (!fontFile) { cemuLog_log(LogType::Force, "Failed to load shared font {}", shareddataDef[i].fileName); continue; } uint32 fileSize = fontFile->GetSize(); fontFile->readData(dataWritePtr, fileSize); delete fontFile; // setup entry shareddataTable[i].name = shareddataDef[i].name; shareddataTable[i].fileType = shareddataDef[i].fileType; shareddataTable[i].kernelFilenamePtr = 0x00000000; shareddataTable[i].data = dataWritePtr; shareddataTable[i].size = fileSize; shareddataTable[i].ukn14 = 0x00000000; shareddataTable[i].ukn18 = 0x00000000; // advance write offset and pad to 16 byte alignment dataWritePtr += ((fileSize + 15) & ~15); } cemuLog_log(LogType::Force, "COS: System fonts found. Generated shareddata ({}KB)", (uint32)(dataWritePtr - (uint8*)shareddataTable) / 1024); return memory_getVirtualOffsetFromPointer(dataWritePtr); } // alternative method: load RAM dump const auto path = ActiveSettings::GetUserDataPath("shareddata.bin"); FileStream* ramDumpFile = FileStream::openFile2(path); if (ramDumpFile) { ramDumpFile->readData(memory_getPointerFromVirtualOffset(0xF8000000), 0x02000000); delete ramDumpFile; return (mmuRange_SHARED_AREA.getBase() + 0x02000000); } return mmuRange_SHARED_AREA.getBase() + sizeof(SharedDataEntry) * numEntries; } void cemu_initForGame() { gui_updateWindowTitles(false, true, 0.0); // input manager apply game profile InputManager::instance().apply_game_profile(); // log info for launched title InfoLog_TitleLoaded(); // determine cycle offset since 1.1.2000 uint64 secondsSince2000_UTC = (uint64)(time(NULL) - 946684800); ppcCyclesSince2000_UTC = secondsSince2000_UTC * (uint64)ESPRESSO_CORE_CLOCK; time_t theTime = (time(NULL) - 946684800); { tm* lt = localtime(&theTime); #if BOOST_OS_WINDOWS theTime = _mkgmtime(lt); #else theTime = timegm(lt); #endif } ppcCyclesSince2000 = theTime * (uint64)ESPRESSO_CORE_CLOCK; ppcCyclesSince2000TimerClock = ppcCyclesSince2000 / 20ULL; PPCTimer_start(); // this must happen after the RPX/RPL files are mapped to memory (coreinit sets up heaps so that they don't overwrite RPX/RPL data) osLib_load(); // link all modules uint32 linkTimeStart = GetTickCount(); RPLLoader_UpdateDependencies(); RPLLoader_Link(); RPLLoader_NotifyControlPassedToApplication(); uint32 linkTime = GetTickCount() - linkTimeStart; cemuLog_log(LogType::Force, "RPL link time: {}ms", linkTime); // for HBL ELF: Setup OS-specifics struct if (isLaunchTypeELF) { memory_writeU32(0x801500, rpl_mapHLEImport(nullptr, "coreinit", "OSDynLoad_Acquire", true)); memory_writeU32(0x801504, rpl_mapHLEImport(nullptr, "coreinit", "OSDynLoad_FindExport", true)); } else { // replace any known function signatures with our HLE implementations and patch bugs in the games GamePatch_scan(); } LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled(); InfoLog_PrintActiveSettings(); Latte_Start(); // check for debugger entrypoint bp if (g_gdbstub) { g_gdbstub->HandleEntryStop(_entryPoint); g_gdbstub->Initialize(); } debugger_handleEntryBreakpoint(_entryPoint); // load graphic packs cemuLog_log(LogType::Force, "------- Activate graphic packs -------"); GraphicPack2::ActivateForCurrentTitle(); // print audio log IAudioAPI::PrintLogging(); IAudioInputAPI::PrintLogging(); // everything initialized cemuLog_log(LogType::Force, "------- Run title -------"); // wait till GPU thread is initialized while (g_isGPUInitFinished == false) std::this_thread::sleep_for(std::chrono::milliseconds(50)); // init initial thread OSThread_t* initialThread = coreinit::OSGetDefaultThread(1); coreinit::OSSetThreadPriority(initialThread, 16); coreinit::OSRunThread(initialThread, PPCInterpreter_makeCallableExportDepr(coreinit_start), 0, nullptr); // init AX and start AX I/O thread snd_core::AXOut_init(); } namespace CafeSystem { void InitVirtualMlcStorage(); void MlcStorageMountTitle(TitleInfo& titleInfo); void MlcStorageUnmountAllTitles(); static bool s_initialized = false; static SystemImplementation* s_implementation{nullptr}; bool sLaunchModeIsStandalone = false; std::optional> s_overrideArgs; bool sSystemRunning = false; TitleId sForegroundTitleId = 0; GameInfo2 sGameInfo_ForegroundTitle; static void _CheckForWine() { #if BOOST_OS_WINDOWS const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); if (!hmodule) return; const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); if (pwine_get_version) { cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version()); } #endif } void logCPUAndMemoryInfo() { std::string cpuName = g_CPUFeatures.GetCPUName(); if (!cpuName.empty()) cemuLog_log(LogType::Force, "CPU: {}", cpuName); #if BOOST_OS_WINDOWS MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB); #elif BOOST_OS_LINUX struct sysinfo info {}; sysinfo(&info); cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL)); #elif BOOST_OS_MACOS int64_t totalRam; size_t size = sizeof(totalRam); int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0); if (result == 0) cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); #endif } #if BOOST_OS_WINDOWS std::string GetWindowsNamedVersion(uint32& buildNumber) { char productName[256]; HKEY hKey; DWORD dwType = REG_SZ; DWORD dwSize = sizeof(productName); if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { if (RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)productName, &dwSize) != ERROR_SUCCESS) strcpy(productName, "Windows"); RegCloseKey(hKey); } OSVERSIONINFO osvi; ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); buildNumber = osvi.dwBuildNumber; return std::string(productName); } #endif void logPlatformInfo() { std::string buffer; const char* platform = NULL; #if BOOST_OS_WINDOWS uint32 buildNumber; std::string windowsVersionName = GetWindowsNamedVersion(buildNumber); buffer = fmt::format("{} (Build {})", windowsVersionName, buildNumber); platform = buffer.c_str(); #elif BOOST_OS_LINUX if (getenv ("APPIMAGE")) platform = "Linux (AppImage)"; else if (getenv ("SNAP")) platform = "Linux (Snap)"; else if (platform = getenv ("container")) { if (strcmp (platform, "flatpak") == 0) platform = "Linux (Flatpak)"; } else platform = "Linux"; #elif BOOST_OS_MACOS platform = "MacOS"; #endif cemuLog_log(LogType::Force, "Platform: {}", platform); } // initialize all subsystems which are persistent and don't depend on a game running void Initialize() { if (s_initialized) return; s_initialized = true; // init core systems cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); fsc_init(); memory_init(); cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); PPCCore_init(); RPLLoader_InitState(); cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); _CheckForWine(); // CPU and RAM info logCPUAndMemoryInfo(); logPlatformInfo(); cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); // misc systems rplSymbolStorage_init(); // allocate memory for all SysAllocators // must happen before COS module init, but also before iosu::kernel::Initialize() SysAllocatorContainer::GetInstance().Initialize(); // init IOSU iosuCrypto_init(); iosu::kernel::Initialize(); iosu::fsa::Initialize(); iosuIoctl_init(); iosuAct_init_depr(); iosu::act::Initialize(); iosu::fpd::Initialize(); iosu::iosuMcp_init(); iosu::mcp::Init(); iosu::iosuAcp_init(); iosu::boss_init(); iosu::nim::Initialize(); iosu::pdm::Initialize(); iosu::odm::Initialize(); // init Cafe OS avm::Initialize(); drmapp::Initialize(); TCL::Initialize(); nn::cmpt::Initialize(); nn::ccr::Initialize(); nn::temp::Initialize(); nn::aoc::Initialize(); nn::pdm::Initialize(); snd::user::Initialize(); H264::Initialize(); snd_core::Initialize(); mic::Initialize(); // init hardware register interfaces HW_SI::Initialize(); } void SetImplementation(SystemImplementation* impl) { s_implementation = impl; } void Shutdown() { cemu_assert_debug(s_initialized); // if a title is running, shut it down if (sSystemRunning) ShutdownTitle(); // shutdown persistent subsystems iosu::odm::Shutdown(); iosu::act::Stop(); iosu::mcp::Shutdown(); iosu::fsa::Shutdown(); s_initialized = false; } std::string GetInternalVirtualCodeFolder() { return "/internal/current_title/code/"; } void MountBaseDirectories() { const auto mlc = ActiveSettings::GetMlcPath(); FSCDeviceHostFS_Mount("/cemuBossStorage/", _pathToUtf8(mlc / "usr/boss/"), FSC_PRIORITY_BASE); FSCDeviceHostFS_Mount("/vol/storage_mlc01/", _pathToUtf8(mlc / ""), FSC_PRIORITY_BASE); } void UnmountBaseDirectories() { fsc_unmount("/vol/storage_mlc01/", FSC_PRIORITY_BASE); fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE); } STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId) { cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId); sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId); if (!sGameInfo_ForegroundTitle.IsValid()) { cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)"); return STATUS_CODE::UNABLE_TO_MOUNT; } // check base TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase(); if (!titleBase.IsValid()) return STATUS_CODE::UNABLE_TO_MOUNT; if(!titleBase.ParseXmlInfo()) return STATUS_CODE::UNABLE_TO_MOUNT; cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath()); // mount base if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE)) { cemuLog_log(LogType::Force, "Mounting failed"); return STATUS_CODE::UNABLE_TO_MOUNT; } // check update TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate(); if (titleUpdate.IsValid()) { if (!titleUpdate.ParseXmlInfo()) return STATUS_CODE::UNABLE_TO_MOUNT; cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath()); // mount update if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH)) { cemuLog_log(LogType::Force, "Mounting failed"); return STATUS_CODE::UNABLE_TO_MOUNT; } } else cemuLog_log(LogType::Force, "Update: Not present"); // check AOC auto aocList = sGameInfo_ForegroundTitle.GetAOC(); if (!aocList.empty()) { // todo - support for multi-title AOC TitleInfo& titleAOC = aocList[0]; if (!titleAOC.ParseXmlInfo()) return STATUS_CODE::UNABLE_TO_MOUNT; cemu_assert_debug(titleAOC.IsValid()); cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath()); // mount AOC if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH)) { cemuLog_log(LogType::Force, "Mounting failed"); return STATUS_CODE::UNABLE_TO_MOUNT; } } else cemuLog_log(LogType::Force, "DLC: Not present"); sForegroundTitleId = titleId; return STATUS_CODE::SUCCESS; } void UnmountForegroundTitle() { if(sLaunchModeIsStandalone) return; cemu_assert_debug(sGameInfo_ForegroundTitle.IsValid()); // unmounting title which was never mounted? if (!sGameInfo_ForegroundTitle.IsValid()) return; sGameInfo_ForegroundTitle.GetBase().Unmount("/vol/content"); sGameInfo_ForegroundTitle.GetBase().Unmount(GetInternalVirtualCodeFolder()); if (sGameInfo_ForegroundTitle.HasUpdate()) { if(auto& update = sGameInfo_ForegroundTitle.GetUpdate(); update.IsValid()) { update.Unmount("/vol/content"); update.Unmount(GetInternalVirtualCodeFolder()); } } auto aocList = sGameInfo_ForegroundTitle.GetAOC(); if (!aocList.empty()) { TitleInfo& titleAOC = aocList[0]; titleAOC.Unmount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId())); } } STATUS_CODE SetupExecutable() { // set rpx path from cos.xml if available _pathToBaseExecutable = _pathToExecutable; if (!sLaunchModeIsStandalone) { std::string _argstr = CafeSystem::GetForegroundTitleArgStr(); const char* argstr = _argstr.c_str(); if (argstr && *argstr != '\0') { const std::string tmp = argstr; const auto index = tmp.find(".rpx"); if (index != std::string::npos) { fs::path rpx = _pathToExecutable; rpx.replace_filename(tmp.substr(0, index + 4)); // cut off after .rpx std::string rpxPath; rpxPath = "/internal/current_title/code/"; rpxPath.append(rpx.generic_string()); int status; const auto file = fsc_open(rpxPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status); if (file) { _pathToExecutable = std::move(rpxPath); fsc_close(file); } } } } LoadMainExecutable(); gameProfile_load(); return STATUS_CODE::SUCCESS; } void SetupMemorySpace() { memory_mapForCurrentTitle(); LoadSharedData(); } void DestroyMemorySpace() { memory_unmapForCurrentTitle(); } STATUS_CODE PrepareForegroundTitle(TitleId titleId) { CafeTitleList::WaitForMandatoryScan(); sLaunchModeIsStandalone = false; _pathToExecutable.clear(); TitleIdParser tip(titleId); if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC || tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) cemuLog_log(LogType::Force, "Launched titleId is not the base of a title"); // mount mlc storage MountBaseDirectories(); // mount title folders STATUS_CODE r = LoadAndMountForegroundTitle(titleId); if (r != STATUS_CODE::SUCCESS) return r; // setup memory space and PPC recompiler SetupMemorySpace(); PPCRecompiler_init(); r = SetupExecutable(); // load RPX if (r != STATUS_CODE::SUCCESS) return r; InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path) { sLaunchModeIsStandalone = true; cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files"); fs::path executablePath = path; std::string dirName = _pathToUtf8(executablePath.parent_path().filename()); if (boost::iequals(dirName, "code")) { // check for content folder fs::path contentPath = executablePath.parent_path().parent_path().append("content"); std::error_code ec; if (fs::is_directory(contentPath, ec)) { // mounting content folder bool r = FSCDeviceHostFS_Mount(std::string("/vol/content").c_str(), _pathToUtf8(contentPath), FSC_PRIORITY_BASE); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath)); return STATUS_CODE::UNABLE_TO_MOUNT; } } } // mount code folder to a virtual temporary path FSCDeviceHostFS_Mount(std::string("/internal/code/").c_str(), _pathToUtf8(executablePath.parent_path()), FSC_PRIORITY_BASE); std::string internalExecutablePath = "/internal/code/"; internalExecutablePath.append(_pathToUtf8(executablePath.filename())); _pathToExecutable = internalExecutablePath; // since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash auto execData = fsc_extractFile(_pathToExecutable.c_str()); if (!execData) return STATUS_CODE::INVALID_RPX; uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); // setup memory space and ppc recompiler SetupMemorySpace(); PPCRecompiler_init(); // load executable SetupExecutable(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } void _LaunchTitleThread() { // init cemu_initForGame(); // enter scheduler if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler) coreinit::OSSchedulerBegin(3); else coreinit::OSSchedulerBegin(1); iosu::pdm::StartTrackingTime(GetForegroundTitleId()); } void LaunchForegroundTitle() { PPCTimer_waitForInit(); // start system sSystemRunning = true; gui_notifyGameLoaded(); std::thread t(_LaunchTitleThread); t.detach(); } bool IsTitleRunning() { return sSystemRunning; } TitleId GetForegroundTitleId() { cemu_assert_debug(sForegroundTitleId != 0); return sForegroundTitleId; } uint16 GetForegroundTitleVersion() { if (sLaunchModeIsStandalone) return 0; return sGameInfo_ForegroundTitle.GetVersion(); } uint32 GetForegroundTitleSDKVersion() { if (sLaunchModeIsStandalone) return 999999; return sGameInfo_ForegroundTitle.GetSDKVersion(); } CafeConsoleRegion GetForegroundTitleRegion() { if (sLaunchModeIsStandalone) return CafeConsoleRegion::USA; return sGameInfo_ForegroundTitle.GetRegion(); } std::string GetForegroundTitleName() { if (sLaunchModeIsStandalone) return "Unknown Game"; std::string applicationName; applicationName = sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetShortName(GetConfig().console_language); if (applicationName.empty()) //Try to get the English Title applicationName = sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetShortName(CafeConsoleLanguage::EN); if (applicationName.empty()) //Unknown Game applicationName = "Unknown Game"; return applicationName; } uint32 GetForegroundTitleOlvAccesskey() { if (sLaunchModeIsStandalone) return -1; return sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetOlvAccesskey(); } std::string GetForegroundTitleArgStr() { if (sLaunchModeIsStandalone) return ""; auto& update = sGameInfo_ForegroundTitle.GetUpdate(); if (update.IsValid()) return update.GetArgStr(); return sGameInfo_ForegroundTitle.GetBase().GetArgStr(); } // when switching titles custom parameters can be passed, returns true if override args are used bool GetOverrideArgStr(std::vector& args) { args.clear(); if(!s_overrideArgs) return false; args = *s_overrideArgs; return true; } void SetOverrideArgs(std::span args) { s_overrideArgs = std::vector(args.begin(), args.end()); } void UnsetOverrideArgs() { s_overrideArgs = std::nullopt; } // pick platform region based on title region CafeConsoleRegion GetPlatformRegion() { CafeConsoleRegion titleRegion = GetForegroundTitleRegion(); CafeConsoleRegion platformRegion = CafeConsoleRegion::USA; if (HAS_FLAG(titleRegion, CafeConsoleRegion::JPN)) platformRegion = CafeConsoleRegion::JPN; else if (HAS_FLAG(titleRegion, CafeConsoleRegion::EUR)) platformRegion = CafeConsoleRegion::EUR; else if (HAS_FLAG(titleRegion, CafeConsoleRegion::USA)) platformRegion = CafeConsoleRegion::USA; return platformRegion; } void UnmountCurrentTitle() { UnmountForegroundTitle(); fsc_unmount("/internal/code/", FSC_PRIORITY_BASE); } void ShutdownTitle() { if(!sSystemRunning) return; coreinit::OSSchedulerEnd(); Latte_Stop(); // reset Cafe OS userspace modules snd_core::reset(); coreinit::OSAlarm_Shutdown(); GX2::_GX2DriverReset(); nn::save::ResetToDefaultState(); coreinit::__OSDeleteAllActivePPCThreads(); RPLLoader_ResetState(); // stop time tracking iosu::pdm::Stop(); // reset Cemu subsystems PPCRecompiler_Shutdown(); GraphicPack2::Reset(); UnmountCurrentTitle(); MlcStorageUnmountAllTitles(); UnmountBaseDirectories(); DestroyMemorySpace(); sSystemRunning = false; } /* Virtual mlc storage */ void InitVirtualMlcStorage() { // starting with Cemu 1.27.0 /vol/storage_mlc01/ is virtualized, meaning that it doesn't point to one singular host os folder anymore // instead it now uses a more complex solution to source titles with various formats (folder, wud, wua) from the game paths and host mlc path // todo - mount /vol/storage_mlc01/ with base priority to the host mlc? // since mounting titles is an expensive operation we have to avoid mounting all titles at once // only the current title gets mounted immediately, every other title should be mounted lazily on first access // always mount the currently running title if (sGameInfo_ForegroundTitle.GetBase().IsValid()) MlcStorageMountTitle(sGameInfo_ForegroundTitle.GetBase()); if (sGameInfo_ForegroundTitle.GetUpdate().IsValid()) MlcStorageMountTitle(sGameInfo_ForegroundTitle.GetUpdate()); for(auto& it : sGameInfo_ForegroundTitle.GetAOC()) MlcStorageMountTitle(it); // setup system for lazy-mounting of other known titles // todo - how to handle this? // when something iterates /vol/storage_mlc01/usr/title/ we can use a fake FS device mounted to /vol/storage_mlc01/usr/title and sys/title that simulates the title id folders // the same device would then have to mount titles when their folders are actually accessed } // /vol/storage_mlc01//title// std::string GetMlcStoragePath(TitleId titleId) { TitleIdParser tip(titleId); return fmt::format("/vol/storage_mlc01/{}/title/{:08x}/{:08x}", tip.IsSystemTitle() ? "sys" : "usr", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } std::map m_mlcMountedTitles; // mount title to our virtual MLC storage // /vol/storage_mlc01//title// void MlcStorageMountTitle(TitleInfo& titleInfo) { if (!titleInfo.IsValid()) { cemu_assert_suspicious(); return; } TitleId titleId = titleInfo.GetAppTitleId(); if (m_mlcMountedTitles.find(titleId) != m_mlcMountedTitles.end()) return; std::string mlcStoragePath = GetMlcStoragePath(titleId); TitleInfo* mountTitleInfo = new TitleInfo(titleInfo); if (!mountTitleInfo->Mount(mlcStoragePath, "", FSC_PRIORITY_BASE)) { cemuLog_log(LogType::Force, "Failed to mount title to virtual storage"); delete mountTitleInfo; return; } m_mlcMountedTitles.emplace(titleId, mountTitleInfo); } void MlcStorageMountTitle(TitleId titleId) { TitleInfo titleInfo; if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo)) return; MlcStorageMountTitle(titleInfo); } void MlcStorageMountAllTitles() { std::vector titleIds = CafeTitleList::GetAllTitleIds(); for (auto& it : titleIds) MlcStorageMountTitle(it); } void MlcStorageUnmountAllTitles() { for(auto& it : m_mlcMountedTitles) { std::string mlcStoragePath = GetMlcStoragePath(it.first); it.second->Unmount(mlcStoragePath); } m_mlcMountedTitles.clear(); } uint32 GetRPXHashBase() { return currentBaseApplicationHash; } uint32 GetRPXHashUpdated() { return currentUpdatedApplicationHash; } void RequestRecreateCanvas() { s_implementation->CafeRecreateCanvas(); } }