Files
parallel-gx2/src/Cafe/CafeSystem.cpp
T
WizzardSK 69049a592d Say which build wrote a log
Cemu names its build in the "Init" line, which a libretro core writes
before its log.txt exists, so a log sent in never said which commit made
it (sco8487). log.txt now names it under "Loaded title", and the
frontend's log in the first line from retro_init. The hash is the one
CMake takes from git at configure time, as it is for the standalone
build.

(cherry picked from commit e387d972eb8f4a2ae7a27ceb2d2ff992b556ffd8)
2026-09-26 07:56:34 +02:00

1376 lines
47 KiB
C++

#include "Cafe/OS/common/OSCommon.h"
#include "WindowSystem.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 "config/LaunchSettings.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/coreinit/coreinit_MEM.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_nim.h"
#include "Cafe/IOSU/PDM/iosu_pdm.h"
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
#include "Cafe/IOSU/nn/boss/boss_service.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/nfc/nfc.h"
#include "Cafe/OS/libs/ntag/ntag.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"
#include <time.h>
#if BOOST_OS_LINUX
#include <sys/sysinfo.h>
#elif defined(__APPLE__) || BOOST_OS_BSD
#include <sys/types.h>
#include <sys/sysctl.h>
#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(), true);
applicationRPX = RPLLoader_LoadFromMemory(rpxData, rpxSize, (char*)_pathToExecutable.c_str());
if (!applicationRPX)
{
WindowSystem::ShowErrorDialog(_tr("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);
}
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()
{
uint64 titleId = CafeSystem::GetForegroundTitleId();
cemuLog_log(LogType::Force, "------- Loaded title -------");
#ifdef RETRO_CORE
// The "Init" line that names the build is written before a libretro
// core's log.txt exists, so a log sent in never said which commit made it.
cemuLog_log(LogType::Force, "Build: {} (libretro)", BUILD_VERSION_WITH_NAME_STRING);
#endif
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!");
}
#ifdef ENABLE_METAL
else if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kMetal)
{
cemuLog_log(LogType::Force, "Async compile: {}", GetConfig().async_compile.GetValue() ? "true" : "false");
cemuLog_log(LogType::Force, "Force mesh shaders: {}", GetConfig().force_mesh_shaders.GetValue() ? "true" : "false");
cemuLog_log(LogType::Force, "Fast math: {}", g_current_game_profile->GetShaderFastMath() ? "true" : "false");
cemuLog_log(LogType::Force, "Buffer cache type: {}", g_current_game_profile->GetBufferCacheMode());
cemuLog_log(LogType::Force, "Position invariance: {}", g_current_game_profile->GetPositionInvariance());
if (!GetConfig().vk_accurate_barriers.GetValue())
cemuLog_log(LogType::Force, "Accurate barriers are disabled!");
}
#endif
cemuLog_log(LogType::Force, "Console language: {}", stdx::to_underlying(config.console_language.GetValue()));
}
struct SharedDataEntry
{
/* +0x00 */ uint32be name;
/* +0x04 */ uint32be fileType; // 2 = font
/* +0x08 */ uint32be kernelFilenamePtr;
/* +0x0C */ MEMPTR<void> 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()
{
WindowSystem::UpdateWindowTitles(false, true, 0.0);
cemuLog_createLogFile(false);
// input manager apply game profile
InputManager::instance().apply_game_profile();
// 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();
// coreinit is bootstrapped first and then the main game executable is loaded
RPLLoader_LoadCoreinit();
LoadMainExecutable();
// log info for launched title
InfoLog_TitleLoaded();
// 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.isDRCPrimary = 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 -------");
#ifdef RETRO_CORE
// In the core there is no graphic pack window to see what was found: a
// pack that is not there, switched off (default = 1 in its [Definition]
// switches it on) or for another title is otherwise silent.
{
const uint64 titleId = CafeSystem::GetForegroundTitleId();
size_t forTitle = 0;
for (auto& gp : GraphicPack2::GetGraphicPacks())
forTitle += gp->ContainsTitleId(titleId) ? 1 : 0;
cemuLog_log(LogType::Force, "Graphic packs found: {}, {} for this title", GraphicPack2::GetGraphicPacks().size(), forTitle);
for (auto& gp : GraphicPack2::GetGraphicPacks())
if (gp->ContainsTitleId(titleId))
cemuLog_log(LogType::Force, " {}: {}", gp->GetVirtualPath(), gp->IsEnabled() ? "on" : "off");
}
#endif
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));
// run coreinit rpl_entry
RPLLoader_CallCoreinitEntrypoint();
// 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<std::vector<std::string>> s_overrideArgs;
std::optional<sint32> s_foregroundReturnStatus;
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<uint64_t>(info.totalram) * info.mem_unit) / 1024LL / 1024LL));
#elif defined(__APPLE__)
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));
#elif BOOST_OS_BSD
int64_t totalRam;
size_t size = sizeof(totalRam);
int result = sysctlbyname("hw.physmem", &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];
char buildNumberStr[32];
char featureVersion[32];
HKEY hKey;
DWORD dwType = REG_SZ;
DWORD dwSize = sizeof(productName);
buildNumber = 0;
featureVersion[0] = '\0';
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");
dwType = REG_SZ;
dwSize = sizeof(buildNumberStr);
if (RegQueryValueExA(hKey, "CurrentBuildNumber", NULL, &dwType, (LPBYTE)buildNumberStr, &dwSize) == ERROR_SUCCESS)
buildNumber = (uint32)atoi(buildNumberStr);
dwType = REG_SZ;
dwSize = sizeof(featureVersion);
if (RegQueryValueExA(hKey, "DisplayVersion", NULL, &dwType, (LPBYTE)featureVersion, &dwSize) != ERROR_SUCCESS)
{
dwType = REG_SZ;
dwSize = sizeof(featureVersion);
if (RegQueryValueExA(hKey, "ReleaseId", NULL, &dwType, (LPBYTE)featureVersion, &dwSize) != ERROR_SUCCESS)
featureVersion[0] = '\0';
}
RegCloseKey(hKey);
}
std::string result(productName);
// ProductName still reads as "Windows 10" on Windows 11. Find and replace with "Windows 11" based on build number.
if (buildNumber >= 22000)
{
size_t pos = result.find("Windows 10");
if (pos != std::string::npos)
result.replace(pos, 10, "Windows 11");
}
if (featureVersion[0] != '\0')
result += fmt::format(" {}", featureVersion);
return result;
}
#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 defined(__APPLE__)
char productVersion[256]{};
size_t productVersionSize = sizeof(productVersion);
const int productVersionResult = sysctlbyname("kern.osproductversion", productVersion, &productVersionSize, nullptr, 0);
char buildVersion[256]{};
size_t buildVersionSize = sizeof(buildVersion);
const int buildVersionResult = sysctlbyname("kern.osversion", buildVersion, &buildVersionSize, nullptr, 0);
if (productVersionResult == 0 && buildVersionResult == 0)
buffer = fmt::format("macOS {} ({})", productVersion, buildVersion);
else if (productVersionResult == 0)
buffer = fmt::format("macOS {}", productVersion);
else
buffer = "macOS";
platform = buffer.c_str();
#elif BOOST_OS_BSD
#if defined(__FreeBSD__)
platform = "FreeBSD";
#elif defined(__OpenBSD__)
platform = "OpenBSD";
#elif defined(__NetBSD__)
platform = "NetBSD";
#else
platform = "Unknown BSD";
#endif
#endif
cemuLog_log(LogType::Force, "Platform: {}", platform);
}
static std::vector<IOSUModule*> s_iosuModules =
{
// entries in this list are ordered by initialization order. Shutdown in reverse order
iosu::kernel::GetModule(),
iosu::acp::GetModule(),
iosu::fpd::GetModule(),
iosu::pdm::GetModule(),
iosu::ccr_nfc::GetModule(),
iosu::boss::GetModule()
};
// 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
cemuLog_log(LogType::Force, "Init: rplSymbolStorage");
rplSymbolStorage_init();
// allocate memory for all SysAllocators
// must happen before COS module init, but also before iosu::kernel::Initialize()
cemuLog_log(LogType::Force, "Init: SysAllocators");
SysAllocatorContainer::GetInstance().Initialize();
// init IOSU modules
cemuLog_log(LogType::Force, "Init: IOSU modules");
for(auto& module : s_iosuModules)
module->SystemLaunch();
// init IOSU (deprecated manual init)
cemuLog_log(LogType::Force, "Init: iosuCrypto");
iosuCrypto_init();
cemuLog_log(LogType::Force, "Init: IOSU services");
iosu::fsa::Initialize();
iosuIoctl_init();
iosuAct_init_depr();
iosu::act::Initialize();
iosu::iosuMcp_init();
iosu::mcp::Init();
iosu::iosuAcp_init();
iosu::nim::Initialize();
iosu::odm::Initialize();
// init hardware register interfaces
cemuLog_log(LogType::Force, "Init: HW_SI");
HW_SI::Initialize();
// Everything allocated from the system area up to here belongs to the
// process - the SysAllocators above all, which hand out their addresses
// for as long as it lives. What a title allocates comes after this line
// and is given back when it stops.
coreinit_markSysAreaPersistent();
cemuLog_log(LogType::Force, "Init: done");
}
// Initialize() runs once for the life of the process, so anything it starts
// that can be stopped again has to have a way back. The deprecated IOSU
// workers below are stopped when a libretro core is deinitialised - which a
// frontend does when content is closed, not only when it is finished with
// the core - and the next title then has no one reading its act, mcp, acp
// or nim queues. Its first request into one of them suspends the emulated
// thread that made it and nothing ever resumes it: the title boots, reaches
// its first save call, and stops there for good.
//
// Each worker clears its own "initialized" flag on the way out, so the
// guards inside these four start a replacement rather than refusing.
void RestartDeprecatedIOSUServices()
{
iosuIoctl_clearShutdown();
iosuAct_init_depr();
iosu::iosuMcp_init();
iosu::iosuAcp_init();
iosu::nim::Initialize();
// The resource managers go back up too, in the order Initialize brings
// them up. Each unregisters its device path on the way out now, so the
// registration below is accepted rather than refused - which it was, and
// silently, leaving /dev/fsa claimed by a queue with no reader. That was
// the second step of the same hang: past act, into FSAddClientEx, and
// stopped there.
iosu::fsa::Initialize();
iosu::act::Initialize();
iosu::mcp::Init();
iosu::odm::Initialize();
// And the modules, which SystemLaunch starts and ShutdownIOSUModules
// stops. /dev/ccr_nfc is one of these.
for (auto& module : s_iosuModules)
module->SystemLaunch();
}
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 (deprecated manual shutdown)
iosu::odm::Shutdown();
iosu::act::Stop();
iosu::mcp::Shutdown();
iosu::fsa::Shutdown();
ShutdownIOSUModules();
s_initialized = false;
}
void ShutdownIOSUModules()
{
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
(*it)->SystemExit();
}
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);
}
void MountExtras()
{
for (const auto& [host_path, emulatedPath] : LaunchSettings::CosMounts())
{
FSCDeviceHostFS_Mount(boost::nowide::narrow(emulatedPath), _pathToUtf8(host_path), FSC_PRIORITY_BASE);
}
}
void UnmountExtras()
{
for (const auto& [_, emulatedPath] : LaunchSettings::CosMounts())
{
fsc_unmount(boost::nowide::narrow(emulatedPath), FSC_PRIORITY_BASE);
}
}
PREPARE_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 PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
}
// check base
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
if (!titleBase.IsValid())
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
if(!titleBase.ParseXmlInfo())
return PREPARE_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 PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
}
// check update
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
if (titleUpdate.IsValid())
{
if (!titleUpdate.ParseXmlInfo())
return PREPARE_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 PREPARE_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 PREPARE_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 PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
}
}
else
cemuLog_log(LogType::Force, "DLC: Not present");
sForegroundTitleId = titleId;
return PREPARE_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()));
}
}
PREPARE_STATUS_CODE PrepareExecutable()
{
// 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);
}
}
}
}
return PREPARE_STATUS_CODE::SUCCESS;
}
void SetupMemorySpace()
{
memory_mapForCurrentTitle();
LoadSharedData();
}
void DestroyMemorySpace()
{
memory_unmapForCurrentTitle();
}
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId)
{
CafeTitleList::WaitForMandatoryScan();
sLaunchModeIsStandalone = false;
s_foregroundReturnStatus = std::nullopt;
_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
PREPARE_STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
if (r != PREPARE_STATUS_CODE::SUCCESS)
return r;
gameProfile_load();
// setup memory space and PPC recompiler
SetupMemorySpace();
PPCRecompiler_init();
r = PrepareExecutable(); // load RPX
if (r != PREPARE_STATUS_CODE::SUCCESS)
return r;
InitVirtualMlcStorage();
MountExtras();
return PREPARE_STATUS_CODE::SUCCESS;
}
PREPARE_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 PREPARE_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 PREPARE_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
PrepareExecutable();
InitVirtualMlcStorage();
MountExtras();
return PREPARE_STATUS_CODE::SUCCESS;
}
void _LaunchTitleThread()
{
for(auto& module : s_iosuModules)
module->TitleStart();
cemu_initForGame();
// enter scheduler
if ((ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler || LaunchSettings::ForceMultiCoreInterpreter()) && !LaunchSettings::ForceInterpreter())
coreinit::OSSchedulerBegin(3);
else
coreinit::OSSchedulerBegin(1);
}
void LaunchForegroundTitle()
{
PPCTimer_waitForInit();
// start system
sSystemRunning = true;
WindowSystem::NotifyGameLoaded();
#ifdef ENABLE_LIBRETRO
// On this thread, not a detached one. What that thread does is start the
// IOSU modules, scan the title for patches and bring the scheduler up -
// all of it reading the memory space the title was just mounted into -
// and detaching it means the launch outlives the call that asked for it.
// A reset then tears that memory space down while the scan is still
// walking it: GamePatch_scan faulting on a page marked ---p, one
// millisecond after "ShutdownTitle: releasing memory".
//
// A core has somewhere to do this work: the frontend's own thread,
// inside the retro_run that asked for the title. Nothing of the launch
// outlives that call now, so a stop that comes after it has nothing
// left to race.
_LaunchTitleThread();
#else
std::thread t(_LaunchTitleThread);
t.detach();
#endif
}
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()
{
auto optional_arguments = LaunchSettings::CosArgstr();
if (optional_arguments.has_value())
return *optional_arguments;
if (sLaunchModeIsStandalone)
return "";
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
if (update.IsValid())
return update.GetArgStr();
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
}
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group)
{
if (sLaunchModeIsStandalone)
return CosCapabilityBits::All;
CosCapabilityBits resultMask = static_cast<CosCapabilityBits>(0);
for (const auto& pack : GraphicPack2::GetActiveGraphicPacks())
{
for (const auto& permissionOverrides : pack->GetPermissionOverrides())
{
if (permissionOverrides.first == group)
resultMask |= static_cast<CosCapabilityBits>(permissionOverrides.second);
}
}
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
if (update.IsValid())
{
ParsedCosXml* cosXml = update.GetCosInfo();
if (cosXml)
return cosXml->GetCapabilityBits(group) | resultMask;
}
auto& base = sGameInfo_ForegroundTitle.GetBase();
if(base.IsValid())
{
ParsedCosXml* cosXml = base.GetCosInfo();
if (cosXml)
return cosXml->GetCapabilityBits(group) | resultMask;
}
return CosCapabilityBits::All;
}
// when switching titles custom parameters can be passed, returns true if override args are used
bool GetOverrideArgStr(std::vector<std::string>& args)
{
args.clear();
if(!s_overrideArgs)
return false;
args = *s_overrideArgs;
return true;
}
void SetOverrideArgs(std::span<std::string> args)
{
s_overrideArgs = std::vector<std::string>(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);
}
// Where ShutdownTitle has got to. Set by it, read by anything that has to
// give up on it: "the title did not stop" says nothing about which step of
// a dozen was the one that would not come back.
static std::atomic<const char*> s_shutdownPhase{"not shutting down"};
const char* GetShutdownPhase()
{
return s_shutdownPhase.load(std::memory_order_acquire);
}
void ShutdownTitle()
{
if(!sSystemRunning)
{
// Which means nothing below runs: no scheduler stop, no Latte_Stop,
// no memory released. Harmless when the title really is down, and
// the first thing to look at when something outlived it.
cemuLog_log(LogType::Force, "ShutdownTitle: no title was running, nothing to stop");
return;
}
// Logged as well as stored. The store is for anyone who asks afterwards;
// the line is for a shutdown that never finishes, where the last one
// written is the step it is still in. Nothing bounds this any more, so
// this log is the only thing that can name a wedge.
auto phase = [](const char* name) {
s_shutdownPhase.store(name, std::memory_order_release);
cemuLog_log(LogType::Force, "ShutdownTitle: {}", name);
};
phase("stopping the scheduler");
coreinit::OSSchedulerEnd();
phase("stopping the GPU thread");
Latte_Stop();
// reset Cafe OS userspace modules
phase("resetting audio");
snd_core::reset();
phase("stopping alarms");
coreinit::OSAlarm_Shutdown();
phase("resetting GX2");
GX2::_GX2DriverReset();
phase("resetting save state");
nn::save::ResetToDefaultState();
phase("resetting the H264 decoder");
H264::ResetToDefaultState();
phase("deleting PPC threads");
coreinit::__OSDeleteAllActivePPCThreads();
phase("unloading modules");
RPLLoader_UnloadAll();
phase("stopping IOSU modules");
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
(*it)->TitleStop();
// reset Cemu subsystems
phase("stopping the recompiler");
PPCRecompiler_Shutdown();
phase("resetting graphic packs");
GraphicPack2::Reset();
phase("unmounting");
UnmountCurrentTitle();
UnmountExtras();
MlcStorageUnmountAllTitles();
UnmountBaseDirectories();
phase("releasing memory");
DestroyMemorySpace();
// The system area is a bump allocator with no free, and CEMU_AREA stays
// mapped across titles, so this has to be given back by hand. The
// modules that cache a pointer into it (the placeholder font, the swkbd
// state, the uds workspace, the GX2 shared area) have dropped theirs by
// now - RPLUnmapped and _GX2DriverReset both ran above.
coreinit_releaseSysAreaForTitle();
LaunchSettings::ClearCosArgstr();
sSystemRunning = false;
phase("done");
}
/* 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/<usr or sys>/title/<titleIdHigh>/<titleIdLow>
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<TitleId, TitleInfo*> m_mlcMountedTitles;
// mount title to our virtual MLC storage
// /vol/storage_mlc01/<usr or sys>/title/<titleIdHigh>/<titleIdLow>
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<uint64> 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();
}
void NotifyPPCProcessExit(sint32 status)
{
s_foregroundReturnStatus = status;
s_implementation->CafePPCProcessExit();
}
std::optional<sint32> GetForegroundTitleReturnStatus()
{
return s_foregroundReturnStatus;
}
void DumpCurrentRPX()
{
if (!IsTitleRunning() || _pathToExecutable.empty())
return;
const fs::path dumpDirectory = ActiveSettings::GetUserDataPath("dump/rpx/{:016x}", GetForegroundTitleId());
std::error_code ec;
fs::create_directories(dumpDirectory, ec);
if (ec)
{
cemuLog_log(LogType::Force, "Failed to create RPX dump directory \"{}\": {}", _pathToUtf8(dumpDirectory), ec.message());
return;
}
auto dumpFile = [&dumpDirectory](const std::string& sourcePath)
{
auto fileData = fsc_extractFile(sourcePath.c_str());
if (!fileData)
return;
FileStream* fs = FileStream::createFile2(dumpDirectory / _utf8ToPath(sourcePath).filename());
if (!fs)
return;
fs->writeData(fileData->data(), (sint32)fileData->size());
delete fs;
};
// dump RPX
dumpFile(_pathToExecutable);
// dump RPLs
const std::string codeDirectory = _pathToExecutable.substr(0, _pathToExecutable.find_last_of('/') + 1);
sint32 fscStatus;
FSCVirtualFile* dirIterator = fsc_openDirIterator(codeDirectory.c_str(), &fscStatus);
if (dirIterator)
{
FSCDirEntry dirEntry;
while (fsc_nextDir(dirIterator, &dirEntry))
{
if (dirEntry.isFile && boost::iends_with(dirEntry.path, ".rpl"))
dumpFile(codeDirectory + dirEntry.path);
}
fsc_close(dirIterator);
}
}
}