mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-12-02 05:54:18 +01:00
860 lines
28 KiB
C++
860 lines
28 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 "audio/IAudioAPI.h"
|
|
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
|
|
|
#include "config/ActiveSettings.h"
|
|
#include "Cafe/TitleList/GameInfo.h"
|
|
#include "util/helpers/SystemException.h"
|
|
#include "Cafe/GraphicPack/GraphicPack2.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 <time.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"
|
|
|
|
// Cafe OS initializer 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"
|
|
|
|
// HW interfaces
|
|
#include "Cafe/HW/SI/si.h"
|
|
|
|
// dependency to be removed
|
|
#include "gui/guiWrapper.h"
|
|
|
|
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())
|
|
{
|
|
forceLog_printf("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)
|
|
{
|
|
forceLog_printf("Failed to load \"%s\"", _pathToExecutable.c_str());
|
|
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 = rpl_loadFromMem(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_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();
|
|
forceLog_printf("------- Active settings -------");
|
|
|
|
// settings to log:
|
|
forceLog_printf("CPU-Mode: %s%s", fmt::format("{}", ActiveSettings::GetCPUMode()).c_str(), g_current_game_profile->GetCPUMode().has_value() ? " (gameprofile)" : "");
|
|
forceLog_printf("Load shared libraries: %s%s", ActiveSettings::LoadSharedLibrariesEnabled() ? "true" : "false", g_current_game_profile->ShouldLoadSharedLibraries().has_value() ? " (gameprofile)" : "");
|
|
forceLog_printf("Use precompiled shaders: %s%s", fmt::format("{}", ActiveSettings::GetPrecompiledShadersOption()).c_str(), g_current_game_profile->GetPrecompiledShadersState().has_value() ? " (gameprofile)" : "");
|
|
forceLog_printf("Full sync at GX2DrawDone: %s", ActiveSettings::WaitForGX2DrawDoneEnabled() ? "true" : "false");
|
|
if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kVulkan)
|
|
{
|
|
forceLog_printf("Async compile: %s", GetConfig().async_compile.GetValue() ? "true" : "false");
|
|
if(!GetConfig().vk_accurate_barriers.GetValue())
|
|
forceLog_printf("Accurate barriers are disabled!");
|
|
}
|
|
|
|
forceLog_printf("Console language: %s", fmt::format("{}", config.console_language).c_str());
|
|
}
|
|
|
|
void PPCCore_setupSPR(PPCInterpreter_t* hCPU, uint32 coreIndex)
|
|
{
|
|
hCPU->sprExtended.PVR = 0x70010001;
|
|
hCPU->spr.UPIR = coreIndex;
|
|
hCPU->sprExtended.msr |= MSR_FP; // enable floating point
|
|
}
|
|
|
|
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::GetPath(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::GetPath(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);
|
|
}
|
|
forceLog_printfW(L"COS: System fonts found. Generated shareddata (%dKB)", (uint32)(dataWritePtr - (uint8*)shareddataTable) / 1024);
|
|
return memory_getVirtualOffsetFromPointer(dataWritePtr);
|
|
}
|
|
// alternative method: load RAM dump
|
|
const auto path = ActiveSettings::GetPath("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;
|
|
forceLog_printf("RPL link time: %dms", 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();
|
|
}
|
|
InfoLog_PrintActiveSettings();
|
|
Latte_Start();
|
|
// check for debugger entrypoint bp
|
|
debugger_handleEntryBreakpoint(_entryPoint);
|
|
// load graphic packs
|
|
forceLog_printf("------- Activate graphic packs -------");
|
|
GraphicPack2::ActivateForCurrentTitle();
|
|
// print audio log
|
|
IAudioAPI::PrintLogging();
|
|
// everything initialized
|
|
forceLog_printf("------- 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();
|
|
// init ppc recompiler
|
|
PPCRecompiler_init();
|
|
}
|
|
|
|
void cemu_deinitForGame()
|
|
{
|
|
// reset audio
|
|
snd_core::AXOut_reset();
|
|
snd_core::reset();
|
|
// reset alarms
|
|
coreinit::OSAlarm_resetAll();
|
|
// delete all threads
|
|
PPCCore_deleteAllThreads();
|
|
// reset mount paths
|
|
fsc_unmountAll();
|
|
// reset RPL loader
|
|
RPLLoader_ResetState();
|
|
// reset GX2
|
|
GX2::_GX2DriverReset();
|
|
}
|
|
|
|
namespace CafeSystem
|
|
{
|
|
void InitVirtualMlcStorage();
|
|
void MlcStorageMountTitle(TitleInfo& titleInfo);
|
|
|
|
bool sLaunchModeIsStandalone = false;
|
|
|
|
bool sSystemRunning = false;
|
|
TitleId sForegroundTitleId = 0;
|
|
|
|
GameInfo2 sGameInfo_ForegroundTitle;
|
|
|
|
void Initialize()
|
|
{
|
|
static bool s_initialized = false;
|
|
if (s_initialized)
|
|
return;
|
|
s_initialized = true;
|
|
// allocate memory for all SysAllocators
|
|
// must happen before all COS modules, but also before iosu::kernel::Init()
|
|
SysAllocatorContainer::GetInstance().Initialize();
|
|
// init IOSU
|
|
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();
|
|
// 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();
|
|
}
|
|
|
|
std::string GetInternalVirtualCodeFolder()
|
|
{
|
|
return "/internal/current_title/code/";
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
STATUS_CODE SetupExecutable()
|
|
{
|
|
// mount mlc directories
|
|
fscDeviceHostFS_mapBaseDirectories_deprecated();
|
|
// 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;
|
|
}
|
|
|
|
STATUS_CODE PrepareForegroundTitle(TitleId titleId)
|
|
{
|
|
CafeTitleList::WaitForMandatoryScan();
|
|
sLaunchModeIsStandalone = false;
|
|
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 title folders
|
|
STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
|
|
if (r != STATUS_CODE::SUCCESS)
|
|
return r;
|
|
// map memory
|
|
memory_mapForCurrentTitle();
|
|
// load RPX
|
|
r = SetupExecutable();
|
|
if (r != STATUS_CODE::SUCCESS)
|
|
return r;
|
|
|
|
loadSharedData();
|
|
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);
|
|
// load executable
|
|
memory_mapForCurrentTitle();
|
|
SetupExecutable();
|
|
loadSharedData();
|
|
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();
|
|
}
|
|
|
|
CafeConsoleRegion GetForegroundTitleRegion()
|
|
{
|
|
if (sLaunchModeIsStandalone)
|
|
return CafeConsoleRegion::USA;
|
|
return sGameInfo_ForegroundTitle.GetRegion();
|
|
}
|
|
|
|
std::string GetForegroundTitleName()
|
|
{
|
|
if (sLaunchModeIsStandalone)
|
|
return "Missing meta data";
|
|
// todo - use language based on Cemu console language
|
|
return sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetShortName(CafeConsoleLanguage::EN);
|
|
}
|
|
|
|
std::string GetForegroundTitleArgStr()
|
|
{
|
|
if (sLaunchModeIsStandalone)
|
|
return "";
|
|
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
|
|
if (update.IsValid())
|
|
return update.GetArgStr();
|
|
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
|
|
if (titleBase.IsValid())
|
|
titleBase.UnmountAll();
|
|
if (sGameInfo_ForegroundTitle.HasUpdate())
|
|
{
|
|
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
|
|
if (titleUpdate.IsValid())
|
|
titleUpdate.UnmountAll();
|
|
}
|
|
if (sGameInfo_ForegroundTitle.HasAOC())
|
|
{
|
|
auto titleInfoList = sGameInfo_ForegroundTitle.GetAOC();
|
|
for(auto& it : titleInfoList)
|
|
{
|
|
if (it.IsValid())
|
|
it.UnmountAll();
|
|
}
|
|
}
|
|
fsc_unmount("/internal/code/", FSC_PRIORITY_BASE);
|
|
}
|
|
|
|
void ShutdownTitle()
|
|
{
|
|
if(!sSystemRunning)
|
|
return;
|
|
coreinit::OSSchedulerEnd();
|
|
Latte_Stop();
|
|
iosu::pdm::Stop();
|
|
iosu::act::Stop();
|
|
iosu::mcp::Shutdown();
|
|
iosu::fsa::Shutdown();
|
|
GraphicPack2::Reset();
|
|
UnmountCurrentTitle();
|
|
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())
|
|
{
|
|
cemu_assert_suspicious(); // already mounted
|
|
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);
|
|
}
|
|
|
|
uint32 GetRPXHashBase()
|
|
{
|
|
return currentBaseApplicationHash;
|
|
}
|
|
|
|
uint32 GetRPXHashUpdated()
|
|
{
|
|
return currentUpdatedApplicationHash;
|
|
}
|
|
|
|
} |