2022-08-22 22:21:23 +02:00
#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"
2022-11-03 00:24:34 +01:00
#include "audio/IAudioInputAPI.h"
2022-08-22 22:21:23 +02:00
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "config/ActiveSettings.h"
#include "Cafe/TitleList/GameInfo.h"
#include "util/helpers/SystemException.h"
2022-09-04 01:27:44 +02:00
#include "Cafe/GraphicPack/GraphicPack2.h"
2022-08-22 22:21:23 +02:00
#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"
2022-09-07 02:42:25 +02:00
#include "Common/FileStream.h"
2022-08-22 22:21:23 +02:00
#include "GamePatch.h"
#include <time.h>
2023-02-19 15:41:49 +01:00
#include "HW/Espresso/Debugger/GDBStub.h"
2022-08-22 22:21:23 +02:00
#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 );
2022-09-09 23:48:52 +02:00
cemuLog_force ( "Save path: {}{}" , _pathToUtf8 ( effectiveSavePath ), saveDirExists ? "" : " (not present)" );
2022-08-22 22:21:23 +02:00
// 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 ();
2023-03-13 03:30:40 +01:00
cemuLog_log ( LogType :: Force , "------- Active settings -------" );
2022-08-22 22:21:23 +02:00
// settings to log:
2023-03-13 03:30:40 +01:00
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" );
2022-08-22 22:21:23 +02:00
if ( ActiveSettings :: GetGraphicsAPI () == GraphicAPI :: kVulkan )
{
2023-03-13 03:30:40 +01:00
cemuLog_log ( LogType :: Force , "Async compile: {}" , GetConfig (). async_compile . GetValue () ? "true" : "false" );
2022-08-22 22:21:23 +02:00
if ( ! GetConfig (). vk_accurate_barriers . GetValue ())
2023-03-13 03:30:40 +01:00
cemuLog_log ( LogType :: Force , "Accurate barriers are disabled!" );
2022-08-22 22:21:23 +02:00
}
2023-03-13 03:30:40 +01:00
cemuLog_log ( LogType :: Force , "Console language: {}" , config . console_language );
2022-08-22 22:21:23 +02:00
}
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 );
2022-08-26 19:41:42 +02:00
uint32 loadSharedData ()
2022-08-22 22:21:23 +02:00
{
// 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 ));
2022-10-11 23:03:26 -07:00
bool existsInResources = fs :: exists ( ActiveSettings :: GetDataPath ( shareddataDef [ i ]. resourcePath ));
2022-08-22 22:21:23 +02:00
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 )
{
2022-10-11 23:03:26 -07:00
path = ActiveSettings :: GetDataPath ( shareddataDef [ i ]. resourcePath );
2022-08-22 22:21:23 +02:00
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 );
}
2023-01-29 12:53:31 +00:00
cemuLog_log ( LogType :: Force , "COS: System fonts found. Generated shareddata ({}KB)" , ( uint32 )( dataWritePtr - ( uint8 * ) shareddataTable ) / 1024 );
2022-08-22 22:21:23 +02:00
return memory_getVirtualOffsetFromPointer ( dataWritePtr );
}
// alternative method: load RAM dump
2022-10-11 23:03:26 -07:00
const auto path = ActiveSettings :: GetUserDataPath ( "shareddata.bin" );
2022-08-22 22:21:23 +02:00
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 );
2022-08-26 04:03:26 +02:00
#if BOOST_OS_WINDOWS
2022-08-22 22:21:23 +02:00
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 ();
}
2022-11-24 12:29:29 +01:00
LatteGPUState . alwaysDisplayDRC = ActiveSettings :: DisplayDRCEnabled ();
2022-08-22 22:21:23 +02:00
InfoLog_PrintActiveSettings ();
Latte_Start ();
// check for debugger entrypoint bp
2023-02-19 15:41:49 +01:00
if ( g_gdbstub )
{
g_gdbstub -> HandleEntryStop ( _entryPoint );
g_gdbstub -> Initialize ();
}
2022-08-22 22:21:23 +02:00
debugger_handleEntryBreakpoint ( _entryPoint );
// load graphic packs
forceLog_printf ( "------- Activate graphic packs -------" );
2022-09-04 01:27:44 +02:00
GraphicPack2 :: ActivateForCurrentTitle ();
2022-08-22 22:21:23 +02:00
// print audio log
IAudioAPI :: PrintLogging ();
2022-11-03 00:24:34 +01:00
IAudioInputAPI :: PrintLogging ();
2022-08-22 22:21:23 +02:00
// 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 )
{
2022-09-17 04:45:18 +03:00
_pathToExecutable = std :: move ( rpxPath );
2022-08-22 22:21:23 +02:00
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 ;
2022-09-09 23:48:52 +02:00
std :: string dirName = _pathToUtf8 ( executablePath . parent_path (). filename ());
2022-08-22 22:21:23 +02:00
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
2022-09-09 23:48:52 +02:00
bool r = FSCDeviceHostFS_Mount ( std :: string ( "/vol/content" ). c_str (), _pathToUtf8 ( contentPath ), FSC_PRIORITY_BASE );
2022-08-22 22:21:23 +02:00
if ( ! r )
{
2022-09-09 23:48:52 +02:00
cemuLog_log ( LogType :: Force , "Failed to mount {}" , _pathToUtf8 ( contentPath ));
2022-08-22 22:21:23 +02:00
return STATUS_CODE :: UNABLE_TO_MOUNT ;
}
}
}
// mount code folder to a virtual temporary path
2022-09-09 23:48:52 +02:00
FSCDeviceHostFS_Mount ( std :: string ( "/internal/code/" ). c_str (), _pathToUtf8 ( executablePath . parent_path ()), FSC_PRIORITY_BASE );
2022-08-22 22:21:23 +02:00
std :: string internalExecutablePath = "/internal/code/" ;
2022-09-09 23:48:52 +02:00
internalExecutablePath . append ( _pathToUtf8 ( executablePath . filename ()));
2022-08-22 22:21:23 +02:00
_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 )
2022-10-18 17:08:09 +02:00
return "Unknown Game" ;
2022-10-21 00:17:11 +02:00
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 ;
2022-08-22 22:21:23 +02:00
}
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 ();
2022-09-04 01:27:44 +02:00
GraphicPack2 :: Reset ();
2022-08-22 22:21:23 +02:00
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 ;
}
2022-11-24 12:29:29 +01:00
}