mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-09 08:21:18 +12:00
Interpreter mode (as opposed to the regular speedy recompiler mode) is expected to be primarily used for debugging, and thus should be used with one core. However, unlike the game profile option, the `--force-interpreter` launch parameter didn't limit the amount of cores used to 1. This commit fixes that issue, alongside an additional temporary fix that helps situations where a debugger trap instruction gets hit by two threads simultaneously. Eventually this should be replaced with a more advanced approach that'd freeze other threads while stepping.
1109 lines
36 KiB
C++
1109 lines
36 KiB
C++
#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 "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/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"
|
|
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.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"
|
|
|
|
// dependency to be removed
|
|
#include "gui/guiWrapper.h"
|
|
|
|
#include <time.h>
|
|
|
|
#if BOOST_OS_LINUX
|
|
#include <sys/sysinfo.h>
|
|
#elif BOOST_OS_MACOS
|
|
#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());
|
|
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: {}", 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()
|
|
{
|
|
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.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 -------");
|
|
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<std::vector<std::string>> 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<uint64_t>(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);
|
|
}
|
|
|
|
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(),
|
|
};
|
|
|
|
// 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 modules
|
|
for(auto& module : s_iosuModules)
|
|
module->SystemLaunch();
|
|
// init IOSU (deprecated manual init)
|
|
iosuCrypto_init();
|
|
iosu::fsa::Initialize();
|
|
iosuIoctl_init();
|
|
iosuAct_init_depr();
|
|
iosu::act::Initialize();
|
|
iosu::iosuMcp_init();
|
|
iosu::mcp::Init();
|
|
iosu::iosuAcp_init();
|
|
iosu::boss_init();
|
|
iosu::nim::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();
|
|
nfc::Initialize();
|
|
ntag::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 (deprecated manual shutdown)
|
|
iosu::odm::Shutdown();
|
|
iosu::act::Stop();
|
|
iosu::mcp::Shutdown();
|
|
iosu::fsa::Shutdown();
|
|
// shutdown IOSU modules
|
|
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
|
|
(*it)->SystemExit();
|
|
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);
|
|
}
|
|
|
|
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 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();
|
|
return PREPARE_STATUS_CODE::SUCCESS;
|
|
}
|
|
|
|
void SetupMemorySpace()
|
|
{
|
|
memory_mapForCurrentTitle();
|
|
LoadSharedData();
|
|
}
|
|
|
|
void DestroyMemorySpace()
|
|
{
|
|
memory_unmapForCurrentTitle();
|
|
}
|
|
|
|
PREPARE_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
|
|
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 = SetupExecutable(); // load RPX
|
|
if (r != PREPARE_STATUS_CODE::SUCCESS)
|
|
return r;
|
|
InitVirtualMlcStorage();
|
|
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
|
|
SetupExecutable();
|
|
InitVirtualMlcStorage();
|
|
return PREPARE_STATUS_CODE::SUCCESS;
|
|
}
|
|
|
|
void _LaunchTitleThread()
|
|
{
|
|
for(auto& module : s_iosuModules)
|
|
module->TitleStart();
|
|
cemu_initForGame();
|
|
// enter scheduler
|
|
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter())
|
|
coreinit::OSSchedulerBegin(3);
|
|
else
|
|
coreinit::OSSchedulerBegin(1);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group)
|
|
{
|
|
if (sLaunchModeIsStandalone)
|
|
return CosCapabilityBits::All;
|
|
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
|
|
if (update.IsValid())
|
|
{
|
|
ParsedCosXml* cosXml = update.GetCosInfo();
|
|
if (cosXml)
|
|
return cosXml->GetCapabilityBits(group);
|
|
}
|
|
auto& base = sGameInfo_ForegroundTitle.GetBase();
|
|
if(base.IsValid())
|
|
{
|
|
ParsedCosXml* cosXml = base.GetCosInfo();
|
|
if (cosXml)
|
|
return cosXml->GetCapabilityBits(group);
|
|
}
|
|
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);
|
|
}
|
|
|
|
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();
|
|
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
|
|
(*it)->TitleStop();
|
|
// 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/<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();
|
|
}
|
|
|
|
}
|