diff --git a/CMakeLists.txt b/CMakeLists.txt
index 26d676bc..34a28a06 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -89,6 +89,9 @@ if (WIN32)
option(ENABLE_XINPUT "Enables the usage of XInput" ON)
option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON)
add_compile_definitions(HAS_DIRECTINPUT)
+ set(ENABLE_WIIMOTE ON)
+elseif (UNIX)
+ option(ENABLE_HIDAPI "Build with HIDAPI" ON)
endif()
option(ENABLE_SDL "Enables the SDLController backend" ON)
@@ -114,7 +117,7 @@ find_package(ZLIB REQUIRED)
find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available
find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED)
find_package(glm REQUIRED)
-find_package(fmt 9.1.0 REQUIRED)
+find_package(fmt 9.1.0...<10 REQUIRED)
find_package(PNG REQUIRED)
# glslang versions older than 11.11.0 define targets without a namespace
@@ -155,6 +158,12 @@ if (ENABLE_DISCORD_RPC)
target_include_directories(discord-rpc INTERFACE ./dependencies/discord-rpc/include)
endif()
+if (ENABLE_HIDAPI)
+ find_package(hidapi REQUIRED)
+ set(ENABLE_WIIMOTE ON)
+ add_compile_definitions(HAS_HIDAPI)
+endif ()
+
if(UNIX AND NOT APPLE)
if(ENABLE_FERAL_GAMEMODE)
add_compile_definitions(ENABLE_FERAL_GAMEMODE)
diff --git a/dist/linux/info.cemu.Cemu.metainfo.xml b/dist/linux/info.cemu.Cemu.metainfo.xml
index f056555b..ef59427f 100644
--- a/dist/linux/info.cemu.Cemu.metainfo.xml
+++ b/dist/linux/info.cemu.Cemu.metainfo.xml
@@ -3,15 +3,15 @@
info.cemu.Cemu
Cemu
- Software to emulate Wii U games and applications on PC
- Software zum emulieren von Wii U Spielen und Anwendungen auf dem PC
- Application pour émuler des jeux et applications Wii U sur PC
- Applicatie om Wii U spellen en applicaties te emuleren op PC
- Πρόγραμμα προσομοίωσης παιχνιδιών και εφαρμογών Wii U στον υπολογιστή
- Software para emular juegos y aplicaciones de Wii U en PC
- Software para emular jogos e aplicativos de Wii U no PC
- Software per emulare giochi e applicazioni per Wii U su PC
- Ojelmisto Wii U -pelien ja -sovellusten emulointiin PC:llä
+ Nintendo Wii U Emulator
+ Nintendo Wii U Emulator
+ Émulateur Nintendo Wii U
+ Nintendo Wii U Emulator
+ Εξομοιωτής Nintendo Wii U
+ Emulador de Nintendo Wii U
+ Emulador Nintendo Wii U
+ Emulatore Nintendo Wii U
+ Nintendo Wii U Emulaattori
Cemu Project
info.cemu.Cemu.desktop
CC0-1.0
diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp
index 213cdd95..d022b604 100644
--- a/src/Cafe/Account/Account.cpp
+++ b/src/Cafe/Account/Account.cpp
@@ -1,12 +1,13 @@
#include "Account.h"
#include "util/helpers/helpers.h"
#include "util/helpers/SystemException.h"
+#include "util/helpers/StringHelpers.h"
#include "config/ActiveSettings.h"
#include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "Common/FileStream.h"
+#include
#include
-#include
std::vector Account::s_account_list;
@@ -460,15 +461,14 @@ OnlineValidator Account::ValidateOnlineFiles() const
void Account::ParseFile(class FileStream* file)
{
- std::vector buffer;
-
- std::string tmp;
- while (file->readLine(tmp))
- buffer.emplace_back(tmp);
- for (const auto& s : buffer)
+ std::vector buffer;
+ buffer.resize(file->GetSize());
+ if( file->readData(buffer.data(), buffer.size()) != buffer.size())
+ throw std::system_error(AccountErrc::ParseError);
+ for (const auto& s : StringHelpers::StringLineIterator(buffer))
{
std::string_view view = s;
- const auto find = view.find(L'=');
+ const auto find = view.find('=');
if (find == std::string_view::npos)
continue;
diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt
index ea3a728b..b7656789 100644
--- a/src/Cafe/CMakeLists.txt
+++ b/src/Cafe/CMakeLists.txt
@@ -240,6 +240,8 @@ add_library(CemuCafe
IOSU/nn/iosu_nn_service.h
IOSU/PDM/iosu_pdm.cpp
IOSU/PDM/iosu_pdm.h
+ IOSU/ODM/iosu_odm.cpp
+ IOSU/ODM/iosu_odm.h
OS/common/OSCommon.cpp
OS/common/OSCommon.h
OS/common/OSUtil.h
@@ -399,6 +401,8 @@ add_library(CemuCafe
OS/libs/nn_idbe/nn_idbe.h
OS/libs/nn_ndm/nn_ndm.cpp
OS/libs/nn_ndm/nn_ndm.h
+ OS/libs/nn_spm/nn_spm.cpp
+ OS/libs/nn_spm/nn_spm.h
OS/libs/nn_nfp/AmiiboCrypto.h
OS/libs/nn_nfp/nn_nfp.cpp
OS/libs/nn_nfp/nn_nfp.h
@@ -416,6 +420,10 @@ add_library(CemuCafe
OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h
OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp
OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h
+ OS/libs/nn_olv/nn_olv_PostTypes.cpp
+ OS/libs/nn_olv/nn_olv_PostTypes.h
+ OS/libs/nn_olv/nn_olv_OfflineDB.cpp
+ OS/libs/nn_olv/nn_olv_OfflineDB.h
OS/libs/nn_pdm/nn_pdm.cpp
OS/libs/nn_pdm/nn_pdm.h
OS/libs/nn_save/nn_save.cpp
@@ -464,8 +472,6 @@ add_library(CemuCafe
OS/RPL/rpl_structs.h
OS/RPL/rpl_symbol_storage.cpp
OS/RPL/rpl_symbol_storage.h
- TitleList/BaseInfo.cpp
- TitleList/BaseInfo.h
TitleList/GameInfo.h
TitleList/ParsedMetaXml.h
TitleList/SaveInfo.cpp
diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp
index b81b3681..76bce1b1 100644
--- a/src/Cafe/CafeSystem.cpp
+++ b/src/Cafe/CafeSystem.cpp
@@ -4,17 +4,16 @@
#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 "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 "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"
@@ -22,14 +21,9 @@
#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
#include "HW/Espresso/Debugger/GDBStub.h"
#include "Cafe/IOSU/legacy/iosu_ioctl.h"
@@ -45,8 +39,9 @@
// 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 functions
+// 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"
@@ -61,6 +56,7 @@
#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"
@@ -70,6 +66,15 @@
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
+#include
+
+#if BOOST_OS_LINUX
+#include
+#elif BOOST_OS_MACOS
+#include
+#include
+#endif
+
std::string _pathToExecutable;
std::string _pathToBaseExecutable;
@@ -286,7 +291,7 @@ struct
static_assert(sizeof(SharedDataEntry) == 0x1C);
-uint32 loadSharedData()
+uint32 LoadSharedData()
{
// check if font files are dumped
bool hasAllShareddataFiles = true;
@@ -423,49 +428,139 @@ void cemu_initForGame()
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);
+ void MlcStorageUnmountAllTitles();
- bool sLaunchModeIsStandalone = false;
+ static bool s_initialized = false;
+ static SystemImplementation* s_implementation{nullptr};
+ bool sLaunchModeIsStandalone = false;
+ std::optional> s_overrideArgs;
bool sSystemRunning = false;
TitleId sForegroundTitleId = 0;
GameInfo2 sGameInfo_ForegroundTitle;
+
+ static void _CheckForWine()
+ {
+ #if BOOST_OS_WINDOWS
+ const HMODULE hmodule = GetModuleHandleA("ntdll.dll");
+ if (!hmodule)
+ return;
+
+ const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version");
+ if (pwine_get_version)
+ {
+ cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version());
+ }
+ #endif
+ }
+
+ void logCPUAndMemoryInfo()
+ {
+ std::string cpuName = g_CPUFeatures.GetCPUName();
+ if (!cpuName.empty())
+ cemuLog_log(LogType::Force, "CPU: {}", cpuName);
+ #if BOOST_OS_WINDOWS
+ MEMORYSTATUSEX statex;
+ statex.dwLength = sizeof(statex);
+ GlobalMemoryStatusEx(&statex);
+ uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL);
+ cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB);
+ #elif BOOST_OS_LINUX
+ struct sysinfo info {};
+ sysinfo(&info);
+ cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL));
+ #elif BOOST_OS_MACOS
+ int64_t totalRam;
+ size_t size = sizeof(totalRam);
+ int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0);
+ if (result == 0)
+ cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL));
+ #endif
+ }
+
+ #if BOOST_OS_WINDOWS
+ std::string GetWindowsNamedVersion(uint32& buildNumber)
+ {
+ static char productName[256];
+ HKEY hKey;
+ DWORD dwType = REG_SZ;
+ DWORD dwSize = sizeof(productName);
+ if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
+ {
+ if (RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)productName, &dwSize) != ERROR_SUCCESS)
+ strcpy(productName, "Windows");
+ RegCloseKey(hKey);
+ }
+ OSVERSIONINFO osvi;
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&osvi);
+ buildNumber = osvi.dwBuildNumber;
+ return std::string(productName);
+ }
+ #endif
+
+ void logPlatformInfo()
+ {
+ std::string buffer;
+ const char* platform = NULL;
+ #if BOOST_OS_WINDOWS
+ uint32 buildNumber;
+ std::string windowsVersionName = GetWindowsNamedVersion(buildNumber);
+ buffer = fmt::format("{} (Build {})", windowsVersionName, buildNumber);
+ platform = buffer.c_str();
+ #elif BOOST_OS_LINUX
+ if (getenv ("APPIMAGE"))
+ platform = "Linux (AppImage)";
+ else if (getenv ("SNAP"))
+ platform = "Linux (Snap)";
+ else if (platform = getenv ("container"))
+ {
+ if (strcmp (platform, "flatpak") == 0)
+ platform = "Linux (Flatpak)";
+ }
+ else
+ platform = "Linux";
+ #elif BOOST_OS_MACOS
+ platform = "MacOS";
+ #endif
+ cemuLog_log(LogType::Force, "Platform: {}", platform);
+ }
+
+ // initialize all subsystems which are persistent and don't depend on a game running
void Initialize()
{
- static bool s_initialized = false;
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 all COS modules, but also before iosu::kernel::Init()
+ // must happen before COS module init, but also before iosu::kernel::Initialize()
SysAllocatorContainer::GetInstance().Initialize();
// init IOSU
+ iosuCrypto_init();
iosu::kernel::Initialize();
iosu::fsa::Initialize();
iosuIoctl_init();
@@ -478,6 +573,7 @@ namespace CafeSystem
iosu::boss_init();
iosu::nim::Initialize();
iosu::pdm::Initialize();
+ iosu::odm::Initialize();
// init Cafe OS
avm::Initialize();
drmapp::Initialize();
@@ -495,14 +591,46 @@ namespace CafeSystem
HW_SI::Initialize();
}
+ void SetImplementation(SystemImplementation* impl)
+ {
+ s_implementation = impl;
+ }
+
+ void Shutdown()
+ {
+ cemu_assert_debug(s_initialized);
+ // if a title is running, shut it down
+ if (sSystemRunning)
+ ShutdownTitle();
+ // shutdown persistent subsystems
+ iosu::odm::Shutdown();
+ iosu::act::Stop();
+ iosu::mcp::Shutdown();
+ iosu::fsa::Shutdown();
+ s_initialized = false;
+ }
+
std::string GetInternalVirtualCodeFolder()
{
return "/internal/current_title/code/";
}
+ void MountBaseDirectories()
+ {
+ const auto mlc = ActiveSettings::GetMlcPath();
+ FSCDeviceHostFS_Mount("/cemuBossStorage/", _pathToUtf8(mlc / "usr/boss/"), FSC_PRIORITY_BASE);
+ FSCDeviceHostFS_Mount("/vol/storage_mlc01/", _pathToUtf8(mlc / ""), FSC_PRIORITY_BASE);
+ }
+
+ void UnmountBaseDirectories()
+ {
+ fsc_unmount("/vol/storage_mlc01/", FSC_PRIORITY_BASE);
+ fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
+ }
+
STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
{
- cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
+ cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId);
if (!sGameInfo_ForegroundTitle.IsValid())
{
@@ -561,10 +689,33 @@ namespace CafeSystem
return STATUS_CODE::SUCCESS;
}
+ void UnmountForegroundTitle()
+ {
+ if(sLaunchModeIsStandalone)
+ return;
+ cemu_assert_debug(sGameInfo_ForegroundTitle.IsValid()); // unmounting title which was never mounted?
+ if (!sGameInfo_ForegroundTitle.IsValid())
+ return;
+ sGameInfo_ForegroundTitle.GetBase().Unmount("/vol/content");
+ sGameInfo_ForegroundTitle.GetBase().Unmount(GetInternalVirtualCodeFolder());
+ if (sGameInfo_ForegroundTitle.HasUpdate())
+ {
+ if(auto& update = sGameInfo_ForegroundTitle.GetUpdate(); update.IsValid())
+ {
+ update.Unmount("/vol/content");
+ update.Unmount(GetInternalVirtualCodeFolder());
+ }
+ }
+ auto aocList = sGameInfo_ForegroundTitle.GetAOC();
+ if (!aocList.empty())
+ {
+ TitleInfo& titleAOC = aocList[0];
+ titleAOC.Unmount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()));
+ }
+ }
+
STATUS_CODE SetupExecutable()
{
- // mount mlc directories
- fscDeviceHostFS_mapBaseDirectories_deprecated();
// set rpx path from cos.xml if available
_pathToBaseExecutable = _pathToExecutable;
if (!sLaunchModeIsStandalone)
@@ -599,26 +750,37 @@ namespace CafeSystem
return STATUS_CODE::SUCCESS;
}
+ void SetupMemorySpace()
+ {
+ memory_mapForCurrentTitle();
+ LoadSharedData();
+ }
+
+ void DestroyMemorySpace()
+ {
+ memory_unmapForCurrentTitle();
+ }
+
STATUS_CODE PrepareForegroundTitle(TitleId titleId)
{
CafeTitleList::WaitForMandatoryScan();
sLaunchModeIsStandalone = false;
+ _pathToExecutable.clear();
TitleIdParser tip(titleId);
if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC || tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE)
cemuLog_log(LogType::Force, "Launched titleId is not the base of a title");
-
- // mount title folders
+ // mount mlc storage
+ MountBaseDirectories();
+ // mount title folders
STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
if (r != STATUS_CODE::SUCCESS)
return r;
- // map memory
- memory_mapForCurrentTitle();
- // load RPX
- r = SetupExecutable();
+ // setup memory space and PPC recompiler
+ SetupMemorySpace();
+ PPCRecompiler_init();
+ r = SetupExecutable(); // load RPX
if (r != STATUS_CODE::SUCCESS)
return r;
-
- loadSharedData();
InitVirtualMlcStorage();
return STATUS_CODE::SUCCESS;
}
@@ -657,10 +819,11 @@ namespace CafeSystem
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();
+ // setup memory space and ppc recompiler
+ SetupMemorySpace();
+ PPCRecompiler_init();
+ // load executable
+ SetupExecutable();
InitVirtualMlcStorage();
return STATUS_CODE::SUCCESS;
}
@@ -705,6 +868,13 @@ namespace CafeSystem
return sGameInfo_ForegroundTitle.GetVersion();
}
+ uint32 GetForegroundTitleSDKVersion()
+ {
+ if (sLaunchModeIsStandalone)
+ return 999999;
+ return sGameInfo_ForegroundTitle.GetSDKVersion();
+ }
+
CafeConsoleRegion GetForegroundTitleRegion()
{
if (sLaunchModeIsStandalone)
@@ -742,6 +912,26 @@ namespace CafeSystem
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
}
+ // when switching titles custom parameters can be passed, returns true if override args are used
+ bool GetOverrideArgStr(std::vector& args)
+ {
+ args.clear();
+ if(!s_overrideArgs)
+ return false;
+ args = *s_overrideArgs;
+ return true;
+ }
+
+ void SetOverrideArgs(std::span args)
+ {
+ s_overrideArgs = std::vector(args.begin(), args.end());
+ }
+
+ void UnsetOverrideArgs()
+ {
+ s_overrideArgs = std::nullopt;
+ }
+
// pick platform region based on title region
CafeConsoleRegion GetPlatformRegion()
{
@@ -758,39 +948,32 @@ namespace CafeSystem
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);
+ UnmountForegroundTitle();
+ fsc_unmount("/internal/code/", FSC_PRIORITY_BASE);
}
void ShutdownTitle()
{
if(!sSystemRunning)
return;
- coreinit::OSSchedulerEnd();
- Latte_Stop();
+ coreinit::OSSchedulerEnd();
+ Latte_Stop();
+ // reset Cafe OS userspace modules
+ snd_core::reset();
+ coreinit::OSAlarm_Shutdown();
+ GX2::_GX2DriverReset();
+ nn::save::ResetToDefaultState();
+ coreinit::__OSDeleteAllActivePPCThreads();
+ RPLLoader_ResetState();
+ // stop time tracking
iosu::pdm::Stop();
- iosu::act::Stop();
- iosu::mcp::Shutdown();
- iosu::fsa::Shutdown();
- GraphicPack2::Reset();
- UnmountCurrentTitle();
+ // reset Cemu subsystems
+ PPCRecompiler_Shutdown();
+ GraphicPack2::Reset();
+ UnmountCurrentTitle();
+ MlcStorageUnmountAllTitles();
+ UnmountBaseDirectories();
+ DestroyMemorySpace();
sSystemRunning = false;
}
@@ -912,10 +1095,7 @@ namespace CafeSystem
}
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))
@@ -942,6 +1122,16 @@ namespace CafeSystem
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;
@@ -952,4 +1142,9 @@ namespace CafeSystem
return currentUpdatedApplicationHash;
}
+ void RequestRecreateCanvas()
+ {
+ s_implementation->CafeRecreateCanvas();
+ }
+
}
diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h
index db5f1dc4..d79ae23b 100644
--- a/src/Cafe/CafeSystem.h
+++ b/src/Cafe/CafeSystem.h
@@ -6,6 +6,12 @@
namespace CafeSystem
{
+ class SystemImplementation
+ {
+ public:
+ virtual void CafeRecreateCanvas() = 0;
+ };
+
enum class STATUS_CODE
{
SUCCESS,
@@ -15,13 +21,21 @@ namespace CafeSystem
};
void Initialize();
+ void SetImplementation(SystemImplementation* impl);
+ void Shutdown();
+
STATUS_CODE PrepareForegroundTitle(TitleId titleId);
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
void LaunchForegroundTitle();
bool IsTitleRunning();
+ bool GetOverrideArgStr(std::vector& args);
+ void SetOverrideArgs(std::span args);
+ void UnsetOverrideArgs();
+
TitleId GetForegroundTitleId();
uint16 GetForegroundTitleVersion();
+ uint32 GetForegroundTitleSDKVersion();
CafeConsoleRegion GetForegroundTitleRegion();
CafeConsoleRegion GetPlatformRegion();
std::string GetForegroundTitleName();
@@ -43,6 +57,8 @@ namespace CafeSystem
uint32 GetRPXHashBase();
uint32 GetRPXHashUpdated();
+
+ void RequestRecreateCanvas();
};
extern RPLModule* applicationRPX;
diff --git a/src/Cafe/Filesystem/fsc.cpp b/src/Cafe/Filesystem/fsc.cpp
index ec53673d..ed7cfb00 100644
--- a/src/Cafe/Filesystem/fsc.cpp
+++ b/src/Cafe/Filesystem/fsc.cpp
@@ -6,7 +6,7 @@ struct FSCMountPathNode
std::string path;
std::vector subnodes;
FSCMountPathNode* parent;
- // device target and path (if subnodes is empty)
+ // associated device target and path
fscDeviceC* device{ nullptr };
void* ctx{ nullptr };
std::string deviceTargetPath; // the destination base path for the device, utf8
@@ -17,6 +17,25 @@ struct FSCMountPathNode
{
}
+ void AssignDevice(fscDeviceC* device, void* ctx, std::string_view deviceBasePath)
+ {
+ this->device = device;
+ this->ctx = ctx;
+ this->deviceTargetPath = deviceBasePath;
+ }
+
+ void UnassignDevice()
+ {
+ this->device = nullptr;
+ this->ctx = nullptr;
+ this->deviceTargetPath.clear();
+ }
+
+ bool IsRootNode() const
+ {
+ return !parent;
+ }
+
~FSCMountPathNode()
{
for (auto& itr : subnodes)
@@ -141,9 +160,7 @@ sint32 fsc_mount(std::string_view mountPath, std::string_view targetPath, fscDev
fscLeave();
return FSC_STATUS_INVALID_PATH;
}
- node->device = fscDevice;
- node->ctx = ctx;
- node->deviceTargetPath = std::move(targetPathWithSlash);
+ node->AssignDevice(fscDevice, ctx, targetPathWithSlash);
fscLeave();
return FSC_STATUS_OK;
}
@@ -160,14 +177,13 @@ bool fsc_unmount(std::string_view mountPath, sint32 priority)
}
cemu_assert(mountPathNode->priority == priority);
cemu_assert(mountPathNode->device);
- // delete node
- while (mountPathNode && mountPathNode->parent)
+ // unassign device
+ mountPathNode->UnassignDevice();
+ // prune empty branch
+ while (mountPathNode && !mountPathNode->IsRootNode() && mountPathNode->subnodes.empty() && !mountPathNode->device)
{
FSCMountPathNode* parent = mountPathNode->parent;
- cemu_assert(!(!mountPathNode->subnodes.empty() && mountPathNode->device));
- if (!mountPathNode->subnodes.empty())
- break;
- parent->subnodes.erase(std::find(parent->subnodes.begin(), parent->subnodes.end(), mountPathNode));
+ std::erase(parent->subnodes, mountPathNode);
delete mountPathNode;
mountPathNode = parent;
}
diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h
index b9ef6f84..b3b66d04 100644
--- a/src/Cafe/Filesystem/fsc.h
+++ b/src/Cafe/Filesystem/fsc.h
@@ -213,7 +213,6 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
// hostFS device
-void fscDeviceHostFS_mapBaseDirectories_deprecated();
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);
// redirect device
diff --git a/src/Cafe/Filesystem/fscDeviceHostFS.cpp b/src/Cafe/Filesystem/fscDeviceHostFS.cpp
index 2e1954d9..7f66c99b 100644
--- a/src/Cafe/Filesystem/fscDeviceHostFS.cpp
+++ b/src/Cafe/Filesystem/fscDeviceHostFS.cpp
@@ -305,13 +305,6 @@ public:
}
};
-void fscDeviceHostFS_mapBaseDirectories_deprecated()
-{
- const auto mlc = ActiveSettings::GetMlcPath();
- fsc_mount("/cemuBossStorage/", _pathToUtf8(mlc / "usr/boss/"), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE);
- fsc_mount("/vol/storage_mlc01/", _pathToUtf8(mlc / ""), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE);
-}
-
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority)
{
return fsc_mount(mountPath, hostTargetPath, &fscDeviceHostFSC::instance(), nullptr, priority) == FSC_STATUS_OK;
diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp
index b6417080..0883c436 100644
--- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp
+++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp
@@ -1,5 +1,6 @@
#include "gui/guiWrapper.h"
#include "Debugger.h"
+#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cemu/PPCAssembler/ppcAssembler.h"
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
#include "Cemu/ExpressionParser/ExpressionParser.h"
@@ -74,7 +75,7 @@ uint32 debugger_getAddressOriginalOpcode(uint32 address)
auto bpItr = debugger_getFirstBP(address);
while (bpItr)
{
- if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_ONE_SHOT)
+ if (bpItr->isExecuteBP())
return bpItr->originalOpcodeValue;
bpItr = bpItr->next;
}
@@ -121,32 +122,23 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore)
}
}
-void debugger_createExecuteBreakpoint(uint32 address)
+void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
{
// check if breakpoint already exists
auto existingBP = debugger_getFirstBP(address);
- if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_NORMAL))
+ if (existingBP && debuggerBPChain_hasType(existingBP, bpType))
return; // breakpoint already exists
// get original opcode at address
uint32 originalOpcode = debugger_getAddressOriginalOpcode(address);
// init breakpoint object
- DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_NORMAL, true);
+ DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, bpType, true);
debuggerBPChain_add(address, bp);
debugger_updateExecutionBreakpoint(address);
}
-void debugger_createSingleShotExecuteBreakpoint(uint32 address)
+void debugger_createExecuteBreakpoint(uint32 address)
{
- // check if breakpoint already exists
- auto existingBP = debugger_getFirstBP(address);
- if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_ONE_SHOT))
- return; // breakpoint already exists
- // get original opcode at address
- uint32 originalOpcode = debugger_getAddressOriginalOpcode(address);
- // init breakpoint object
- DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_ONE_SHOT, true);
- debuggerBPChain_add(address, bp);
- debugger_updateExecutionBreakpoint(address);
+ debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}
namespace coreinit
@@ -218,7 +210,7 @@ void debugger_handleSingleStepException(uint64 dr6)
}
if (catchBP)
{
- debugger_createSingleShotExecuteBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4);
+ debugger_createCodeBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT);
}
}
@@ -250,7 +242,7 @@ void debugger_handleEntryBreakpoint(uint32 address)
if (!debuggerState.breakOnEntry)
return;
- debugger_createExecuteBreakpoint(address);
+ debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}
void debugger_deleteBreakpoint(DebuggerBreakpoint* bp)
@@ -298,10 +290,12 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
{
// delete existing breakpoint
debugger_deleteBreakpoint(existingBP);
- return;
}
- // create new
- debugger_createExecuteBreakpoint(address);
+ else
+ {
+ // create new breakpoint
+ debugger_createExecuteBreakpoint(address);
+ }
}
void debugger_forceBreak()
@@ -327,7 +321,7 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
{
if (bpItr == bp)
{
- if (bpItr->bpType == DEBUGGER_BP_T_NORMAL)
+ if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING)
{
bp->enabled = state;
debugger_updateExecutionBreakpoint(address);
@@ -486,7 +480,7 @@ bool debugger_stepOver(PPCInterpreter_t* hCPU)
return false;
}
// create one-shot breakpoint at next instruction
- debugger_createSingleShotExecuteBreakpoint(initialIP +4);
+ debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
// step over current instruction (to avoid breakpoint)
debugger_stepInto(hCPU);
debuggerWindow_moveIP();
@@ -506,8 +500,39 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU)
debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i];
}
+void DebugLogStackTrace(OSThread_t* thread, MPTR sp);
+
void debugger_enterTW(PPCInterpreter_t* hCPU)
{
+ // handle logging points
+ DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer);
+ bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT);
+ while (bp)
+ {
+ if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
+ {
+ std::wstring logName = !bp->comment.empty() ? L"Breakpoint '"+bp->comment+L"'" : fmt::format(L"Breakpoint at 0x{:08X} (no comment)", bp->address);
+ std::wstring logContext = fmt::format(L"Thread: {:08x} LR: 0x{:08x}", coreinitThread_getCurrentThreadMPTRDepr(hCPU), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? L" Stack Trace:" : L"");
+ cemuLog_log(LogType::Force, L"[Debugger] {} was executed! {}", logName, logContext);
+ if (cemuLog_advancedPPCLoggingEnabled())
+ DebugLogStackTrace(coreinitThread_getCurrentThreadDepr(hCPU), hCPU->gpr[1]);
+ break;
+ }
+ bp = bp->next;
+ }
+
+ // return early if it's only a non-pausing logging breakpoint to prevent a modified debugger state and GUI updates
+ if (!shouldBreak)
+ {
+ uint32 backupIP = debuggerState.debugSession.instructionPointer;
+ debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
+ debugger_stepInto(hCPU, false);
+ PPCInterpreterSlim_executeInstruction(hCPU);
+ debuggerState.debugSession.instructionPointer = backupIP;
+ return;
+ }
+
+ // handle breakpoints
debuggerState.debugSession.isTrapped = true;
debuggerState.debugSession.debuggedThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(hCPU);
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
@@ -579,6 +604,20 @@ void debugger_shouldBreak(PPCInterpreter_t* hCPU)
void debugger_addParserSymbols(class ExpressionParser& ep)
{
+ const auto module_count = RPLLoader_GetModuleCount();
+ const auto module_list = RPLLoader_GetModuleList();
+
+ std::vector module_tmp(module_count);
+ for (int i = 0; i < module_count; i++)
+ {
+ const auto module = module_list[i];
+ if (module)
+ {
+ module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
+ ep.AddConstant(module->moduleName2, module_tmp[i]);
+ }
+ }
+
for (sint32 i = 0; i < 32; i++)
ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]);
}
\ No newline at end of file
diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h
index 08cbd90a..717df28a 100644
--- a/src/Cafe/HW/Espresso/Debugger/Debugger.h
+++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h
@@ -7,6 +7,7 @@
#define DEBUGGER_BP_T_ONE_SHOT 1 // normal breakpoint, deletes itself after trigger (used for stepping)
#define DEBUGGER_BP_T_MEMORY_READ 2 // memory breakpoint
#define DEBUGGER_BP_T_MEMORY_WRITE 3 // memory breakpoint
+#define DEBUGGER_BP_T_LOGGING 4 // logging breakpoint, prints the breakpoint comment and stack trace whenever hit
#define DEBUGGER_BP_T_GDBSTUB 1 // breakpoint created by GDBStub
#define DEBUGGER_BP_T_DEBUGGER 2 // breakpoint created by Cemu's debugger
@@ -42,7 +43,7 @@ struct DebuggerBreakpoint
bool isExecuteBP() const
{
- return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_ONE_SHOT;
+ return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_LOGGING || bpType == DEBUGGER_BP_T_ONE_SHOT;
}
bool isMemBP() const
@@ -98,8 +99,9 @@ extern debuggerState_t debuggerState;
// new API
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
-void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
+void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
void debugger_createExecuteBreakpoint(uint32 address);
+void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
diff --git a/src/Cafe/HW/Espresso/PPCScheduler.cpp b/src/Cafe/HW/Espresso/PPCScheduler.cpp
index ab662150..2a3a4aaa 100644
--- a/src/Cafe/HW/Espresso/PPCScheduler.cpp
+++ b/src/Cafe/HW/Espresso/PPCScheduler.cpp
@@ -100,11 +100,6 @@ PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR)
return hCPU;
}
-void PPCCore_deleteAllThreads()
-{
- assert_dbg();
-}
-
void PPCCore_init()
{
}
diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h
index a9f2d3ee..2b30326b 100644
--- a/src/Cafe/HW/Espresso/PPCState.h
+++ b/src/Cafe/HW/Espresso/PPCState.h
@@ -236,7 +236,6 @@ HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex);
// HLE scheduler
-void PPCCore_deleteAllThreads();
void PPCInterpreter_relinquishTimeslice();
void PPCCore_boostQuantum(sint32 numCycles);
diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp
index 4fac51ad..6b830563 100644
--- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp
+++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp
@@ -289,11 +289,16 @@ void PPCRecompiler_recompileAtAddress(uint32 address)
bool r = PPCRecompiler_makeRecompiledFunctionActive(address, range, func, functionEntryPoints);
}
+std::thread s_threadRecompiler;
+std::atomic_bool s_recompilerThreadStopSignal{false};
+
void PPCRecompiler_thread()
{
SetThreadName("PPCRecompiler_thread");
while (true)
{
+ if(s_recompilerThreadStopSignal)
+ return;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// asynchronous recompilation:
// 1) take address from queue
@@ -326,7 +331,12 @@ void PPCRecompiler_thread()
#define PPC_REC_ALLOC_BLOCK_SIZE (4*1024*1024) // 4MB
-std::bitset<(MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE) / PPC_REC_ALLOC_BLOCK_SIZE> ppcRecompiler_reservedBlockMask;
+constexpr uint32 PPCRecompiler_GetNumAddressSpaceBlocks()
+{
+ return (MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE + PPC_REC_ALLOC_BLOCK_SIZE - 1) / PPC_REC_ALLOC_BLOCK_SIZE;
+}
+
+std::bitset ppcRecompiler_reservedBlockMask;
void PPCRecompiler_reserveLookupTableBlock(uint32 offset)
{
@@ -496,16 +506,9 @@ void PPCRecompiler_init()
MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true);
PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions();
- uint32 codeRegionEnd = RPLLoader_GetMaxCodeOffset();
- codeRegionEnd = (codeRegionEnd + PPC_REC_ALLOC_BLOCK_SIZE - 1) & ~(PPC_REC_ALLOC_BLOCK_SIZE - 1);
-
- uint32 codeRegionSize = codeRegionEnd - PPC_REC_CODE_AREA_START;
- cemuLog_logDebug(LogType::Force, "Allocating recompiler tables for range 0x{:08x}-0x{:08x}", PPC_REC_CODE_AREA_START, codeRegionEnd);
-
- for (uint32 i = 0; i < codeRegionSize; i += PPC_REC_ALLOC_BLOCK_SIZE)
- {
- PPCRecompiler_reserveLookupTableBlock(i);
- }
+ PPCRecompiler_allocateRange(0, 0x1000); // the first entry is used for fallback to interpreter
+ PPCRecompiler_allocateRange(mmuRange_TRAMPOLINE_AREA.getBase(), mmuRange_TRAMPOLINE_AREA.getSize());
+ PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize());
// init x64 recompiler instance data
ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[0] = 1ULL << 63ULL;
@@ -589,6 +592,33 @@ void PPCRecompiler_init()
ppcRecompilerEnabled = true;
// launch recompilation thread
- std::thread t_recompiler(PPCRecompiler_thread);
- t_recompiler.detach();
+ s_recompilerThreadStopSignal = false;
+ s_threadRecompiler = std::thread(PPCRecompiler_thread);
}
+
+void PPCRecompiler_Shutdown()
+{
+ // shut down recompiler thread
+ s_recompilerThreadStopSignal = true;
+ if(s_threadRecompiler.joinable())
+ s_threadRecompiler.join();
+ // clean up queues
+ while(!PPCRecompilerState.targetQueue.empty())
+ PPCRecompilerState.targetQueue.pop();
+ PPCRecompilerState.invalidationRanges.clear();
+ // clean range store
+ rangeStore_ppcRanges.clear();
+ // clean up memory
+ uint32 numBlocks = PPCRecompiler_GetNumAddressSpaceBlocks();
+ for(uint32 i=0; ippcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true);
+ MemMapper::FreeMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true);
+ // mark as unmapped
+ ppcRecompiler_reservedBlockMask[i] = false;
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h
index 4f89b985..2e40f19d 100644
--- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h
+++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h
@@ -373,6 +373,7 @@ extern PPCRecompilerInstanceData_t* ppcRecompilerInstanceData;
extern bool ppcRecompilerEnabled;
void PPCRecompiler_init();
+void PPCRecompiler_Shutdown();
void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size);
diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h
index 36268645..f13abca0 100644
--- a/src/Cafe/HW/Latte/Core/Latte.h
+++ b/src/Cafe/HW/Latte/Core/Latte.h
@@ -84,6 +84,10 @@ extern uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring bu
void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd);
// render target
+
+#define RENDER_TARGET_TV (1 << 0)
+#define RENDER_TARGET_DRC (1 << 2)
+
void LatteRenderTarget_updateScissorBox();
void LatteRenderTarget_trackUpdates();
@@ -173,4 +177,5 @@ void LatteRenderTarget_updateViewport();
// Latte emulation control
void Latte_Start();
void Latte_Stop();
-bool Latte_IsActive();
+bool Latte_GetStopSignal(); // returns true if stop was requested or if in stopped state
+void LatteThread_Exit();
\ No newline at end of file
diff --git a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp
index cf2d53cc..4b114ddf 100644
--- a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp
@@ -96,7 +96,7 @@ void LatteAsyncCommands_waitUntilAllProcessed()
void LatteAsyncCommands_checkAndExecute()
{
// quick check if queue is empty (requires no lock)
- if (!Latte_IsActive())
+ if (Latte_GetStopSignal())
LatteThread_Exit();
if (LatteAsyncCommandQueue.empty())
return;
diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp
index 1196376e..92c2d1b0 100644
--- a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp
@@ -257,6 +257,11 @@ public:
}
}
+ bool empty() const
+ {
+ return m_map.empty();
+ }
+
const std::map& getAll() const { return m_map; };
};
@@ -455,48 +460,6 @@ public:
}
if(m_invalidationRangeEnd <= m_invalidationRangeBegin)
m_hasInvalidation = false;
-
- //if (resRangeBegin <= m_invalidationRangeBegin)
- //{
- // // shrink/replace invalidation range from the bottom
- // uint32 uploadBegin = m_invalidationRangeBegin;//std::max(m_invalidationRangeBegin, resRangeBegin);
- // uint32 uploadEnd = std::min(resRangeEnd, m_invalidationRangeEnd);
- // cemu_assert_debug(uploadEnd >= uploadBegin);
- // if (uploadBegin != uploadEnd)
- // checkAndSyncModifications(uploadBegin, uploadEnd, true);
- // m_invalidationRangeBegin = uploadEnd;
- // cemu_assert_debug(m_invalidationRangeBegin <= m_invalidationRangeEnd);
- // if (m_invalidationRangeBegin >= m_invalidationRangeEnd)
- // m_hasInvalidation = false;
- //}
- //else if (resRangeEnd >= m_invalidationRangeEnd)
- //{
- // // shrink/replace invalidation range from the top
- // uint32 uploadBegin = std::max(m_invalidationRangeBegin, resRangeBegin);
- // uint32 uploadEnd = m_invalidationRangeEnd;// std::min(resRangeEnd, m_invalidationRangeEnd);
- // cemu_assert_debug(uploadEnd >= uploadBegin);
- // if (uploadBegin != uploadEnd)
- // checkAndSyncModifications(uploadBegin, uploadEnd, true);
- // m_invalidationRangeEnd = uploadBegin;
- // cemu_assert_debug(m_invalidationRangeBegin <= m_invalidationRangeEnd);
- // if (m_invalidationRangeBegin >= m_invalidationRangeEnd)
- // m_hasInvalidation = false;
- //}
- //else
- //{
- // // since we cant cut holes into the range upload it in it's entirety
- // cemu_assert_debug(m_invalidationRangeEnd <= m_rangeEnd);
- // cemu_assert_debug(m_invalidationRangeBegin >= m_rangeBegin);
- // cemu_assert_debug(m_invalidationRangeBegin < m_invalidationRangeEnd);
- // checkAndSyncModifications(m_invalidationRangeBegin, m_invalidationRangeEnd, true);
- // m_hasInvalidation = false;
- //}
-
-
-
- // todo - dont re-upload the whole range immediately
- // under ideal circumstances we would only upload the data range requested for the current draw call
- // but this is a hot path so we can't check
}
}
@@ -827,6 +790,21 @@ private:
static std::vector g_deallocateQueue;
public:
+ static void UnloadAll()
+ {
+ size_t i = 0;
+ while (i < s_allCacheNodes.size())
+ {
+ BufferCacheNode* node = s_allCacheNodes[i];
+ node->ReleaseCacheMemoryImmediately();
+ LatteBufferCache_removeSingleNodeFromTree(node);
+ delete node;
+ }
+ for(auto& it : s_allCacheNodes)
+ delete it;
+ s_allCacheNodes.clear();
+ g_deallocateQueue.clear();
+ }
static void ProcessDeallocations()
{
@@ -931,7 +909,6 @@ public:
};
std::vector BufferCacheNode::g_deallocateQueue;
-
IntervalTree2 g_gpuBufferCache;
void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node)
@@ -1009,10 +986,16 @@ void LatteBufferCache_processDeallocations()
void LatteBufferCache_init(size_t bufferSize)
{
+ cemu_assert_debug(g_gpuBufferCache.empty());
g_gpuBufferHeap.reset(new VHeap(nullptr, (uint32)bufferSize));
g_renderer->bufferCache_init((uint32)bufferSize);
}
+void LatteBufferCache_UnloadAll()
+{
+ BufferCacheNode::UnloadAll();
+}
+
void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum)
{
g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum);
diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.h b/src/Cafe/HW/Latte/Core/LatteBufferCache.h
index da285192..62ae3f1f 100644
--- a/src/Cafe/HW/Latte/Core/LatteBufferCache.h
+++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.h
@@ -1,6 +1,7 @@
#pragma once
void LatteBufferCache_init(size_t bufferSize);
+void LatteBufferCache_UnloadAll();
uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size);
void LatteBufferCache_copyStreamoutDataToCache(MPTR physAddress, uint32 size, uint32 streamoutBufferOffset);
diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp
index 671ba496..37ce8ff9 100644
--- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp
@@ -125,7 +125,7 @@ uint32 LatteCP_readU32Deprc()
readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr);
if (readDistance != 0)
break;
- if (!Latte_IsActive())
+ if (Latte_GetStopSignal())
LatteThread_Exit();
// still no command data available, do some other tasks
@@ -172,7 +172,7 @@ void LatteCP_waitForNWords(uint32 numWords)
if (readDistance >= waitDistance)
break;
- if (!Latte_IsActive())
+ if (Latte_GetStopSignal())
LatteThread_Exit();
// still no command data available, do some other tasks
diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp
index 4f9d8173..3a52f641 100644
--- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp
@@ -373,6 +373,7 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade
if ((colorBufferWidth < (sint32)scissorAccessWidth) ||
(colorBufferHeight < (sint32)scissorAccessHeight))
{
+ // log this?
colorBufferMask &= ~(1<IsPadWindowActive())
+ if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive())
LatteRenderTarget_copyToBackbuffer(texView, true);
- if ((renderTarget == 1 && !showDRC) || (renderTarget == 4 && showDRC))
+ if (((renderTarget & RENDER_TARGET_TV) && !showDRC) || ((renderTarget & RENDER_TARGET_DRC) && showDRC))
LatteRenderTarget_copyToBackbuffer(texView, false);
}
diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp
index 5508d92c..c0ad06a1 100644
--- a/src/Cafe/HW/Latte/Core/LatteShader.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp
@@ -144,7 +144,7 @@ LatteShaderPSInputTable* LatteSHRC_GetPSInputTable()
return &_activePSImportTable;
}
-bool LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader)
+void LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader)
{
bool removed = false;
auto& cache = LatteSHRC_GetCacheByType(shader->shaderType);
@@ -156,10 +156,14 @@ bool LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader)
}
else if (baseIt->second == shader)
{
- if (baseIt->second->next)
- cache.emplace(shader->baseHash, baseIt->second->next);
- else
- cache.erase(baseIt);
+ cemu_assert_debug(baseIt->second == shader);
+ cache.erase(baseIt);
+ if (shader->next)
+ {
+ cemu_assert_debug(shader->baseHash == shader->next->baseHash);
+ cache.emplace(shader->baseHash, shader->next);
+ }
+ shader->next = 0;
removed = true;
}
else
@@ -176,7 +180,7 @@ bool LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader)
}
}
}
- return removed;
+ cemu_assert(removed);
}
void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type)
@@ -1009,3 +1013,16 @@ void LatteSHRC_Init()
cemu_assert_debug(sGeometryShaders.empty());
cemu_assert_debug(sPixelShaders.empty());
}
+
+void LatteSHRC_UnloadAll()
+{
+ while(!sVertexShaders.empty())
+ LatteShader_free(sVertexShaders.begin()->second);
+ cemu_assert_debug(sVertexShaders.empty());
+ while(!sGeometryShaders.empty())
+ LatteShader_free(sGeometryShaders.begin()->second);
+ cemu_assert_debug(sGeometryShaders.empty());
+ while(!sPixelShaders.empty())
+ LatteShader_free(sPixelShaders.begin()->second);
+ cemu_assert_debug(sPixelShaders.empty());
+}
\ No newline at end of file
diff --git a/src/Cafe/HW/Latte/Core/LatteShader.h b/src/Cafe/HW/Latte/Core/LatteShader.h
index eb623d85..f8dc6d1a 100644
--- a/src/Cafe/HW/Latte/Core/LatteShader.h
+++ b/src/Cafe/HW/Latte/Core/LatteShader.h
@@ -3,6 +3,7 @@
#include "Cafe/HW/Latte/ISA/RegDefines.h"
void LatteSHRC_Init();
+void LatteSHRC_UnloadAll();
void LatteSHRC_ResetCachedShaderHash();
void LatteShaderSHRC_UpdateFetchShader();
@@ -117,11 +118,12 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad
void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen);
// shader cache file
-void LatteShaderCache_load();
+void LatteShaderCache_Load();
+void LatteShaderCache_Close();
void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader);
void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount);
void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader);
-// todo - sort this
+// todo - refactor this
sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType);
\ No newline at end of file
diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp
index f413c925..55bf4b8a 100644
--- a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp
@@ -53,7 +53,7 @@ struct
sint32 pipelineFileCount;
}g_shaderCacheLoaderState;
-FileCache* fc_shaderCacheGeneric = nullptr; // contains hardware and Cemu version independent shader information
+FileCache* s_shaderCacheGeneric = nullptr; // contains hardware and version independent shader information
#define SHADER_CACHE_GENERIC_EXTRA_VERSION 2 // changing this constant will invalidate all hardware-independent cache files
@@ -62,7 +62,7 @@ FileCache* fc_shaderCacheGeneric = nullptr; // contains hardware and Cemu versio
#define SHADER_CACHE_TYPE_PIXEL (2)
bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderInfoSize);
-void LatteShaderCache_loadVulkanPipelineCache(uint64 cacheTitleId);
+void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId);
bool LatteShaderCache_updatePipelineLoadingProgress();
void LatteShaderCache_ShowProgress(const std::function & loadUpdateFunc, bool isPipelines);
@@ -216,7 +216,7 @@ void LatteShaderCache_drawBackgroundImage(ImTextureID texture, int width, int he
ImGui::PopStyleVar(2);
}
-void LatteShaderCache_load()
+void LatteShaderCache_Load()
{
shaderCacheScreenStats.compiledShaderCount = 0;
shaderCacheScreenStats.vertexShaderCount = 0;
@@ -251,21 +251,21 @@ void LatteShaderCache_load()
LatteShaderCache_handleDeprecatedCacheFiles(pathGeneric, pathGenericPre1_25_0, pathGenericPre1_16_0);
// calculate extraVersion for transferable and precompiled shader cache
uint32 transferableExtraVersion = SHADER_CACHE_GENERIC_EXTRA_VERSION;
- fc_shaderCacheGeneric = FileCache::Open(pathGeneric.generic_wstring(), false, transferableExtraVersion); // legacy extra version (1.25.0 - 1.25.1b)
- if(!fc_shaderCacheGeneric)
- fc_shaderCacheGeneric = FileCache::Open(pathGeneric.generic_wstring(), true, LatteShaderCache_getShaderCacheExtraVersion(cacheTitleId));
- if(!fc_shaderCacheGeneric)
+ s_shaderCacheGeneric = FileCache::Open(pathGeneric, false, transferableExtraVersion); // legacy extra version (1.25.0 - 1.25.1b)
+ if(!s_shaderCacheGeneric)
+ s_shaderCacheGeneric = FileCache::Open(pathGeneric, true, LatteShaderCache_getShaderCacheExtraVersion(cacheTitleId));
+ if(!s_shaderCacheGeneric)
{
// no shader cache available yet
cemuLog_log(LogType::Force, "Unable to open or create shader cache file \"{}\"", _pathToUtf8(pathGeneric));
LatteShaderCache_finish();
return;
}
- fc_shaderCacheGeneric->UseCompression(false);
+ s_shaderCacheGeneric->UseCompression(false);
// load/compile cached shaders
- sint32 entryCount = fc_shaderCacheGeneric->GetMaximumFileIndex();
- g_shaderCacheLoaderState.shaderFileCount = fc_shaderCacheGeneric->GetFileCount();
+ sint32 entryCount = s_shaderCacheGeneric->GetMaximumFileIndex();
+ g_shaderCacheLoaderState.shaderFileCount = s_shaderCacheGeneric->GetFileCount();
g_shaderCacheLoaderState.loadedShaderFiles = 0;
// get game background loading image
@@ -304,13 +304,13 @@ void LatteShaderCache_load()
auto LoadShadersUpdate = [&]() -> bool
{
- if (loadIndex >= (uint32)fc_shaderCacheGeneric->GetMaximumFileIndex())
+ if (loadIndex >= (uint32)s_shaderCacheGeneric->GetMaximumFileIndex())
return false;
LatteShaderCache_updateCompileQueue(SHADER_CACHE_COMPILE_QUEUE_SIZE - 2);
uint64 name1;
uint64 name2;
std::vector fileData;
- if (!fc_shaderCacheGeneric->GetFileByIndex(loadIndex, &name1, &name2, fileData))
+ if (!s_shaderCacheGeneric->GetFileByIndex(loadIndex, &name1, &name2, fileData))
{
loadIndex++;
return true;
@@ -320,7 +320,7 @@ void LatteShaderCache_load()
{
// something is wrong with the stored shader, remove entry from shader cache files
cemuLog_log(LogType::Force, "Shader cache entry {} invalid, deleting...", loadIndex);
- fc_shaderCacheGeneric->DeleteFile({ name1, name2 });
+ s_shaderCacheGeneric->DeleteFile({name1, name2 });
}
numLoadedShaders++;
loadIndex++;
@@ -343,7 +343,7 @@ void LatteShaderCache_load()
LatteShaderCache_finish();
// if Vulkan then also load pipeline cache
if (g_renderer->GetType() == RendererAPI::Vulkan)
- LatteShaderCache_loadVulkanPipelineCache(cacheTitleId);
+ LatteShaderCache_LoadVulkanPipelineCache(cacheTitleId);
g_renderer->BeginFrame(true);
@@ -376,6 +376,8 @@ void LatteShaderCache_ShowProgress(const std::function & loadUpdateF
while (true)
{
+ if (Latte_GetStopSignal())
+ break; // thread stop requested, cancel shader loading
bool r = loadUpdateFunc();
if (!r)
break;
@@ -496,13 +498,15 @@ void LatteShaderCache_ShowProgress(const std::function & loadUpdateF
}
}
-void LatteShaderCache_loadVulkanPipelineCache(uint64 cacheTitleId)
+void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId)
{
auto& pipelineCache = VulkanPipelineStableCache::GetInstance();
g_shaderCacheLoaderState.pipelineFileCount = pipelineCache.BeginLoading(cacheTitleId);
g_shaderCacheLoaderState.loadedPipelines = 0;
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
pipelineCache.EndLoading();
+ if(Latte_GetStopSignal())
+ LatteThread_Exit();
}
bool LatteShaderCache_updatePipelineLoadingProgress()
@@ -520,7 +524,7 @@ uint64 LatteShaderCache_getShaderNameInTransferableCache(uint64 baseHash, uint32
void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader)
{
- if (!fc_shaderCacheGeneric)
+ if (!s_shaderCacheGeneric)
return;
MemStreamWriter streamWriter(128 * 1024);
// header
@@ -539,12 +543,12 @@ void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 s
// write to cache
uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_VERTEX);
std::span dataBlob = streamWriter.getResult();
- fc_shaderCacheGeneric->AddFileAsync({ shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size());
+ s_shaderCacheGeneric->AddFileAsync({shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size());
}
void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount)
{
- if (!fc_shaderCacheGeneric)
+ if (!s_shaderCacheGeneric)
return;
MemStreamWriter streamWriter(128 * 1024);
// header
@@ -564,12 +568,12 @@ void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64
// write to cache
uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_GEOMETRY);
std::span dataBlob = streamWriter.getResult();
- fc_shaderCacheGeneric->AddFileAsync({ shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size());
+ s_shaderCacheGeneric->AddFileAsync({shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size());
}
void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader)
{
- if (!fc_shaderCacheGeneric)
+ if (!s_shaderCacheGeneric)
return;
MemStreamWriter streamWriter(128 * 1024);
streamWriter.writeBE(1 | (SHADER_CACHE_TYPE_PIXEL << 4)); // version and type (shared field)
@@ -585,7 +589,7 @@ void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 sh
// write to cache
uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_PIXEL);
std::span dataBlob = streamWriter.getResult();
- fc_shaderCacheGeneric->AddFileAsync({ shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size());
+ s_shaderCacheGeneric->AddFileAsync({shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size());
}
void LatteShaderCache_loadOrCompileSeparableShader(LatteDecompilerShader* shader, uint64 shaderBaseHash, uint64 shaderAuxHash)
@@ -759,6 +763,23 @@ bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderIn
return false;
}
+void LatteShaderCache_Close()
+{
+ if(s_shaderCacheGeneric)
+ {
+ delete s_shaderCacheGeneric;
+ s_shaderCacheGeneric = nullptr;
+ }
+ if (g_renderer->GetType() == RendererAPI::Vulkan)
+ RendererShaderVk::ShaderCacheLoading_Close();
+ else if (g_renderer->GetType() == RendererAPI::OpenGL)
+ RendererShaderGL::ShaderCacheLoading_Close();
+
+ // if Vulkan then also close pipeline cache
+ if (g_renderer->GetType() == RendererAPI::Vulkan)
+ VulkanPipelineStableCache::GetInstance().Close();
+}
+
#include
void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path pathGenericPre1_25_0, fs::path pathGenericPre1_16_0)
diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp
index bb5344a1..897f769c 100644
--- a/src/Cafe/HW/Latte/Core/LatteThread.cpp
+++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp
@@ -183,9 +183,8 @@ int Latte_ThreadEntry()
// before doing anything with game specific shaders, we need to wait for graphic packs to finish loading
GraphicPack2::WaitUntilReady();
- // load/init shader cache file
- LatteShaderCache_load();
-
+ // load disk shader cache
+ LatteShaderCache_Load();
// init registers
Latte_LoadInitialRegisters();
// let CPU thread know the GPU is done initializing
@@ -196,7 +195,7 @@ int Latte_ThreadEntry()
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
LatteThread_HandleOSScreen();
- if (!Latte_IsActive())
+ if (Latte_GetStopSignal())
LatteThread_Exit();
}
gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer;
@@ -232,20 +231,24 @@ void Latte_Stop()
sLatteThread.join();
}
-bool Latte_IsActive()
+bool Latte_GetStopSignal()
{
- return sLatteThreadRunning;
+ return !sLatteThreadRunning;
}
void LatteThread_Exit()
{
if (g_renderer)
g_renderer->Shutdown();
+ // clean up vertex/uniform cache
+ LatteBufferCache_UnloadAll();
// clean up texture cache
LatteTC_UnloadAllTextures();
// clean up runtime shader cache
- // todo
- // destroy renderer but make sure that g_renderer remains valid until the destructor has finished
+ LatteSHRC_UnloadAll();
+ // close disk cache
+ LatteShaderCache_Close();
+ // destroy renderer but make sure that g_renderer remains valid until the destructor has finished
if (g_renderer)
{
Renderer* renderer = g_renderer.get();
diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp
index 4dfdc52b..5269be64 100644
--- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp
+++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp
@@ -363,9 +363,6 @@ void OpenGLRenderer::NotifyLatteCommandProcessorIdle()
glFlush();
}
-
-bool IsRunningInWine();
-
void OpenGLRenderer::GetVendorInformation()
{
// example vendor strings:
diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp
index 4de30064..5530b4ec 100644
--- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp
+++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp
@@ -13,7 +13,7 @@ bool s_isLoadingShaders{false};
bool RendererShaderGL::loadBinary()
{
- if (!g_programBinaryCache)
+ if (!s_programBinaryCache)
return false;
if (m_isGameShader == false || m_isGfxPackShader)
return false; // only non-custom
@@ -25,7 +25,7 @@ bool RendererShaderGL::loadBinary()
GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2);
sint32 fileSize = 0;
std::vector cacheFileData;
- if (!g_programBinaryCache->GetFile({ h1, h2 }, cacheFileData))
+ if (!s_programBinaryCache->GetFile({h1, h2 }, cacheFileData))
return false;
if (fileSize < sizeof(uint32))
{
@@ -51,7 +51,7 @@ bool RendererShaderGL::loadBinary()
void RendererShaderGL::storeBinary()
{
- if (!g_programBinaryCache)
+ if (!s_programBinaryCache)
return;
if (!glGetProgramBinary)
return;
@@ -72,7 +72,7 @@ void RendererShaderGL::storeBinary()
glGetProgramBinary(m_program, binaryLength, NULL, &binaryFormat, storedBinary.data()+sizeof(uint32));
*(uint32*)(storedBinary.data() + 0) = binaryFormat;
// store
- g_programBinaryCache->AddFileAsync({ h1, h2 }, storedBinary.data(), storedBinary.size());
+ s_programBinaryCache->AddFileAsync({h1, h2 }, storedBinary.data(), storedBinary.size());
}
}
@@ -247,12 +247,7 @@ void RendererShaderGL::SetUniform4iv(sint32 location, void* data, sint32 count)
void RendererShaderGL::ShaderCacheLoading_begin(uint64 cacheTitleId)
{
- if (g_programBinaryCache)
- {
- delete g_programBinaryCache;
- g_programBinaryCache = nullptr;
- }
-
+ cemu_assert_debug(!s_programBinaryCache); // should not be set, ShaderCacheLoading_Close() not called?
// determine if cache is enabled
bool usePrecompiled = false;
switch (ActiveSettings::GetPrecompiledShadersOption())
@@ -279,9 +274,8 @@ void RendererShaderGL::ShaderCacheLoading_begin(uint64 cacheTitleId)
{
const uint32 cacheMagic = GeneratePrecompiledCacheId();
const std::string cacheFilename = fmt::format("{:016x}_gl.bin", cacheTitleId);
- const std::wstring cachePath = ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename).generic_wstring();
- g_programBinaryCache = FileCache::Open(cachePath, true, cacheMagic);
- if (g_programBinaryCache == nullptr)
+ s_programBinaryCache = FileCache::Open(ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename), true, cacheMagic);
+ if (s_programBinaryCache == nullptr)
cemuLog_log(LogType::Force, "Unable to open OpenGL precompiled cache {}", cacheFilename);
}
s_isLoadingShaders = true;
@@ -292,4 +286,15 @@ void RendererShaderGL::ShaderCacheLoading_end()
s_isLoadingShaders = false;
}
-FileCache* RendererShaderGL::g_programBinaryCache{};
+void RendererShaderGL::ShaderCacheLoading_Close()
+{
+ if(s_programBinaryCache)
+ {
+ delete s_programBinaryCache;
+ s_programBinaryCache = nullptr;
+ }
+ g_compiled_shaders_total = 0;
+ g_compiled_shaders_async = 0;
+}
+
+FileCache* RendererShaderGL::s_programBinaryCache{};
diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h
index 1c0753dc..abc62358 100644
--- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h
+++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h
@@ -24,6 +24,7 @@ public:
static void ShaderCacheLoading_begin(uint64 cacheTitleId);
static void ShaderCacheLoading_end();
+ static void ShaderCacheLoading_Close();
private:
GLuint m_program;
@@ -37,6 +38,6 @@ private:
bool m_shader_attached{ false };
bool m_isCompiled{ false };
- static class FileCache* g_programBinaryCache;
+ static class FileCache* s_programBinaryCache;
};
diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.cpp b/src/Cafe/HW/Latte/Renderer/Renderer.cpp
index 63ba5a7d..4db88355 100644
--- a/src/Cafe/HW/Latte/Renderer/Renderer.cpp
+++ b/src/Cafe/HW/Latte/Renderer/Renderer.cpp
@@ -62,6 +62,7 @@ void Renderer::Shutdown()
// imgui
ImGui::DestroyContext(imguiTVContext);
ImGui::DestroyContext(imguiPadContext);
+ ImGui_ClearFonts();
delete imguiFontAtlas;
}
diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp
index 604fba0d..804d03cc 100644
--- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp
+++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp
@@ -451,3 +451,9 @@ void RendererShaderVk::ShaderCacheLoading_end()
// keep g_spirvCache open since we will write to it while the game is running
s_isLoadingShadersVk = false;
}
+
+void RendererShaderVk::ShaderCacheLoading_Close()
+{
+ delete s_spirvCache;
+ s_spirvCache = nullptr;
+}
\ No newline at end of file
diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h
index b1883c3d..561145f9 100644
--- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h
+++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h
@@ -22,7 +22,8 @@ class RendererShaderVk : public RendererShader
public:
static void ShaderCacheLoading_begin(uint64 cacheTitleId);
- static void ShaderCacheLoading_end();
+ static void ShaderCacheLoading_end();
+ static void ShaderCacheLoading_Close();
RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode);
virtual ~RendererShaderVk();
diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp
index 7586a1b7..74247b9a 100644
--- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp
+++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp
@@ -117,6 +117,15 @@ void VulkanPipelineStableCache::EndLoading()
// keep cache file open for writing of new pipelines
}
+void VulkanPipelineStableCache::Close()
+{
+ if(s_cache)
+ {
+ delete s_cache;
+ s_cache = nullptr;
+ }
+}
+
struct CachedPipeline
{
struct ShaderHash
diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h
index a18bb982..7cba2930 100644
--- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h
+++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h
@@ -41,6 +41,7 @@ public:
bool UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders);
void EndLoading();
void LoadPipelineFromCache(std::span fileData);
+ void Close(); // called on title exit
bool HasPipelineCached(uint64 baseHash, uint64 pipelineStateHash);
void AddCurrentStateToCache(uint64 baseHash, uint64 pipelineStateHash);
diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp
index 95386284..937e3266 100644
--- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp
+++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp
@@ -179,7 +179,6 @@ std::vector VulkanRenderer::GetDevices()
}
-bool IsRunningInWine();
void VulkanRenderer::DetermineVendor()
{
VkPhysicalDeviceProperties2 properties{};
@@ -350,7 +349,15 @@ VulkanRenderer::VulkanRenderer()
create_info.ppEnabledLayerNames = m_layerNames.data();
create_info.enabledLayerCount = m_layerNames.size();
- if ((err = vkCreateInstance(&create_info, nullptr, &m_instance)) != VK_SUCCESS)
+ err = vkCreateInstance(&create_info, nullptr, &m_instance);
+
+ if (err == VK_ERROR_LAYER_NOT_PRESENT) {
+ cemuLog_log(LogType::Force, "Failed to enable vulkan validation (VK_LAYER_KHRONOS_validation)");
+ create_info.enabledLayerCount = 0;
+ err = vkCreateInstance(&create_info, nullptr, &m_instance);
+ }
+
+ if (err != VK_SUCCESS)
throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err));
if (!InitializeInstanceVulkan(m_instance))
diff --git a/src/Cafe/HW/MMU/MMU.cpp b/src/Cafe/HW/MMU/MMU.cpp
index eec61b8b..443607e9 100644
--- a/src/Cafe/HW/MMU/MMU.cpp
+++ b/src/Cafe/HW/MMU/MMU.cpp
@@ -159,7 +159,7 @@ void MMURange::mapMem()
void MMURange::unmapMem()
{
- cemu_assert_debug(false);
+ MemMapper::FreeMemory(memory_base + baseAddress, size, true);
m_isMapped = false;
}
@@ -252,6 +252,15 @@ void memory_mapForCurrentTitle()
}
}
+void memory_unmapForCurrentTitle()
+{
+ for (auto& itr : g_mmuRanges)
+ {
+ if (itr->isMapped() && !itr->isMappedEarly())
+ itr->unmapMem();
+ }
+}
+
void memory_logModifiedMemoryRanges()
{
auto gfxPackMappings = GraphicPack2::GetActiveRAMMappings();
diff --git a/src/Cafe/HW/MMU/MMU.h b/src/Cafe/HW/MMU/MMU.h
index d03307a9..216b4109 100644
--- a/src/Cafe/HW/MMU/MMU.h
+++ b/src/Cafe/HW/MMU/MMU.h
@@ -4,6 +4,7 @@
void memory_init();
void memory_mapForCurrentTitle();
+void memory_unmapForCurrentTitle();
void memory_logModifiedMemoryRanges();
void memory_enableOverlayArena();
diff --git a/src/Cafe/IOSU/ODM/iosu_odm.cpp b/src/Cafe/IOSU/ODM/iosu_odm.cpp
new file mode 100644
index 00000000..3dc8e431
--- /dev/null
+++ b/src/Cafe/IOSU/ODM/iosu_odm.cpp
@@ -0,0 +1,155 @@
+#include "iosu_odm.h"
+#include "config/ActiveSettings.h"
+#include "Common/FileStream.h"
+#include "util/helpers/Semaphore.h"
+#include "../kernel/iosu_kernel.h"
+
+namespace iosu
+{
+ namespace odm
+ {
+ using namespace iosu::kernel;
+
+ std::string s_devicePath = "/dev/odm";
+ std::thread s_serviceThread;
+ std::atomic_bool s_requestStop{false};
+ std::atomic_bool s_isRunning{false};
+ std::atomic_bool s_threadInitialized{ false };
+
+ IOSMsgQueueId s_msgQueueId;
+ SysAllocator _s_msgBuffer;
+
+ enum class ODM_CMD_OPERATION_TYPE
+ {
+ CHECK_STATE = 4,
+ UKN_5 = 5,
+ };
+
+ enum class ODM_STATE
+ {
+ NONE = 0,
+ INITIAL = 1,
+ AUTHENTICATION = 2,
+ WAIT_FOR_DISC_READY = 3,
+ CAFE_DISC = 4,
+ RVL_DISC = 5,
+ CLEANING_DISC = 6,
+ INVALID_DISC = 8,
+ DIRTY_DISC = 9,
+ NO_DISC = 10,
+ INVALID_DRIVE = 11,
+ FATAL = 12,
+ HARD_FATAL = 13,
+ SHUTDOWN = 14,
+ };
+
+ void ODMHandleCommandIoctl(uint32 clientHandle, IPCCommandBody* cmd, ODM_CMD_OPERATION_TYPE operationId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut)
+ {
+ switch(operationId)
+ {
+ case ODM_CMD_OPERATION_TYPE::CHECK_STATE:
+ {
+ *(uint32be*)ptrOut = (uint32)ODM_STATE::NO_DISC;
+ break;
+ }
+ case ODM_CMD_OPERATION_TYPE::UKN_5:
+ {
+ // does this return anything?
+ break;
+ }
+ default:
+ {
+ cemuLog_log(LogType::Force, "ODMHandleCommandIoctl: Unknown operationId %d\n", (uint32)operationId);
+ break;
+ }
+ }
+
+ IOS_ResourceReply(cmd, IOS_ERROR_OK);
+ }
+
+ uint32 CreateClientHandle()
+ {
+ return 1; // we dont care about handles for now
+ }
+
+ void CloseClientHandle(uint32 handle)
+ {
+
+ }
+
+ void ODMServiceThread()
+ {
+ s_msgQueueId = IOS_CreateMessageQueue(_s_msgBuffer.GetPtr(), _s_msgBuffer.GetCount());
+ cemu_assert(!IOS_ResultIsError((IOS_ERROR)s_msgQueueId));
+ IOS_ERROR r = IOS_RegisterResourceManager(s_devicePath.c_str(), s_msgQueueId);
+ cemu_assert(!IOS_ResultIsError(r));
+ s_threadInitialized = true;
+ while (true)
+ {
+ IOSMessage msg;
+ IOS_ERROR r = IOS_ReceiveMessage(s_msgQueueId, &msg, 0);
+ cemu_assert(!IOS_ResultIsError(r));
+ if (msg == 0)
+ {
+ cemu_assert_debug(s_requestStop);
+ break;
+ }
+ IPCCommandBody* cmd = MEMPTR(msg).GetPtr();
+ uint32 clientHandle = (uint32)cmd->devHandle;
+ if (cmd->cmdId == IPCCommandId::IOS_OPEN)
+ {
+ IOS_ResourceReply(cmd, (IOS_ERROR)CreateClientHandle());
+ continue;
+ }
+ else if (cmd->cmdId == IPCCommandId::IOS_CLOSE)
+ {
+ CloseClientHandle((IOSDevHandle)(uint32)cmd->devHandle);
+ IOS_ResourceReply(cmd, IOS_ERROR_OK);
+ continue;
+ }
+ else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV)
+ {
+ uint32 requestId = cmd->args[0];
+ uint32 numIn = cmd->args[1];
+ uint32 numOut = cmd->args[2];
+ IPCIoctlVector* vec = MEMPTR{ cmd->args[3] }.GetPtr();
+ IPCIoctlVector* vecIn = vec + numIn;
+ IPCIoctlVector* vecOut = vec + 0;
+ cemuLog_log(LogType::Force, "{}: Received unsupported Ioctlv cmd", s_devicePath);
+ IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
+ continue;
+ }
+ else if (cmd->cmdId == IPCCommandId::IOS_IOCTL)
+ {
+ ODMHandleCommandIoctl(clientHandle, cmd, (ODM_CMD_OPERATION_TYPE)cmd->args[0].value(), MEMPTR(cmd->args[1]), cmd->args[2], MEMPTR(cmd->args[3]), cmd->args[4]);
+ }
+ else
+ {
+ cemuLog_log(LogType::Force, "{}: Unsupported cmdId", s_devicePath);
+ cemu_assert_unimplemented();
+ IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
+ }
+ }
+ s_threadInitialized = false;
+ }
+
+ void Initialize()
+ {
+ if (s_isRunning.exchange(true))
+ return;
+ s_threadInitialized = false;
+ s_requestStop = false;
+ s_serviceThread = std::thread(&ODMServiceThread);
+ while (!s_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+
+ void Shutdown()
+ {
+ if (!s_isRunning.exchange(false))
+ return;
+ s_requestStop = true;
+ IOS_SendMessage(s_msgQueueId, 0, 0);
+ s_serviceThread.join();
+ }
+ }
+}
diff --git a/src/Cafe/IOSU/ODM/iosu_odm.h b/src/Cafe/IOSU/ODM/iosu_odm.h
new file mode 100644
index 00000000..f5f721a1
--- /dev/null
+++ b/src/Cafe/IOSU/ODM/iosu_odm.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace iosu
+{
+ namespace odm
+ {
+ void Initialize();
+ void Shutdown();
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.cpp b/src/Cafe/IOSU/PDM/iosu_pdm.cpp
index 89be4de2..45b4a1d8 100644
--- a/src/Cafe/IOSU/PDM/iosu_pdm.cpp
+++ b/src/Cafe/IOSU/PDM/iosu_pdm.cpp
@@ -369,7 +369,8 @@ namespace iosu
{
sPDMRequestExitThread.store(true);
sPDMSem.increment();
- sPDMTimeTrackingThread.join();
+ if(sPDMTimeTrackingThread.joinable())
+ sPDMTimeTrackingThread.join();
}
};
diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp
index c830fc4f..919b7b0f 100644
--- a/src/Cafe/IOSU/legacy/iosu_act.cpp
+++ b/src/Cafe/IOSU/legacy/iosu_act.cpp
@@ -238,7 +238,7 @@ namespace iosu
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override
{
- cemuLog_log(LogType::Force, "Unsupported service call to /dec/act");
+ cemuLog_log(LogType::Force, "Unsupported service call to /dev/act");
cemu_assert_unimplemented();
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0);
}
@@ -623,10 +623,19 @@ int iosuAct_thread()
}
else if (actCemuRequest->requestCode == IOSU_ARC_PERSISTENTID)
{
- accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
- _cancelIfAccountDoesNotExist();
- actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId;
- actCemuRequest->setACTReturnCode(0);
+ if(actCemuRequest->accountSlot != 0)
+ {
+ accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
+ _cancelIfAccountDoesNotExist();
+ actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId;
+ actCemuRequest->setACTReturnCode(0);
+ }
+ else
+ {
+ // F1 Race Stars calls IsSlotOccupied and indirectly GetPersistentId on slot 0 which is not valid
+ actCemuRequest->resultU32.u32 = 0;
+ actCemuRequest->setACTReturnCode(0);
+ }
}
else if (actCemuRequest->requestCode == IOSU_ARC_COUNTRY)
{
diff --git a/src/Cafe/IOSU/legacy/iosu_mcp.cpp b/src/Cafe/IOSU/legacy/iosu_mcp.cpp
index 0b99496a..3108e1f2 100644
--- a/src/Cafe/IOSU/legacy/iosu_mcp.cpp
+++ b/src/Cafe/IOSU/legacy/iosu_mcp.cpp
@@ -53,8 +53,10 @@ namespace iosu
std::string titlePath = CafeSystem::GetMlcStoragePath(titleId);
strcpy(titleOut.appPath, titlePath.c_str());
+ strcpy((char*)titleOut.deviceName, "mlc");
+
titleOut.osVersion = 0; // todo
- titleOut.sdkVersion = 0;
+ titleOut.sdkVersion = it->GetAppSDKVersion();
}
numTitlesCopied++;
@@ -73,7 +75,8 @@ namespace iosu
sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount)
{
std::unique_lock _lock(sTitleInfoMutex);
- *titleCount = mcpBuildTitleList(titleList, *titleCount, [](const TitleInfo& titleInfo) -> bool { return true; });
+ uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo);
+ *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [](const TitleInfo& titleInfo) -> bool { return true; });
return 0;
}
@@ -86,7 +89,7 @@ namespace iosu
sint32 mcpGetTitleListByAppType(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint32 appType)
{
std::unique_lock _lock(sTitleInfoMutex);
- uint32 maxEntryCount = (uint32)*titleCount;
+ uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo);
*titleCount = mcpBuildTitleList(titleList, maxEntryCount, [appType](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppType() == appType; });
return 0;
}
@@ -94,7 +97,7 @@ namespace iosu
sint32 mcpGetTitleListByTitleId(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint64 titleId)
{
std::unique_lock _lock(sTitleInfoMutex);
- uint32 maxEntryCount = (uint32)*titleCount;
+ uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo);
*titleCount = mcpBuildTitleList(titleList, maxEntryCount, [titleId](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppTitleId() == titleId; });
return 0;
}
@@ -143,11 +146,11 @@ namespace iosu
return 0;
}
+ // deprecated
void iosuMcp_init()
{
if (iosuMcp.isInitialized)
return;
- // start the act thread
std::thread t(iosuMcp_thread);
t.detach();
iosuMcp.isInitialized = true;
diff --git a/src/Cafe/IOSU/legacy/iosu_mcp.h b/src/Cafe/IOSU/legacy/iosu_mcp.h
index bca301a4..64e75fdc 100644
--- a/src/Cafe/IOSU/legacy/iosu_mcp.h
+++ b/src/Cafe/IOSU/legacy/iosu_mcp.h
@@ -13,7 +13,8 @@ struct MCPTitleInfo
// everything below is uncertain
/* +0x4A */ uint64be osVersion; // app.xml
/* +0x52 */ uint32be sdkVersion; // app.xml
- /* +0x56 */ uint8 ukn[0x61 - 0x56];
+ /* +0x56 */ uint8 deviceName[10];
+ /* +0x60 */ uint8 uknPadding; // possibly the index of the device?
//move this and the stuff below
};
diff --git a/src/Cafe/IOSU/legacy/iosu_nim.cpp b/src/Cafe/IOSU/legacy/iosu_nim.cpp
index f90ec70b..e7cf97ef 100644
--- a/src/Cafe/IOSU/legacy/iosu_nim.cpp
+++ b/src/Cafe/IOSU/legacy/iosu_nim.cpp
@@ -8,11 +8,9 @@
#include "openssl/x509.h"
#include "openssl/ssl.h"
#include "util/helpers/helpers.h"
-
-#include
-
#include "Cemu/napi/napi.h"
#include "Cemu/ncrypto/ncrypto.h"
+#include "Cafe/CafeSystem.h"
namespace iosu
{
@@ -47,6 +45,13 @@ namespace iosu
bool backgroundThreadStarted;
} g_nim = {};
+ bool nim_CheckDownloadsDisabled()
+ {
+ // currently for the Wii U menu we disable NIM to speed up boot times
+ uint64 tid = CafeSystem::GetForegroundTitleId();
+ return tid == 0x0005001010040000 || tid == 0x0005001010040100 || tid == 0x0005001010040200;
+ }
+
bool nim_getLatestVersion()
{
g_nim.latestVersion = -1;
@@ -101,6 +106,13 @@ namespace iosu
void nim_buildDownloadList()
{
+ if(nim_CheckDownloadsDisabled())
+ {
+ cemuLog_logDebug(LogType::Force, "nim_buildDownloadList: Downloads are disabled for this title");
+ g_nim.packages.clear();
+ return;
+ }
+
sint32 titleCount = mcpGetTitleCount();
MCPTitleInfo* titleList = (MCPTitleInfo*)malloc(titleCount * sizeof(MCPTitleInfo));
memset(titleList, 0, titleCount * sizeof(MCPTitleInfo));
@@ -141,6 +153,8 @@ namespace iosu
void nim_getPackagesInfo(uint64* titleIdList, sint32 count, titlePackageInfo_t* packageInfoList)
{
memset(packageInfoList, 0, sizeof(titlePackageInfo_t)*count);
+ if(nim_CheckDownloadsDisabled())
+ return;
for (sint32 i = 0; i < count; i++)
{
uint64 titleId = _swapEndianU64(titleIdList[i]);
diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp
index b0aec8ae..c9683683 100644
--- a/src/Cafe/OS/RPL/rpl.cpp
+++ b/src/Cafe/OS/RPL/rpl.cpp
@@ -1429,6 +1429,7 @@ void RPLLoader_InitState()
rplLoaderHeap_codeArea2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODEAREA_ADDR));
rplLoaderHeap_workarea.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_RPLLOADER_AREA_ADDR));
g_heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2);
+ RPLLoader_ResetState();
}
void RPLLoader_ResetState()
@@ -1436,8 +1437,7 @@ void RPLLoader_ResetState()
// unload all RPL modules
while (rplModuleCount > 0)
RPLLoader_UnloadModule(rplModuleList[0]);
- // clear dependency list
- cemu_assert_debug(false);
+ rplDependencyList.clear();
// unload all remaining symbols
rplSymbolStorage_unloadAll();
// free all code imports
@@ -1448,8 +1448,6 @@ void RPLLoader_ResetState()
rplLoader_applicationHasMemoryControl = false;
rplLoader_maxCodeAddress = 0;
rpl3_currentDataAllocatorAddr = 0x10000000;
- cemu_assert_debug(rplDependencyList.empty());
- rplDependencyList.clear();
_currentTLSModuleIndex = 1;
rplLoader_sdataAddr = MPTR_NULL;
rplLoader_sdata2Addr = MPTR_NULL;
diff --git a/src/Cafe/OS/RPL/rpl_symbol_storage.cpp b/src/Cafe/OS/RPL/rpl_symbol_storage.cpp
index accb7d53..5d3046e3 100644
--- a/src/Cafe/OS/RPL/rpl_symbol_storage.cpp
+++ b/src/Cafe/OS/RPL/rpl_symbol_storage.cpp
@@ -159,6 +159,7 @@ void rplSymbolStorage_unloadAll()
// free strings
for (auto it : rplSymbolStorage.list_strAllocatedBlocks)
free(it);
+ rplSymbolStorage.list_strAllocatedBlocks.clear();
rplSymbolStorage.strAllocatorBlock = nullptr;
rplSymbolStorage.strAllocatorOffset = 0;
}
diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp
index b41ab865..7e11ea13 100644
--- a/src/Cafe/OS/common/OSCommon.cpp
+++ b/src/Cafe/OS/common/OSCommon.cpp
@@ -10,6 +10,7 @@
#include "Cafe/OS/libs/nn_uds/nn_uds.h"
#include "Cafe/OS/libs/nn_nim/nn_nim.h"
#include "Cafe/OS/libs/nn_ndm/nn_ndm.h"
+#include "Cafe/OS/libs/nn_spm/nn_spm.h"
#include "Cafe/OS/libs/nn_ec/nn_ec.h"
#include "Cafe/OS/libs/nn_boss/nn_boss.h"
#include "Cafe/OS/libs/nn_fp/nn_fp.h"
@@ -204,6 +205,7 @@ void osLib_load()
nnUds_load();
nn::nim::load();
nn::ndm::load();
+ nn::spm::load();
nn::save::load();
nsysnet_load();
nn::fp::load();
diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp
index 4253bae8..e8e4ce1f 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp
@@ -113,19 +113,6 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp)
}
}
-void coreinitExport_OSPanic(PPCInterpreter_t* hCPU)
-{
- cemuLog_log(LogType::Force, "OSPanic!\n");
- cemuLog_log(LogType::Force, "File: {}:{}\n", (const char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4]);
- cemuLog_log(LogType::Force, "Msg: {}\n", (const char*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]));
- DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer());
-#ifdef CEMU_DEBUG_ASSERT
- assert_dbg();
- while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100));
-#endif
- osLib_returnFromFunction(hCPU, 0);
-}
-
typedef struct
{
/* +0x00 */ uint32be name;
@@ -296,6 +283,17 @@ namespace coreinit
return 0;
}
+ void OSPanic(const char* file, sint32 lineNumber, const char* msg)
+ {
+ cemuLog_log(LogType::Force, "OSPanic!");
+ cemuLog_log(LogType::Force, "File: {}:{}", file, lineNumber);
+ cemuLog_log(LogType::Force, "Msg: {}", msg);
+ DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer());
+#ifdef CEMU_DEBUG_ASSERT
+ while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100));
+#endif
+ }
+
void InitializeCore()
{
cafeExportRegister("coreinit", OSGetCoreId, LogType::CoreinitThread);
@@ -313,6 +311,8 @@ namespace coreinit
cafeExportRegister("coreinit", OSIsOffBoot, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetBootPMFlags, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetSystemMode, LogType::CoreinitThread);
+
+ cafeExportRegister("coreinit", OSPanic, LogType::Placeholder);
}
};
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp
index afc13919..f7e58115 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp
@@ -117,6 +117,12 @@ namespace coreinit
return currentTick >= g_soonestAlarm;
}
+ static void Reset()
+ {
+ g_activeAlarmList.clear();
+ g_soonestAlarm = 0;
+ }
+
public:
struct ComparatorFireTime
{
@@ -282,11 +288,21 @@ namespace coreinit
return alarm->userData;
}
- void OSAlarm_resetAll()
+ void OSAlarm_Shutdown()
{
- cemu_assert_debug(g_activeAlarms.empty());
-
- cemu_assert_debug(false);
+ __OSLockScheduler();
+ if(g_activeAlarms.empty())
+ {
+ __OSUnlockScheduler();
+ return;
+ }
+ for(auto& itr : g_activeAlarms)
+ {
+ OSHostAlarmDestroy(itr.second);
+ }
+ g_activeAlarms.clear();
+ OSHostAlarm::Reset();
+ __OSUnlockScheduler();
}
void _OSAlarmThread(PPCInterpreter_t* hCPU)
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h
index a67beca7..472d4f21 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h
@@ -45,7 +45,7 @@ namespace coreinit
void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData);
void OSSetPeriodicAlarm(OSAlarm_t* OSAlarm, uint64 startTick, uint64 periodTick, MPTR OSAlarmHandler);
- void OSAlarm_resetAll();
+ void OSAlarm_Shutdown();
void alarm_update();
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp
index dcc0d6df..51a3f542 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp
@@ -64,6 +64,24 @@ sint32 _GetArgLength(const char* arg)
return c;
}
+static std::string GetLaunchArgs()
+{
+ std::string argStr = CafeSystem::GetForegroundTitleArgStr();
+ if(std::vector overrideArgs; CafeSystem::GetOverrideArgStr(overrideArgs))
+ {
+ // args are overriden by launch directive (OSLaunchTitleByPath)
+ // keep the rpx path but use the arguments from the override
+ if (size_t pos = argStr.find(' '); pos != std::string::npos)
+ argStr.resize(pos);
+ for(size_t i=0; iargStorage, std::string(rpxFileName).c_str());
- std::string _argStr = CafeSystem::GetForegroundTitleArgStr();
+ std::string _argStr = GetLaunchArgs();
+ CafeSystem::UnsetOverrideArgs(); // make sure next launch doesn't accidentally use the same arguments
const char* argString = _argStr.c_str();
// attach parameters from arg string
if (argString && argString[0] != '\0')
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp
index 41eaa2fa..b348218f 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp
@@ -103,13 +103,12 @@ void coreinitExport_MCP_TitleListByAppType(PPCInterpreter_t* hCPU)
void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU)
{
- cemuLog_logDebug(LogType::Force, "MCP_TitleList(...) unimplemented");
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32BEPtr(countOutput, 1);
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 2);
ppcDefineParamU32(titleListBufferSize, 3);
- // todo -> Other parameters
+ // todo -> Other parameters?
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST;
@@ -120,6 +119,8 @@ void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU)
*countOutput = mcpRequest->titleListRequest.titleCount;
+ cemuLog_logDebug(LogType::Force, "MCP_TitleList(...) returned {} titles", (uint32)mcpRequest->titleListRequest.titleCount);
+
osLib_returnFromFunction(hCPU, mcpRequest->returnCode);
}
@@ -186,7 +187,6 @@ void coreinitExport_MCP_GetTitleInfoByTitleAndDevice(PPCInterpreter_t* hCPU)
}
osLib_returnFromFunction(hCPU, mcpRequest->returnCode);
-
}
namespace coreinit
@@ -200,7 +200,7 @@ namespace coreinit
systemVersion->n0 = 0x5;
systemVersion->n1 = 0x5;
- systemVersion->n2 = 0x2;
+ systemVersion->n2 = 0x5;
// todo: Load this from \sys\title\00050010\10041200\content\version.bin
osLib_returnFromFunction(hCPU, 0);
@@ -236,57 +236,56 @@ namespace coreinit
}
#pragma pack(1)
- typedef struct
+ struct MCPDevice_t
{
- /* +0x000 */ char storageName[0x90]; // the name in the storage path
+ /* +0x000 */ char storageName[0x8]; // the name in the storage path (mlc, slc, usb?) // volumeId at +8
+ /* +0x008 */ char volumeId[16]; //
+ /* +0x018 */ char ukn[0x90 - 0x18];
/* +0x090 */ char storagePath[0x280 - 1]; // /vol/storage_%s%02x
- /* +0x30F */ uint32be storageSubindexOrMask; // the id in the storage path, but this might also be a MASK of indices (e.g. 1 -> Only device 1, 7 -> Device 1,2,3) men.rpx expects 0xF (or 0x7?) to be set for MLC, SLC and USB for MLC_FullDeviceList
+ /* +0x30F */ uint32be flags; // men.rpx checks for 0x2 and 0x8
uint8 ukn313[4];
uint8 ukn317[4];
- }MCPDevice_t;
+ };
#pragma pack()
- static_assert(sizeof(MCPDevice_t) == 0x31B, "MCPDevice_t has invalid size");
- static_assert(offsetof(MCPDevice_t, storagePath) == 0x090, "MCPDevice_t.storagePath has invalid offset");
- static_assert(offsetof(MCPDevice_t, storageSubindexOrMask) == 0x30F, "MCPDevice_t.storageSubindex has invalid offset");
- static_assert(offsetof(MCPDevice_t, ukn313) == 0x313, "MCPDevice_t.ukn313 has invalid offset");
- static_assert(offsetof(MCPDevice_t, ukn317) == 0x317, "MCPDevice_t.ukn317 has invalid offset");
+ static_assert(sizeof(MCPDevice_t) == 0x31B);
+
+ static_assert(sizeof(MCPDevice_t) == 0x31B);
+ static_assert(offsetof(MCPDevice_t, storagePath) == 0x90);
+ static_assert(offsetof(MCPDevice_t, flags) == 0x30F);
+ static_assert(offsetof(MCPDevice_t, ukn313) == 0x313);
+ static_assert(offsetof(MCPDevice_t, ukn317) == 0x317);
void MCP_DeviceListEx(uint32 mcpHandle, uint32be* deviceCount, MCPDevice_t* deviceList, uint32 deviceListSize, bool returnFullList)
{
sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t);
- if (maxDeviceCount < 3*3)
- assert_dbg();
+ cemu_assert(maxDeviceCount >= 2);
- // if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot
-
- memset(deviceList, 0, sizeof(MCPDevice_t) * 1);
+ memset(deviceList, 0, deviceListSize);
sint32 index = 0;
- for (sint32 f = 0; f < 1; f++)
- {
- // 0
- strcpy(deviceList[index].storageName, "mlc");
- deviceList[index].storageSubindexOrMask = 0xF; // bitmask?
- sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask);
- index++;
- // 1
- strcpy(deviceList[index].storageName, "slc");
- deviceList[index].storageSubindexOrMask = 0xF; // bitmask?
- sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask);
- index++;
- // 2
- strcpy(deviceList[index].storageName, "usb");
- deviceList[index].storageSubindexOrMask = 0xF;
- sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask);
- index++;
- }
+ uint32 flags = 2 | 8;
+ // flag 2 is necessary for Wii U menu and Friend List to load
+ // if we dont set flag 0x8 then Wii U menu will show a disk loading icon and screen
+ // slc
+ strcpy(deviceList[index].storageName, "slc");
+ strcpy(deviceList[index].volumeId, "VOLID_SLC");
+ deviceList[index].flags = flags;
+ strcpy(deviceList[index].storagePath, "/vol/system_slc"); // unsure
+ index++;
+ // mlc
+ strcpy(deviceList[index].storageName, "mlc");
+ strcpy(deviceList[index].volumeId, "VOLID_MLC");
+ deviceList[index].flags = flags;
+ sprintf(deviceList[index].storagePath, "/vol/storage_mlc01");
+ index++;
+
+ // we currently dont emulate USB storage
*deviceCount = index;
}
-
void export_MCP_DeviceList(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
@@ -306,12 +305,12 @@ namespace coreinit
memset(deviceList, 0, sizeof(MCPDevice_t) * 1);
// 0
strcpy(deviceList[0].storageName, "mlc");
- deviceList[0].storageSubindexOrMask = (0x01); // bitmask?
- sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].storageSubindexOrMask);
+ deviceList[0].flags = (0x01); // bitmask?
+ sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].flags);
// 1
strcpy(deviceList[1].storageName, "slc");
- deviceList[1].storageSubindexOrMask = (0x01); // bitmask?
- sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].storageSubindexOrMask);
+ deviceList[1].flags = (0x01); // bitmask?
+ sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].flags);
// 2
//strcpy(deviceList[2].storageName, "usb");
@@ -360,6 +359,8 @@ namespace coreinit
// this callback is to let the app know when the title list changed?
+ //PPCCoreCallback(callbackMPTR); // -> If we trigger the callback then the menu will repeat with a call to MCP_GetTitleList(), MCP_DeviceList() and MCP_TitleListUpdateGetNext
+
osLib_returnFromFunction(hCPU, 0);
}
@@ -387,6 +388,34 @@ namespace coreinit
osLib_returnFromFunction(hCPU, 0);
}
+ uint32 MCP_UpdateClearContextAsync(uint32 mcpHandle, betype* callbackPtr)
+ {
+ cemuLog_logDebug(LogType::Force, "MCP_UpdateClearContextAsync() - stubbed");
+ uint32 clearContextResult = 0;
+ PPCCoreCallback(*callbackPtr, clearContextResult);
+ return 0;
+ }
+
+ uint32 MCP_InstallUtilGetTitleEnability(uint32 mcpHandle, uint32be* enabilityOutput, MCPTitleInfo* title)
+ {
+ *enabilityOutput = 1;
+ return 0;
+ }
+
+ uint32 MCP_GetEcoSettings(uint32 mcpHandle, uint32be* flagCaffeineEnable, uint32be* uknFlag2, uint32be* uknFlag3)
+ {
+ *flagCaffeineEnable = 1; // returning 1 here will stop the Wii U Menu from showing the Quick Start setup dialogue
+ *uknFlag2 = 0;
+ *uknFlag3 = 0;
+ return 0;
+ }
+
+ uint32 MCP_RightCheckLaunchable(uint32 mcpHandle, uint64 titleId, uint32be* launchableOut)
+ {
+ *launchableOut = 1;
+ return 0;
+ }
+
void InitializeMCP()
{
osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open);
@@ -408,6 +437,12 @@ namespace coreinit
osLib_addFunction("coreinit", "MCP_UpdateCheckContext", export_MCP_UpdateCheckContext);
osLib_addFunction("coreinit", "MCP_TitleListUpdateGetNext", export_MCP_TitleListUpdateGetNext);
osLib_addFunction("coreinit", "MCP_GetOverlayAppInfo", export_MCP_GetOverlayAppInfo);
+ cafeExportRegister("coreinit", MCP_UpdateClearContextAsync, LogType::Placeholder);
+
+ cafeExportRegister("coreinit", MCP_InstallUtilGetTitleEnability, LogType::Placeholder);
+ cafeExportRegister("coreinit", MCP_RightCheckLaunchable, LogType::Placeholder);
+
+ cafeExportRegister("coreinit", MCP_GetEcoSettings, LogType::Placeholder);
}
}
@@ -511,6 +546,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU)
{
// get parental online control for online features
// note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used
+ // a non-zero value means network access is restricted through parental access. 0 means allowed
// account in slot 1
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed
@@ -526,7 +562,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU)
{
// miiverse restrictions
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
- memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only?, 2 -> no access?)
+ memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only, 2 -> no access)
}
else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0)
{
@@ -552,6 +588,27 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU)
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
}
+ /* caffeine settings (Quick Start) */
+ else if (_strcmpi(ucParam->settingName, "caffeine.enable") == 0)
+ {
+ if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
+ memory_writeU8(_swapEndianU32(ucParam->resultPtr), 1);
+ }
+ else if (_strcmpi(ucParam->settingName, "caffeine.ad_enable") == 0)
+ {
+ if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
+ memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
+ }
+ else if (_strcmpi(ucParam->settingName, "caffeine.push_enable") == 0)
+ {
+ if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
+ memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
+ }
+ else if (_strcmpi(ucParam->settingName, "caffeine.drcled_enable") == 0)
+ {
+ if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
+ memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
+ }
else
{
cemuLog_logDebug(LogType::Force, "Unsupported SCI value: {} Size {:08x}", ucParam->settingName, ucParam->ukn4_size);
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp
index d8cc500a..dc82f772 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp
@@ -615,10 +615,24 @@ namespace coreinit
cemu_assert_unimplemented();
}
+ void MEMResetToDefaultState()
+ {
+ for (auto& it : sHeapBaseHandle)
+ it = nullptr;
+
+ g_heapTableCount = 0;
+ g_slockInitialized = false;
+ g_listsInitialized = false;
+ gDefaultHeap = nullptr;
+
+ memset(&g_list1, 0, sizeof(g_list1));
+ memset(&g_list2, 0, sizeof(g_list2));
+ memset(&g_list3, 0, sizeof(g_list3));
+ }
+
void InitializeMEM()
{
- for (auto& it : sHeapBaseHandle)
- it = nullptr;
+ MEMResetToDefaultState();
cafeExportRegister("coreinit", CoreInitDefaultHeap, LogType::CoreinitMem);
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp
index b8a964ce..05660c71 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp
@@ -1,5 +1,8 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
+#include "Cafe/CafeSystem.h"
+#include "Cafe/Filesystem/fsc.h"
+#include
namespace coreinit
{
@@ -309,14 +312,20 @@ namespace coreinit
cemu_assert_unimplemented();
}
- void COSWarn()
+ void COSWarn(int moduleId, const char* format)
{
- cemu_assert_debug(false);
+ char buffer[1024 * 2];
+ int prefixLen = sprintf(buffer, "[COSWarn-%d] ", moduleId);
+ sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, ppcInterpreterCurrentInstance, 2);
+ WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen);
}
- void OSLogPrintf()
+ void OSLogPrintf(int ukn1, int ukn2, int ukn3, const char* format)
{
- cemu_assert_debug(false);
+ char buffer[1024 * 2];
+ int prefixLen = sprintf(buffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3);
+ sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, ppcInterpreterCurrentInstance, 4);
+ WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen);
}
void OSConsoleWrite(const char* strPtr, sint32 length)
@@ -341,19 +350,124 @@ namespace coreinit
return true;
}
+ uint32 s_sdkVersion;
+
+ uint32 __OSGetProcessSDKVersion()
+ {
+ return s_sdkVersion;
+ }
+
+ // move this to CafeSystem.cpp?
+ void OSLauncherThread(uint64 titleId)
+ {
+ CafeSystem::ShutdownTitle();
+ CafeSystem::PrepareForegroundTitle(titleId);
+ CafeSystem::RequestRecreateCanvas();
+ CafeSystem::LaunchForegroundTitle();
+ }
+
+ uint32 __LaunchByTitleId(uint64 titleId, uint32 argc, MEMPTR* argv)
+ {
+ // prepare argument buffer
+ #if 0
+ char argumentBuffer[4096];
+ uint32 argumentBufferLength = 0;
+ char* argWriter = argumentBuffer;
+ for(uint32 i=0; i= sizeof(argumentBuffer))
+ {
+ // argument buffer full
+ cemuLog_logDebug(LogType::Force, "LaunchByTitleId: argument buffer full");
+ return 0x80000000;
+ }
+ memcpy(argWriter, arg, argLength);
+ argWriter[argLength] = '\0';
+ argWriter += argLength + 1;
+ argumentBufferLength += argLength + 1;
+ }
+ #endif
+ // normally the above buffer is passed to the PPC kernel via syscall 0x2B and then
+ // the kernel forwards it to IOSU MCP when requesting a title launch
+ // but for now we HLE most of the launching code and can just set the argument array directly
+ std::vector argArray;
+ for(uint32 i=0; i= (sizeof(appXmlPath) - 32))
+ {
+ // path too long
+ cemuLog_logDebug(LogType::Force, "OSLaunchTitleByPathl: path too long");
+ return 0x80000000;
+ }
+ // read app.xml to get the titleId
+ memcpy(appXmlPath, path, pathLength);
+ appXmlPath[pathLength] = '\0';
+ strcat(appXmlPath, "/code/app.xml");
+ sint32 status;
+ auto fscfile = fsc_open(appXmlPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status);
+ if (!fscfile)
+ {
+ cemuLog_logDebug(LogType::Force, "OSLaunchTitleByPathl: failed to open target app.xml");
+ return 0x80000000;
+ }
+ uint32 size = fsc_getFileSize(fscfile);
+ std::vector tmpData(size);
+ fsc_readFile(fscfile, tmpData.data(), size);
+ fsc_close(fscfile);
+ // parse app.xml to get the titleId
+ pugi::xml_document app_doc;
+ if (!app_doc.load_buffer_inplace(tmpData.data(), tmpData.size()))
+ return false;
+ uint64 titleId = std::stoull(app_doc.child("app").child("title_id").child_value(), nullptr, 16);
+ if(titleId == 0)
+ {
+ cemuLog_logDebug(LogType::Force, "OSLaunchTitleByPathl: failed to parse titleId from app.xml");
+ return 0x80000000;
+ }
+ __LaunchByTitleId(titleId, 0, nullptr);
+ return 0;
+ }
+
+ uint32 OSRestartGame(uint32 argc, MEMPTR* argv)
+ {
+ __LaunchByTitleId(CafeSystem::GetForegroundTitleId(), argc, argv);
+ return 0;
+ }
+
void miscInit()
{
+ s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion();
+
cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSReport, LogType::Placeholder);
cafeExportRegister("coreinit", OSVReport, LogType::Placeholder);
cafeExportRegister("coreinit", COSWarn, LogType::Placeholder);
cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder);
+ cafeExportRegister("coreinit", __OSGetProcessSDKVersion, LogType::Placeholder);
g_homeButtonMenuEnabled = true; // enabled by default
// Disney Infinity 2.0 actually relies on home button menu being enabled by default. If it's false it will crash due to calling erreula->IsAppearHomeNixSign() before initializing erreula
cafeExportRegister("coreinit", OSIsHomeButtonMenuEnabled, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSEnableHomeButtonMenu, LogType::CoreinitThread);
+
+ cafeExportRegister("coreinit", OSLaunchTitleByPathl, LogType::Placeholder);
+ cafeExportRegister("coreinit", OSRestartGame, LogType::Placeholder);
}
};
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h
index 5cb0ee10..4a74d490 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h
@@ -2,5 +2,9 @@
namespace coreinit
{
+ uint32 __OSGetProcessSDKVersion();
+ uint32 OSLaunchTitleByPathl(const char* path, uint32 pathLength, uint32 argc);
+ uint32 OSRestartGame(uint32 argc, MEMPTR* argv);
+
void miscInit();
};
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp
index 6724ee5f..0ba23708 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp
@@ -20,8 +20,6 @@ SlimRWLock srwlock_activeThreadList;
MPTR activeThread[256];
sint32 activeThreadCount = 0;
-MPTR exitThreadPtr = 0;
-
void nnNfp_update();
namespace coreinit
@@ -228,8 +226,6 @@ namespace coreinit
}
}
- MPTR funcPtr_threadEntry = 0;
-
void threadEntry(PPCInterpreter_t* hCPU)
{
OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(hCPU);
@@ -253,12 +249,6 @@ namespace coreinit
void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU);
- void initFunctionPointers()
- {
- exitThreadPtr = PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr);
- funcPtr_threadEntry = PPCInterpreter_makeCallableExportDepr(threadEntry);
- }
-
void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType)
{
cemu_assert_debug(thread != nullptr); // make thread struct mandatory. Caller can always use SysAllocator
@@ -266,7 +256,7 @@ namespace coreinit
bool isThreadStillActive = __OSIsThreadActive(thread);
if (isThreadStillActive)
{
- // workaround for games that restart threads to quickly
+ // workaround for games that restart threads before they correctly entered stopped/moribund state
// seen in Fast Racing Neo at boot (0x020617BC OSCreateThread)
cemuLog_log(LogType::Force, "Game attempting to re-initialize existing thread");
while ((thread->state == OSThread_t::THREAD_STATE::STATE_READY || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) && thread->suspendCounter == 0)
@@ -287,10 +277,6 @@ namespace coreinit
}
cemu_assert_debug(__OSIsThreadActive(thread) == false);
__OSUnlockScheduler();
-
- initFunctionPointers();
- if (thread == nullptr)
- thread = (OSThread_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(OSThread_t), 32));
memset(thread, 0x00, sizeof(OSThread_t));
// init signatures
thread->SetMagic();
@@ -307,8 +293,8 @@ namespace coreinit
// init misc stuff
thread->attr = affinityMask;
thread->context.setAffinity(affinityMask);
- thread->context.srr0 = funcPtr_threadEntry;
- thread->context.lr = _swapEndianU32(exitThreadPtr);
+ thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry);
+ thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr));
thread->id = 0x8000; // Warriors Orochi 3 softlocks if this is zero due to confusing threads (_OSActivateThread should set this?)
// init ugqr
thread->context.gqr[0] = 0x00000000;
@@ -390,8 +376,8 @@ namespace coreinit
// todo - this should fully reinitialize the thread?
thread->entrypoint = _swapEndianU32(funcAddress);
- thread->context.srr0 = coreinit::funcPtr_threadEntry;
- thread->context.lr = _swapEndianU32(exitThreadPtr);
+ thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry);
+ thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr));
thread->context.gpr[3] = _swapEndianU32(numParam);
thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam));
thread->suspendCounter = 0; // verify
@@ -1048,6 +1034,18 @@ namespace coreinit
return selectedThread;
}
+ void __OSDeleteAllActivePPCThreads()
+ {
+ __OSLockScheduler();
+ while(activeThreadCount > 0)
+ {
+ MEMPTR t{activeThread[0]};
+ t->state = OSThread_t::THREAD_STATE::STATE_NONE;
+ __OSDeactivateThread(t.GetPtr());
+ }
+ __OSUnlockScheduler();
+ }
+
void __OSCheckSystemEvents()
{
// AX update
@@ -1232,7 +1230,7 @@ namespace coreinit
g_schedulerThreadHandles.emplace_back(it.native_handle());
}
- // shuts down all scheduler host threads and deletes all fibers and their state
+ // shuts down all scheduler host threads and deletes all fibers and ppc threads
void OSSchedulerEnd()
{
std::unique_lock _lock(sSchedulerStateMtx);
@@ -1414,9 +1412,10 @@ namespace coreinit
for (sint32 i = 0; i < PPC_CORE_COUNT; i++)
__currentCoreThread[i] = nullptr;
- __OSInitDefaultThreads();
+ __OSInitDefaultThreads();
__OSInitTerminatorThreads();
- }
+
+ }
}
void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count)
diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h
index bac966a2..74e27d8f 100644
--- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h
+++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h
@@ -608,6 +608,7 @@ namespace coreinit
void __OSQueueThreadDeallocation(OSThread_t* thread);
bool __OSIsThreadActive(OSThread_t* thread);
+ void __OSDeleteAllActivePPCThreads();
}
#pragma pack()
diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp
index ce3bd984..8d584190 100644
--- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp
+++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp
@@ -45,6 +45,12 @@ namespace GX2
sint32 gx2WriteGatherCurrentMainCoreIndex = -1;
bool gx2WriteGatherInited = false;
+ void GX2WriteGather_ResetToDefaultState()
+ {
+ gx2WriteGatherCurrentMainCoreIndex = -1;
+ gx2WriteGatherInited = false;
+ }
+
void GX2Init_writeGather() // init write gather, make current core
{
if (gx2WriteGatherPipe.gxRingBuffer == NULL)
@@ -289,7 +295,6 @@ namespace GX2
void GX2CommandInit()
{
-
cafeExportRegister("gx2", GX2BeginDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2BeginDisplayListEx, LogType::GX2);
cafeExportRegister("gx2", GX2EndDisplayList, LogType::GX2);
@@ -305,4 +310,9 @@ namespace GX2
cafeExportRegister("gx2", GX2PatchDisplayList, LogType::GX2);
}
+ void GX2CommandResetToDefaultState()
+ {
+ GX2WriteGather_ResetToDefaultState();
+ }
+
}
diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.h b/src/Cafe/OS/libs/gx2/GX2_Command.h
index a8d3671f..635680e0 100644
--- a/src/Cafe/OS/libs/gx2/GX2_Command.h
+++ b/src/Cafe/OS/libs/gx2/GX2_Command.h
@@ -97,5 +97,6 @@ namespace GX2
void GX2DirectCallDisplayList(void* addr, uint32 size);
void GX2Init_writeGather();
- void GX2CommandInit();
+ void GX2CommandInit();
+ void GX2CommandResetToDefaultState();
}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/gx2/GX2_Event.cpp b/src/Cafe/OS/libs/gx2/GX2_Event.cpp
index 0b8100f4..ba498477 100644
--- a/src/Cafe/OS/libs/gx2/GX2_Event.cpp
+++ b/src/Cafe/OS/libs/gx2/GX2_Event.cpp
@@ -308,4 +308,15 @@ namespace GX2
coreinit::OSInitEvent(s_updateRetirementEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
coreinit::OSInitSemaphore(s_eventCbQueueSemaphore, 0);
}
+
+ void GX2EventResetToDefaultState()
+ {
+ s_callbackThreadLaunched = false;
+ s_lastRetirementTimestamp = 0;
+ for(auto& it : s_eventCallback)
+ {
+ it.callbackFuncPtr = nullptr;
+ it.userData = nullptr;
+ }
+ }
}
diff --git a/src/Cafe/OS/libs/gx2/GX2_Event.h b/src/Cafe/OS/libs/gx2/GX2_Event.h
index 70c4ba61..09cb073c 100644
--- a/src/Cafe/OS/libs/gx2/GX2_Event.h
+++ b/src/Cafe/OS/libs/gx2/GX2_Event.h
@@ -2,9 +2,10 @@
namespace GX2
{
- void GX2EventInit();
void GX2Init_event();
+ void GX2EventResetToDefaultState();
+ void GX2EventInit();
void GX2WaitForVsync();
void GX2WaitForFlip();
void GX2DrawDone();
diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp
index 0967f5e2..2111238a 100644
--- a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp
+++ b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp
@@ -115,6 +115,9 @@ namespace GX2
void _GX2DriverReset()
{
LatteGPUState.gx2InitCalled = 0;
+ sGX2MainCoreIndex = 0;
+ GX2CommandResetToDefaultState();
+ GX2EventResetToDefaultState();
}
sint32 GX2GetMainCoreId(PPCInterpreter_t* hCPU)
diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp
index f8affea3..516087a3 100644
--- a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp
+++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp
@@ -43,8 +43,11 @@ namespace acp
return ACPStatus::SUCCESS;
}
+ bool sSaveDirMounted{false};
+
ACPStatus ACPMountSaveDir()
{
+ cemu_assert_debug(!sSaveDirMounted);
uint64 titleId = CafeSystem::GetForegroundTitleId();
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
uint32 low = GetTitleIdLow(titleId);
@@ -56,6 +59,13 @@ namespace acp
return _ACPConvertResultToACPStatus(&mountResult, "ACPMountSaveDir", 0x60);
}
+ ACPStatus ACPUnmountSaveDir()
+ {
+ cemu_assert_debug(!sSaveDirMounted);
+ fsc_unmount("/vol/save/", FSC_PRIORITY_BASE);
+ return ACPStatus::SUCCESS;
+ }
+
uint64 _acpGetTimestamp()
{
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
@@ -434,12 +444,12 @@ namespace acp
ppcDefineParamU32(deviceId, 3);
if (deviceId != 3)
- assert_dbg();
+ cemuLog_logDebug(LogType::Force, "ACPGetTitleMetaXmlByDevice(): Unsupported deviceId");
acpPrepareRequest();
acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML;
acpRequest->ptr = acpMetaXml;
- acpRequest->titleId = CafeSystem::GetForegroundTitleId();
+ acpRequest->titleId = titleId;//CafeSystem::GetForegroundTitleId();
__depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector);
diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.h b/src/Cafe/OS/libs/nn_acp/nn_acp.h
index 33a9c487..cbf36c64 100644
--- a/src/Cafe/OS/libs/nn_acp/nn_acp.h
+++ b/src/Cafe/OS/libs/nn_acp/nn_acp.h
@@ -20,6 +20,7 @@ namespace acp
ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId);
ACPStatus ACPMountSaveDir();
+ ACPStatus ACPUnmountSaveDir();
ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type);
ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);;
diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp
index fed77bcc..c2d65a5f 100644
--- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp
+++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp
@@ -1483,7 +1483,7 @@ std::string nnBossNsDataExport_GetPath(nsData_t* nsData)
if (title_id == 0)
title_id = CafeSystem::GetForegroundTitleId();
- fs::path path = fmt::format(L"cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId);
+ fs::path path = fmt::format("cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId);
path /= nsData->storage.storageName;
path /= nsData->name;
return path.string();
@@ -1578,6 +1578,13 @@ void nnBossNsDataExport_getSize(PPCInterpreter_t* hCPU)
osLib_returnFromFunction64(hCPU, fileSize);
}
+uint64 nnBossNsData_GetCreatedTime(nsData_t* nsData)
+{
+ cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0");
+ uint64 createdTime = 0;
+ return createdTime;
+}
+
uint32 nnBossNsData_read(nsData_t* nsData, uint64* sizeOutBE, void* buffer, sint32 length)
{
FSCVirtualFile* fscStorageFile = nullptr;
@@ -1797,6 +1804,7 @@ void nnBoss_load()
osLib_addFunction("nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", nnBossNsDataExport_DeleteRealFileWithHistory);
osLib_addFunction("nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", nnBossNsDataExport_Exist);
osLib_addFunction("nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", nnBossNsDataExport_getSize);
+ cafeExportRegisterFunc(nnBossNsData_GetCreatedTime, "nn_boss", "GetCreatedTime__Q3_2nn4boss6NsDataCFv", LogType::Placeholder);
osLib_addFunction("nn_boss", "Read__Q3_2nn4boss6NsDataFPvUi", nnBossNsDataExport_read);
osLib_addFunction("nn_boss", "Read__Q3_2nn4boss6NsDataFPLPvUi", nnBossNsDataExport_readWithSizeOut);
osLib_addFunction("nn_boss", "Seek__Q3_2nn4boss6NsDataFLQ3_2nn4boss12PositionBase", nnBossNsDataExport_seek);
diff --git a/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp b/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp
index 61e9f831..5a69b787 100644
--- a/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp
+++ b/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp
@@ -1,27 +1,91 @@
#include "nn_ndm.h"
#include "Cafe/OS/common/OSCommon.h"
+#include "Cafe/OS/libs/nn_common.h"
namespace nn
{
namespace ndm
{
- void nnNdmExport_GetDaemonStatus(PPCInterpreter_t* hCPU)
+
+ enum class DAEMON_NAME : uint32
{
- // parameters:
- // r3 pointer to status integer (out)
- // r4 daemon name (integer)
- cemuLog_logDebug(LogType::Force, "nn_ndm.GetDaemonStatus(...) - hack");
- // status codes:
- // 1 - running? Download Manager (scope.rpx) expects this to return 1 (or zero). Otherwise it will display downloads as disabled
- memory_writeU32(hCPU->gpr[3], 1);
- // 2 - running?
- // 3 - suspended?
- osLib_returnFromFunction(hCPU, 0);
+ UKN_0, // Boss related?
+ UKN_1, // Download Manager? scope.rpx (Download Manager app) expects this to have status 0 or 1. Otherwise it will display downloads as disabled
+ UKN_2,
+ };
+
+ enum class DAEMON_STATUS : uint32
+ {
+ STATUS_UKN_0 = 0, // probably: Ready or initializing?
+ RUNNING = 1, // most likely running, but not 100% sure
+ STATUS_UKN_2 = 2, // probably: ready, starting or something like that?
+ SUSPENDED = 3,
+ };
+
+ constexpr size_t NUM_DAEMONS = 3;
+ DAEMON_STATUS s_daemonStatus[NUM_DAEMONS];
+ uint32 s_initializeRefCount;
+
+ uint32 Initialize()
+ {
+ s_initializeRefCount++;
+ return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0);
+ }
+
+ uint32 IsInitialized()
+ {
+ return s_initializeRefCount != 0 ? 1 : 0;
+ }
+
+ uint32 Finalize()
+ {
+ if(s_initializeRefCount == 0)
+ return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NDM, 0);
+ s_initializeRefCount++;
+ return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0);
+ }
+
+ uint32 GetDaemonStatus(betype* statusOut, DAEMON_NAME daemonName)
+ {
+ size_t daemonIndex = (size_t)daemonName;
+ if(daemonIndex >= NUM_DAEMONS)
+ return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NDM, 0);
+ *statusOut = s_daemonStatus[daemonIndex];
+ return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0);
+ }
+
+ uint32 SuspendDaemons(uint32 daemonNameBitmask)
+ {
+ for(size_t i=0; i _buf_nimRequest; \
@@ -61,8 +62,6 @@ namespace nn
void export_GetNumTitlePackages(PPCInterpreter_t* hCPU)
{
- cemuLog_logDebug(LogType::Force, "GetNumTitlePackages() - placeholder");
-
nimPrepareRequest();
nimRequest->requestCode = IOSU_NIM_GET_PACKAGE_COUNT;
@@ -152,9 +151,10 @@ namespace nn
{
cemuLog_logDebug(LogType::Force, "QuerySchedulerStatus() - placeholder");
- // scheduler status seems to a be a 32bit value?
+ // scheduler status seems to be either a 4 byte array or 8 byte array (or structs)?
// scope.rpx only checks the second byte and if it matches 0x01 then the scheduler is considered paused/stopped (displays that downloads are inactive)
-
+ // men.rpx checks the first byte for == 1 and if true, it will show the download manager icon as downloading
+
// downloads disabled:
//memory_writeU32(hCPU->gpr[3], (0x00010000));
// downloads enabled:
@@ -163,24 +163,44 @@ namespace nn
osLib_returnFromFunction(hCPU, 0);
}
- typedef struct
+ struct nimResultError
{
uint32be iosError;
uint32be ukn04;
- }nimResultError_t; // size unknown, but probably is 0x8
+ };
-
- void export_ConstructResultError(PPCInterpreter_t* hCPU)
+ void ConstructResultError(nimResultError* resultError, uint32be* nimErrorCodePtr, uint32 uknParam)
{
- cemuLog_logDebug(LogType::Force, "Construct__Q3_2nn3nim11ResultErrorFQ2_2nn6Resulti() - placeholder");
- ppcDefineParamTypePtr(resultError, nimResultError_t, 0);
- ppcDefineParamU32BEPtr(nimErrorCodePtr, 1);
- ppcDefineParamU32(uknParam, 2);
-
- resultError->iosError = 0;
+ uint32 nnResultCode = *nimErrorCodePtr;
+ resultError->iosError = nnResultCode;
resultError->ukn04 = uknParam;
- osLib_returnFromFunction(hCPU, 0);
+ if (nnResultCode == 0xFFFFFFFF)
+ {
+ // not a valid code, used by a Wii U menu
+ return;
+ }
+
+ // IOS errors need to be translated
+ if ( (nnResultCode&0x18000000) == 0x18000000)
+ {
+ // alternative error format
+ cemu_assert_unimplemented();
+ }
+ else
+ {
+ auto moduleId = nn::nnResult_GetModule(nnResultCode);
+ if (moduleId == NN_RESULT_MODULE_NN_IOS)
+ {
+ // ios error
+ cemu_assert_unimplemented();
+ }
+ else
+ {
+ // other error
+ resultError->iosError = 0;
+ }
+ }
}
void export_GetECommerceInfrastructureCountry(PPCInterpreter_t* hCPU)
@@ -272,7 +292,7 @@ namespace nn
osLib_addFunction("nn_nim", "GetIconDatabaseEntries__Q2_2nn3nimFPQ3_2nn3nim17IconDatabaseEntryPCULUi", export_GetIconDatabaseEntries);
- osLib_addFunction("nn_nim", "Construct__Q3_2nn3nim11ResultErrorFQ2_2nn6Resulti", export_ConstructResultError);
+ cafeExportRegisterFunc(ConstructResultError, "nn_nim", "Construct__Q3_2nn3nim11ResultErrorFQ2_2nn6Resulti", LogType::Placeholder);
osLib_addFunction("nn_nim", "MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy__Q3_2nn3nim4utilFULiQ3_2nn4Cafe9TitleType", export_MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy);
osLib_addFunction("nn_nim", "CalculateTitleInstallSize__Q2_2nn3nimFPLRCQ3_2nn3nim22TitlePackageTaskConfigPCUsUi", export_CalculateTitleInstallSize);
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp
index 4290d3c2..25245b5c 100644
--- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp
@@ -4,6 +4,8 @@
#include "nn_olv_UploadCommunityTypes.h"
#include "nn_olv_DownloadCommunityTypes.h"
#include "nn_olv_UploadFavoriteTypes.h"
+#include "nn_olv_PostTypes.h"
+#include "nn_olv_OfflineDB.h"
#include "Cafe/OS/libs/proc_ui/proc_ui.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
@@ -12,179 +14,6 @@ namespace nn
{
namespace olv
{
- struct DownloadedPostData_t
- {
- /* +0x0000 */ uint32be flags;
- /* +0x0004 */ uint32be userPrincipalId;
- /* +0x0008 */ char postId[0x20]; // size guessed
- /* +0x0028 */ uint64 postDate;
- /* +0x0030 */ uint8 feeling;
- /* +0x0031 */ uint8 padding0031[3];
- /* +0x0034 */ uint32be regionId;
- /* +0x0038 */ uint8 platformId;
- /* +0x0039 */ uint8 languageId;
- /* +0x003A */ uint8 countryId;
- /* +0x003B */ uint8 padding003B[1];
- /* +0x003C */ uint16be bodyText[0x100]; // actual size is unknown
- /* +0x023C */ uint32be bodyTextLength;
- /* +0x0240 */ uint8 compressedMemoBody[0xA000]; // 40KB
- /* +0xA240 */ uint32be compressedMemoBodyRelated; // size of compressed data?
- /* +0xA244 */ uint16be topicTag[0x98];
- // app data
- /* +0xA374 */ uint8 appData[0x400];
- /* +0xA774 */ uint32be appDataLength;
- // external binary
- /* +0xA778 */ uint8 externalBinaryUrl[0x100];
- /* +0xA878 */ uint32be externalBinaryDataSize;
- // external image
- /* +0xA87C */ uint8 externalImageDataUrl[0x100];
- /* +0xA97C */ uint32be externalImageDataSize;
- // external url ?
- /* +0xA980 */ char externalUrl[0x100];
- // mii
- /* +0xAA80 */ uint8 miiData[0x60];
- /* +0xAAE0 */ uint16be miiNickname[0x20];
- /* +0xAB20 */ uint8 unusedAB20[0x14E0];
-
- // everything above is part of DownloadedDataBase
- // everything below is part of DownloadedPostData
- /* +0xC000 */ uint8 uknDataC000[8]; // ??
- /* +0xC008 */ uint32be communityId;
- /* +0xC00C */ uint32be empathyCount;
- /* +0xC010 */ uint32be commentCount;
- /* +0xC014 */ uint8 unused[0x1F4];
- }; // size: 0xC208
-
- static_assert(sizeof(DownloadedPostData_t) == 0xC208, "");
- static_assert(offsetof(DownloadedPostData_t, postDate) == 0x0028, "");
- static_assert(offsetof(DownloadedPostData_t, platformId) == 0x0038, "");
- static_assert(offsetof(DownloadedPostData_t, bodyText) == 0x003C, "");
- static_assert(offsetof(DownloadedPostData_t, compressedMemoBody) == 0x0240, "");
- static_assert(offsetof(DownloadedPostData_t, topicTag) == 0xA244, "");
- static_assert(offsetof(DownloadedPostData_t, appData) == 0xA374, "");
- static_assert(offsetof(DownloadedPostData_t, externalBinaryUrl) == 0xA778, "");
- static_assert(offsetof(DownloadedPostData_t, externalImageDataUrl) == 0xA87C, "");
- static_assert(offsetof(DownloadedPostData_t, externalUrl) == 0xA980, "");
- static_assert(offsetof(DownloadedPostData_t, miiData) == 0xAA80, "");
- static_assert(offsetof(DownloadedPostData_t, miiNickname) == 0xAAE0, "");
- static_assert(offsetof(DownloadedPostData_t, unusedAB20) == 0xAB20, "");
- static_assert(offsetof(DownloadedPostData_t, communityId) == 0xC008, "");
- static_assert(offsetof(DownloadedPostData_t, empathyCount) == 0xC00C, "");
- static_assert(offsetof(DownloadedPostData_t, commentCount) == 0xC010, "");
-
- const int POST_DATA_FLAG_HAS_BODY_TEXT = (0x0001);
- const int POST_DATA_FLAG_HAS_BODY_MEMO = (0x0002);
-
-
- void export_DownloadPostDataList(PPCInterpreter_t* hCPU)
- {
- ppcDefineParamTypePtr(downloadedTopicData, void, 0); // DownloadedTopicData
- ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 1); // DownloadedPostData
- ppcDefineParamTypePtr(downloadedPostDataSize, uint32be, 2);
- ppcDefineParamS32(maxCount, 3);
- ppcDefineParamTypePtr(listParam, void, 4); // DownloadPostDataListParam
-
- maxCount = 0; // DISABLED
-
- // just some test
- for (sint32 i = 0; i < maxCount; i++)
- {
- DownloadedPostData_t* postData = downloadedPostData + i;
- memset(postData, 0, sizeof(DownloadedPostData_t));
- postData->userPrincipalId = 0x1000 + i;
- // post id
- sprintf(postData->postId, "postid-%04x", i+(GetTickCount()%10000));
- postData->bodyTextLength = 12;
- postData->bodyText[0] = 'H';
- postData->bodyText[1] = 'e';
- postData->bodyText[2] = 'l';
- postData->bodyText[3] = 'l';
- postData->bodyText[4] = 'o';
- postData->bodyText[5] = ' ';
- postData->bodyText[6] = 'w';
- postData->bodyText[7] = 'o';
- postData->bodyText[8] = 'r';
- postData->bodyText[9] = 'l';
- postData->bodyText[10] = 'd';
- postData->bodyText[11] = '!';
-
- postData->miiNickname[0] = 'C';
- postData->miiNickname[1] = 'e';
- postData->miiNickname[2] = 'm';
- postData->miiNickname[3] = 'u';
- postData->miiNickname[4] = '-';
- postData->miiNickname[5] = 'M';
- postData->miiNickname[6] = 'i';
- postData->miiNickname[7] = 'i';
-
- postData->topicTag[0] = 't';
- postData->topicTag[1] = 'o';
- postData->topicTag[2] = 'p';
- postData->topicTag[3] = 'i';
- postData->topicTag[4] = 'c';
-
- postData->flags = POST_DATA_FLAG_HAS_BODY_TEXT;
- }
- *downloadedPostDataSize = maxCount;
-
- osLib_returnFromFunction(hCPU, 0);
- }
-
- void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU)
- {
- ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0);
- ppcDefineParamU32(testFlags, 1);
-
- if (((uint32)downloadedPostData->flags) & testFlags)
- osLib_returnFromFunction(hCPU, 1);
- else
- osLib_returnFromFunction(hCPU, 0);
- }
-
- void exportDownloadPostData_GetPostId(PPCInterpreter_t* hCPU)
- {
- ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0);
- osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->postId));
- }
-
- void exportDownloadPostData_GetMiiNickname(PPCInterpreter_t* hCPU)
- {
- ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0);
- if(downloadedPostData->miiNickname[0] == 0 )
- osLib_returnFromFunction(hCPU, MPTR_NULL);
- else
- osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->miiNickname));
- }
-
- void exportDownloadPostData_GetTopicTag(PPCInterpreter_t* hCPU)
- {
- ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0);
- osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->topicTag));
- }
-
- void exportDownloadPostData_GetBodyText(PPCInterpreter_t* hCPU)
- {
- ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0);
- ppcDefineParamWStrBE(strOut, 1);
- ppcDefineParamS32(maxLength, 2);
-
- if (((uint32)downloadedPostData->flags&POST_DATA_FLAG_HAS_BODY_TEXT) == 0)
- {
- osLib_returnFromFunction(hCPU, 0xC1106800);
- return;
- }
-
- memset(strOut, 0, sizeof(uint16be)*maxLength);
- sint32 copyLen = std::min(maxLength - 1, (sint32)downloadedPostData->bodyTextLength);
- for (sint32 i = 0; i < copyLen; i++)
- {
- strOut[i] = downloadedPostData->bodyText[i];
- }
- strOut[copyLen] = '\0';
-
- osLib_returnFromFunction(hCPU, 0);
- }
-
struct PortalAppParam_t
{
/* +0x1A663B */ char serviceToken[32]; // size is unknown
@@ -283,21 +112,19 @@ namespace nn
void load()
{
+ g_ReportTypes = 0;
+ g_IsOnlineMode = false;
+ g_IsInitialized = false;
+ g_IsOfflineDBMode = false;
loadOliveInitializeTypes();
loadOliveUploadCommunityTypes();
loadOliveDownloadCommunityTypes();
loadOliveUploadFavoriteTypes();
+ loadOlivePostAndTopicTypes();
cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::None);
- osLib_addFunction("nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", export_DownloadPostDataList);
- osLib_addFunction("nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", exportDownloadPostData_TestFlags);
- osLib_addFunction("nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetPostId);
- osLib_addFunction("nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetMiiNickname);
- osLib_addFunction("nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetTopicTag);
- osLib_addFunction("nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", exportDownloadPostData_GetBodyText);
-
osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken);
cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Force);
@@ -312,5 +139,10 @@ namespace nn
cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::Force);
}
+ void unload() // not called yet
+ {
+ OfflineDB_Shutdown();
+ }
+
}
}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.h b/src/Cafe/OS/libs/nn_olv/nn_olv.h
index c608e391..52474b49 100644
--- a/src/Cafe/OS/libs/nn_olv/nn_olv.h
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h
@@ -19,5 +19,6 @@ namespace nn
sint32 GetOlvAccessKey(uint32_t* pOutKey);
void load();
+ void unload();
}
}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h
index 718c10c3..c598e7ba 100644
--- a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h
@@ -69,6 +69,7 @@ namespace nn
extern uint32_t g_ReportTypes;
extern bool g_IsInitialized;
extern bool g_IsOnlineMode;
+ extern bool g_IsOfflineDBMode; // use offline cache for posts
static void InitializeOliveRequest(CurlRequestHelper& req)
{
@@ -175,5 +176,39 @@ namespace nn
bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId);
sint32 olv_curlformcode_to_error(CURLFORMcode code);
+
+ // convert and copy utf8 string into UC2 big-endian array
+ template
+ uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false)
+ {
+ if(unescape)
+ {
+ // todo
+ }
+ std::wstring ws = boost::nowide::widen(sv);
+ size_t copyLen = std::min(TLength-1, ws.size());
+ for(size_t i=0; i
+ uint32 SetStringUC2(uint16be(&str)[TLength], const uint16be* strIn)
+ {
+ size_t copyLen = TLength-1;
+ for(size_t i=0; im_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0)
{
-
g_IsOnlineMode = true;
independentServiceToken_t token;
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp
new file mode 100644
index 00000000..e6cea082
--- /dev/null
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp
@@ -0,0 +1,215 @@
+#include "nn_olv_Common.h"
+#include "nn_olv_PostTypes.h"
+#include "nn_olv_OfflineDB.h"
+#include "Cemu/ncrypto/ncrypto.h" // for base64 encoder/decoder
+#include "util/helpers/helpers.h"
+#include "config/ActiveSettings.h"
+#include "Cafe/CafeSystem.h"
+#include
+#include
+#include
+
+namespace nn
+{
+ namespace olv
+ {
+ std::mutex g_offlineDBMutex;
+ bool g_offlineDBInitialized = false;
+ ZArchiveReader* g_offlineDBArchive{nullptr};
+
+ void OfflineDB_LazyInit()
+ {
+ std::scoped_lock _l(g_offlineDBMutex);
+ if(g_offlineDBInitialized)
+ return;
+ // open archive
+ g_offlineDBArchive = ZArchiveReader::OpenFromFile(ActiveSettings::GetUserDataPath("resources/miiverse/OfflineDB.zar"));
+ if(!g_offlineDBArchive)
+ cemuLog_log(LogType::Force, "Failed to open resources/miiverse/OfflineDB.zar. Miiverse posts will not be available");
+ g_offlineDBInitialized = true;
+ }
+
+ void OfflineDB_Shutdown()
+ {
+ std::scoped_lock _l(g_offlineDBMutex);
+ if(!g_offlineDBInitialized)
+ return;
+ delete g_offlineDBArchive;
+ g_offlineDBInitialized = false;
+ }
+
+ bool CheckForOfflineDBFile(const char* filePath, uint32* fileSize)
+ {
+ if(!g_offlineDBArchive)
+ return false;
+ ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath);
+ if (!g_offlineDBArchive->IsFile(fileHandle))
+ return false;
+ if(fileSize)
+ *fileSize = g_offlineDBArchive->GetFileSize(fileHandle);
+ return true;
+ }
+
+ bool LoadOfflineDBFile(const char* filePath, std::vector& fileData)
+ {
+ fileData.clear();
+ if(!g_offlineDBArchive)
+ return false;
+ ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath);
+ if (!g_offlineDBArchive->IsFile(fileHandle))
+ return false;
+ fileData.resize(g_offlineDBArchive->GetFileSize(fileHandle));
+ g_offlineDBArchive->ReadFromFile(fileHandle, 0, fileData.size(), fileData.data());
+ return true;
+ }
+
+ void TryLoadCompressedMemoImage(DownloadedPostData& downloadedPostData)
+ {
+ const unsigned char tgaHeader_320x120_32BPP[] = {0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x1,0x78,0x0,0x20,0x8};
+ std::string memoImageFilename = fmt::format("memo/{}", (char*)downloadedPostData.downloadedDataBase.postId);
+ std::vector bitmaskCompressedImg;
+ if (!LoadOfflineDBFile(memoImageFilename.c_str(), bitmaskCompressedImg))
+ return;
+ if (bitmaskCompressedImg.size() != (320*120)/8)
+ return;
+ std::vector decompressedImage;
+ decompressedImage.resize(sizeof(tgaHeader_320x120_32BPP) + 320 * 120 * 4);
+ memcpy(decompressedImage.data(), tgaHeader_320x120_32BPP, sizeof(tgaHeader_320x120_32BPP));
+ uint8* pOut = decompressedImage.data() + sizeof(tgaHeader_320x120_32BPP);
+ for(int i=0; i<320*120; i++)
+ {
+ bool isWhite = (bitmaskCompressedImg[i/8] & (1 << (i%8))) != 0;
+ if(isWhite)
+ {
+ pOut[0] = pOut[1] = pOut[2] = pOut[3] = 0xFF;
+ }
+ else
+ {
+ pOut[0] = pOut[1] = pOut[2] = 0;
+ pOut[3] = 0xFF;
+ }
+ pOut += 4;
+ }
+ // store compressed image
+ uLongf compressedDestLen = 40960;
+ int r = compress((uint8*)downloadedPostData.downloadedDataBase.compressedMemoBody, &compressedDestLen, decompressedImage.data(), decompressedImage.size());
+ if( r != Z_OK)
+ return;
+ downloadedPostData.downloadedDataBase.compressedMemoBodySize = compressedDestLen;
+ downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO);
+ }
+
+ void CheckForExternalImage(DownloadedPostData& downloadedPostData)
+ {
+ std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)downloadedPostData.downloadedDataBase.postId);
+ uint32 fileSize;
+ if (!CheckForOfflineDBFile(externalImageFilename.c_str(), &fileSize))
+ return;
+ strcpy((char*)downloadedPostData.downloadedDataBase.externalImageDataUrl, externalImageFilename.c_str());
+ downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE);
+ downloadedPostData.downloadedDataBase.externalImageDataSize = fileSize;
+ }
+
+ nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param)
+ {
+ scope_exit _se([&](){coreinit::OSSignalEvent(event);});
+
+ uint64 titleId = CafeSystem::GetForegroundTitleId();
+
+ memset(downloadedTopicData, 0, sizeof(DownloadedTopicData));
+ memset(downloadedPostData, 0, sizeof(DownloadedPostData) * maxCount);
+ *postCountOut = 0;
+
+ const char* postXmlFilename = nullptr;
+ if(titleId == 0x0005000010143400 || titleId == 0x0005000010143500 || titleId == 0x0005000010143600)
+ postXmlFilename = "PostList_WindWakerHD.xml";
+
+ if (!postXmlFilename)
+ return OLV_RESULT_SUCCESS;
+
+ // load post XML
+ std::vector xmlData;
+ if (!LoadOfflineDBFile(postXmlFilename, xmlData))
+ return OLV_RESULT_SUCCESS;
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_buffer(xmlData.data(), xmlData.size());
+ if (!result)
+ return OLV_RESULT_SUCCESS;
+ // collect list of all post xml nodes
+ std::vector postXmlNodes;
+ for (pugi::xml_node postNode = doc.child("posts").child("post"); postNode; postNode = postNode.next_sibling("post"))
+ postXmlNodes.push_back(postNode);
+
+ // randomly select up to maxCount posts
+ srand(GetTickCount());
+ uint32 postCount = 0;
+ while(!postXmlNodes.empty() && postCount < maxCount)
+ {
+ uint32 index = rand() % postXmlNodes.size();
+ pugi::xml_node& postNode = postXmlNodes[index];
+
+ auto& addedPost = downloadedPostData[postCount];
+ memset(&addedPost, 0, sizeof(DownloadedPostData));
+ if (!ParseXML_DownloadedPostData(addedPost, postNode) )
+ continue;
+ TryLoadCompressedMemoImage(addedPost);
+ CheckForExternalImage(addedPost);
+ postCount++;
+ // remove from post list
+ postXmlNodes[index] = postXmlNodes.back();
+ postXmlNodes.pop_back();
+ }
+ *postCountOut = postCount;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param)
+ {
+ OfflineDB_LazyInit();
+
+ memset(downloadedTopicData, 0, sizeof(DownloadedTopicData));
+ downloadedTopicData->communityId = param->communityId;
+ *postCountOut = 0;
+
+ if(param->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY))
+ return OLV_RESULT_SUCCESS; // the offlineDB doesn't contain any self posts
+
+ StackAllocator doneEvent;
+ coreinit::OSInitEvent(doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL);
+ auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList, doneEvent.GetPointer(), downloadedTopicData, downloadedPostData, postCountOut, maxCount, param);
+ coreinit::OSWaitEvent(doneEvent);
+ nnResult r = asyncTask.get();
+ return r;
+ }
+
+ nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize)
+ {
+ scope_exit _se([&](){coreinit::OSSignalEvent(event);});
+
+ if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE))
+ return OLV_RESULT_MISSING_DATA;
+
+ // not all games may use JPEG files?
+ std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)_this->postId);
+ std::vector jpegData;
+ if (!LoadOfflineDBFile(externalImageFilename.c_str(), jpegData))
+ return OLV_RESULT_FAILED_REQUEST;
+
+ memcpy(imageDataOut, jpegData.data(), jpegData.size());
+ *imageSizeOut = jpegData.size();
+
+ return OLV_RESULT_SUCCESS;
+ }
+
+ nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize)
+ {
+ StackAllocator doneEvent;
+ coreinit::OSInitEvent(doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL);
+ auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData, doneEvent.GetPointer(), _this, imageDataOut, imageSizeOut, maxSize);
+ coreinit::OSWaitEvent(doneEvent);
+ nnResult r = asyncTask.get();
+ return r;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h
new file mode 100644
index 00000000..ed790479
--- /dev/null
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "Cafe/OS/libs/nn_common.h"
+#include "nn_olv_Common.h"
+
+namespace nn
+{
+ namespace olv
+ {
+ void OfflineDB_Init();
+ void OfflineDB_Shutdown();
+
+ nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param);
+ nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize);
+
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp
new file mode 100644
index 00000000..722e5584
--- /dev/null
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp
@@ -0,0 +1,463 @@
+#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h"
+#include "nn_olv_PostTypes.h"
+#include "nn_olv_OfflineDB.h"
+#include "Cemu/ncrypto/ncrypto.h" // for base64 decoder
+#include "util/helpers/helpers.h"
+#include
+#include
+
+namespace nn
+{
+ namespace olv
+ {
+ bool ParseXml_DownloadedDataBase(DownloadedDataBase& obj, pugi::xml_node& xmlNode)
+ {
+ // todo:
+ // painting with url?
+
+ pugi::xml_node tokenNode;
+ if(tokenNode = xmlNode.child("body"); tokenNode)
+ {
+ obj.bodyTextLength = SetStringUC2(obj.bodyText, tokenNode.child_value(), true);
+ if(obj.bodyTextLength > 0)
+ obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT);
+ }
+ if(tokenNode = xmlNode.child("topic_tag"); tokenNode)
+ {
+ SetStringUC2(obj.topicTag, tokenNode.child_value(), true);
+ }
+ if(tokenNode = xmlNode.child("feeling_id"); tokenNode)
+ {
+ obj.feeling = ConvertString(tokenNode.child_value());
+ if(obj.feeling < 0 || obj.feeling >= 5)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: feeling_id out of range");
+ return false;
+ }
+ }
+ if(tokenNode = xmlNode.child("id"); tokenNode)
+ {
+ std::string_view id_sv = tokenNode.child_value();
+ if(id_sv.size() > 22)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: id too long");
+ return false;
+ }
+ memcpy(obj.postId, id_sv.data(), id_sv.size());
+ obj.postId[id_sv.size()] = '\0';
+ }
+ if(tokenNode = xmlNode.child("is_autopost"); tokenNode)
+ {
+ uint8 isAutopost = ConvertString(tokenNode.child_value());
+ if(isAutopost == 1)
+ obj.SetFlag(DownloadedDataBase::FLAGS::IS_AUTOPOST);
+ else if(isAutopost == 0)
+ obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST);
+ else
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: is_autopost has invalid value");
+ return false;
+ }
+ }
+ if(tokenNode = xmlNode.child("empathy_added"); tokenNode)
+ {
+ if(ConvertString(tokenNode.child_value()) > 0)
+ obj.SetFlag(DownloadedDataBase::FLAGS::HAS_EMPATHY_ADDED);
+ }
+ if(tokenNode = xmlNode.child("is_spoiler"); tokenNode)
+ {
+ if(ConvertString(tokenNode.child_value()) > 0)
+ obj.SetFlag(DownloadedDataBase::FLAGS::IS_SPOILER);
+ }
+ if(tokenNode = xmlNode.child("mii"); tokenNode)
+ {
+ std::vector miiData = NCrypto::base64Decode(tokenNode.child_value());
+ if(miiData.size() != 96)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicData mii data is not valid (incorrect size)");
+ return false;
+ }
+ memcpy(obj.miiData, miiData.data(), miiData.size());
+ obj.SetFlag(DownloadedDataBase::FLAGS::HAS_MII_DATA);
+ }
+ if(tokenNode = xmlNode.child("pid"); tokenNode)
+ {
+ obj.userPid = ConvertString(tokenNode.child_value());
+ }
+ if(tokenNode = xmlNode.child("screen_name"); tokenNode)
+ {
+ SetStringUC2(obj.miiNickname, tokenNode.child_value(), true);
+ }
+ if(tokenNode = xmlNode.child("region_id"); tokenNode)
+ {
+ obj.regionId = ConvertString(tokenNode.child_value());
+ }
+ if(tokenNode = xmlNode.child("platform_id"); tokenNode)
+ {
+ obj.platformId = ConvertString(tokenNode.child_value());
+ }
+ if(tokenNode = xmlNode.child("language_id"); tokenNode)
+ {
+ obj.languageId = ConvertString(tokenNode.child_value());
+ }
+ if(tokenNode = xmlNode.child("country_id"); tokenNode)
+ {
+ obj.countryId = ConvertString(tokenNode.child_value());
+ }
+ if(tokenNode = xmlNode.child("painting"); tokenNode)
+ {
+ if(pugi::xml_node subNode = tokenNode.child("content"); subNode)
+ {
+ std::vector paintingData = NCrypto::base64Decode(subNode.child_value());
+ if (paintingData.size() > 0xA000)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase painting content is too large");
+ return false;
+ }
+ memcpy(obj.compressedMemoBody, paintingData.data(), paintingData.size());
+ obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO);
+ }
+ if(pugi::xml_node subNode = tokenNode.child("size"); subNode)
+ {
+ obj.compressedMemoBodySize = ConvertString(subNode.child_value());
+ }
+ }
+ if(tokenNode = xmlNode.child("app_data"); tokenNode)
+ {
+ std::vector appData = NCrypto::base64Decode(tokenNode.child_value());
+ if (appData.size() > 0x400)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase AppData is too large");
+ return false;
+ }
+ memcpy(obj.appData, appData.data(), appData.size());
+ obj.appDataLength = appData.size();
+ obj.SetFlag(DownloadedDataBase::FLAGS::HAS_APP_DATA);
+ }
+ return true;
+ }
+
+ bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode)
+ {
+ pugi::xml_node tokenNode;
+ if(tokenNode = xmlNode.child("community_id"); tokenNode)
+ obj.communityId = ConvertString(tokenNode.child_value());
+ if(tokenNode = xmlNode.child("empathy_count"); tokenNode)
+ obj.empathyCount = ConvertString(tokenNode.child_value());
+ if(tokenNode = xmlNode.child("reply_count"); tokenNode)
+ obj.commentCount = ConvertString(tokenNode.child_value());
+ return ParseXml_DownloadedDataBase(obj.downloadedDataBase, xmlNode);
+ }
+
+ bool ParseXML_DownloadedSystemPostData(hidden::DownloadedSystemPostData& obj, pugi::xml_node& xmlNode)
+ {
+ pugi::xml_node tokenNode;
+ if(tokenNode = xmlNode.child("title_id"); tokenNode)
+ obj.titleId = ConvertString(tokenNode.child_value());
+ return ParseXML_DownloadedPostData(obj.downloadedPostData, xmlNode);
+ }
+
+ bool ParseXML_DownloadedTopicData(DownloadedTopicData& obj, pugi::xml_node& xmlNode)
+ {
+ pugi::xml_node tokenNode;
+ if(tokenNode = xmlNode.child("community_id"); tokenNode)
+ obj.communityId = ConvertString(tokenNode.child_value());
+ return true;
+ }
+
+ bool Parse_DownloadedSystemTopicData(hidden::DownloadedSystemTopicData& obj, pugi::xml_node& xmlNode)
+ {
+ if(!ParseXML_DownloadedTopicData(obj.downloadedTopicData, xmlNode))
+ return false;
+ pugi::xml_node tokenNode;
+ if(tokenNode = xmlNode.child("name"); tokenNode)
+ {
+ SetStringUC2(obj.titleText, tokenNode.child_value(), true);
+ obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::HAS_TITLE);
+ }
+ if(tokenNode = xmlNode.child("is_recommended"); tokenNode)
+ {
+ uint32 isRecommended = ConvertString(tokenNode.child_value());
+ if(isRecommended != 0)
+ obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::IS_RECOMMENDED);
+ }
+ if(tokenNode = xmlNode.child("title_id"); tokenNode)
+ {
+ obj.titleId = ConvertString(tokenNode.child_value());
+ }
+ if(tokenNode = xmlNode.child("title_ids"); tokenNode)
+ {
+ cemu_assert_unimplemented();
+ }
+ if(tokenNode = xmlNode.child("icon"); tokenNode)
+ {
+ std::vector iconData = NCrypto::base64Decode(tokenNode.child_value());
+ if(iconData.size() > sizeof(obj.iconData))
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicData icon data is not valid");
+ return false;
+ }
+ obj.iconDataSize = iconData.size();
+ memcpy(obj.iconData, iconData.data(), iconData.size());
+ obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::HAS_ICON_DATA);
+ }
+ return true;
+ }
+
+ uint32 GetSystemTopicDataListFromRawData(hidden::DownloadedSystemTopicDataList* downloadedSystemTopicDataList, hidden::DownloadedSystemPostData* downloadedSystemPostData, uint32be* postCountOut, uint32 postCountMax, void* xmlData, uint32 xmlDataSize)
+ {
+ // copy xmlData into a temporary buffer since load_buffer_inplace will modify it
+ std::vector buffer;
+ buffer.resize(xmlDataSize);
+ memcpy(buffer.data(), xmlData, xmlDataSize);
+ pugi::xml_document doc;
+ if (!doc.load_buffer_inplace(buffer.data(), xmlDataSize, pugi::parse_default, pugi::xml_encoding::encoding_utf8))
+ return -1;
+
+ memset(downloadedSystemTopicDataList, 0, sizeof(hidden::DownloadedSystemTopicDataList));
+ downloadedSystemTopicDataList->topicDataNum = 0;
+
+ cemu_assert_debug(doc.child("result").child("topics"));
+
+ size_t postCount = 0;
+
+ // parse topics
+ for (pugi::xml_node topicsChildNode : doc.child("result").child("topics").children())
+ {
+ const char* name = topicsChildNode.name();
+ cemuLog_logDebug(LogType::Force, "topicsChildNode.name() = {}", name);
+ if (strcmp(topicsChildNode.name(), "topic"))
+ continue;
+ // parse topic
+ if(downloadedSystemTopicDataList->topicDataNum > 10)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicDataList exceeded maximum topic count (10)");
+ return false;
+ }
+ auto& topicEntry = downloadedSystemTopicDataList->topicData[downloadedSystemTopicDataList->topicDataNum];
+ memset(&topicEntry, 0, sizeof(hidden::DownloadedSystemTopicDataList::DownloadedSystemTopicWrapped));
+ Parse_DownloadedSystemTopicData(topicEntry.downloadedSystemTopicData, topicsChildNode);
+ downloadedSystemTopicDataList->topicDataNum = downloadedSystemTopicDataList->topicDataNum + 1;
+
+ topicEntry.postDataNum = 0;
+ // parse all posts within the current topic
+ for (pugi::xml_node personNode : topicsChildNode.child("people").children("person"))
+ {
+ for (pugi::xml_node postNode : personNode.child("posts").children("post"))
+ {
+ if(postCount >= postCountMax)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] GetSystemTopicDataListFromRawData exceeded maximum post count");
+ return false;
+ }
+ auto& postEntry = downloadedSystemPostData[postCount];
+ memset(&postEntry, 0, sizeof(hidden::DownloadedSystemPostData));
+ bool r = ParseXML_DownloadedSystemPostData(postEntry, postNode);
+ if(!r)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemPostData parsing failed");
+ return false;
+ }
+ postCount++;
+ // add post to topic
+ if(topicEntry.postDataNum >= hidden::DownloadedSystemTopicDataList::MAX_POSTS_PER_TOPIC)
+ {
+ cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicDataList has too many posts for a single topic (up to {})", hidden::DownloadedSystemTopicDataList::MAX_POSTS_PER_TOPIC);
+ return false;
+ }
+ topicEntry.postDataList[topicEntry.postDataNum] = &postEntry;
+ topicEntry.postDataNum = topicEntry.postDataNum + 1;
+ }
+ }
+ }
+ *postCountOut = postCount;
+ return 0;
+ }
+
+ nnResult DownloadedDataBase::DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize)
+ {
+ if(g_IsOfflineDBMode)
+ return OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(_this, imageDataOut, imageSizeOut, maxSize);
+
+ if(!g_IsOnlineMode)
+ return OLV_RESULT_OFFLINE_MODE_REQUEST;
+ if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE))
+ return OLV_RESULT_MISSING_DATA;
+
+ cemuLog_logDebug(LogType::Force, "DownloadedDataBase::DownloadExternalImageData not implemented");
+ return OLV_RESULT_FAILED_REQUEST; // placeholder error
+ }
+
+ nnResult DownloadPostDataListParam::GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize)
+ {
+ if(!g_IsOnlineMode)
+ return OLV_RESULT_OFFLINE_MODE_REQUEST;
+ //if(_this->communityId == 0)
+ // cemuLog_log(LogType::Force, "DownloadPostDataListParam::GetRawDataUrl called with invalid communityId");
+
+ // get base url
+ std::string baseUrl;
+ baseUrl.append(g_DiscoveryResults.apiEndpoint);
+ //baseUrl.append(fmt::format("/v1/communities/{}/posts", (uint32)_this->communityId));
+ cemu_assert_debug(_this->communityId == 0);
+ baseUrl.append(fmt::format("/v1/posts.search", (uint32)_this->communityId));
+
+ // "v1/posts.search"
+
+ // build parameter string
+ std::string params;
+
+ // this function behaves differently for the Wii U menu? Where it can lookup posts by titleId?
+ if(_this->titleId != 0)
+ {
+ cemu_assert_unimplemented(); // Wii U menu mode
+ }
+
+ // todo: Generic parameters. Which includes: language_id, limit, type=text/memo
+
+ // handle postIds
+ for(size_t i=0; i<_this->MAX_NUM_POST_ID; i++)
+ {
+ if(_this->searchPostId[i].str[0] == '\0')
+ continue;
+ cemu_assert_unimplemented(); // todo
+ // todo - postId parameter
+ // handle filters
+ if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_MII))
+ params.append("&with_mii=1");
+ if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_EMPATHY))
+ params.append("&with_empathy_added=1");
+ if(_this->bodyTextMaxLength != 0)
+ params.append(fmt::format("&max_body_length={}", _this->bodyTextMaxLength));
+ }
+
+ if(_this->titleId != 0)
+ params.append(fmt::format("&title_id={}", (uint64)_this->titleId));
+
+ if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FRIENDS_ONLY))
+ params.append("&by=friend");
+ if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FOLLOWERS_ONLY))
+ params.append("&by=followings");
+ if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY))
+ params.append("&by=self");
+
+ if(!params.empty())
+ params[0] = '?'; // replace the leading ampersand
+
+ baseUrl.append(params);
+ if(baseUrl.size()+1 > urlMaxSize)
+ return OLV_RESULT_NOT_ENOUGH_SIZE;
+ strncpy(urlOut, baseUrl.c_str(), urlMaxSize);
+ return OLV_RESULT_SUCCESS;
+ }
+
+ nnResult DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param)
+ {
+ if(g_IsOfflineDBMode)
+ return OfflineDB_DownloadPostDataListParam_DownloadPostDataList(downloadedTopicData, downloadedPostData, postCountOut, maxCount, param);
+ memset(downloadedTopicData, 0, sizeof(DownloadedTopicData));
+ downloadedTopicData->communityId = param->communityId;
+ *postCountOut = 0;
+
+ char urlBuffer[2048];
+ if (NN_RESULT_IS_FAILURE(DownloadPostDataListParam::GetRawDataUrl(param, urlBuffer, sizeof(urlBuffer))))
+ return OLV_RESULT_INVALID_PARAMETER;
+
+ /*
+ CurlRequestHelper req;
+ req.initate(urlBuffer, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE);
+ InitializeOliveRequest(req);
+ bool reqResult = req.submitRequest();
+ if (!reqResult)
+ {
+ long httpCode = 0;
+ curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode);
+ cemuLog_log(LogType::Force, "Failed request: {} ({})", urlBuffer, httpCode);
+ if (!(httpCode >= 400))
+ return OLV_RESULT_FAILED_REQUEST;
+ }
+ pugi::xml_document doc;
+ if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size()))
+ {
+ cemuLog_log(LogType::Force, fmt::format("Invalid XML in community download response"));
+ return OLV_RESULT_INVALID_XML;
+ }
+ */
+
+ *postCountOut = 0;
+
+ return OLV_RESULT_SUCCESS;
+ }
+
+ void loadOlivePostAndTopicTypes()
+ {
+ cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::None);
+
+ // DownloadedDataBase getters
+ cafeExportRegisterFunc(DownloadedDataBase::TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetUserPid, "nn_olv", "GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetPostDate, "nn_olv", "GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetFeeling, "nn_olv", "GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetRegionId, "nn_olv", "GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetPlatformId, "nn_olv", "GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetLanguageId, "nn_olv", "GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetCountryId, "nn_olv", "GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetExternalUrl, "nn_olv", "GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetMiiData1, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetMiiNickname, "nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetBodyText, "nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetBodyMemo, "nn_olv", "GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetTopicTag, "nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::DownloadExternalImageData, "nn_olv", "DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", LogType::None);
+ cafeExportRegisterFunc(DownloadedDataBase::GetExternalImageDataSize, "nn_olv", "GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
+
+ // DownloadedPostData getters
+ cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedPostData::GetEmpathyCount, "nn_olv", "GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedPostData::GetCommentCount, "nn_olv", "GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None);
+ cafeExportRegisterFunc(DownloadedPostData::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None);
+
+ // DownloadedSystemPostData getters
+ cafeExportRegisterFunc(hidden::DownloadedSystemPostData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv", LogType::None);
+
+ // DownloadedTopicData getters
+ cafeExportRegisterFunc(DownloadedTopicData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv", LogType::None);
+
+ // DownloadedSystemTopicData getters
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::TestFlags, "nn_olv", "TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIdNum, "nn_olv", "GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleText, "nn_olv", "GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIconData, "nn_olv", "GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi", LogType::None);
+
+ // DownloadedSystemTopicDataList getters
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicDataNum, "nn_olv", "GetDownloadedSystemTopicDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFv", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostDataNum, "nn_olv", "GetDownloadedSystemPostDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None);
+ cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::None);
+
+ // DownloadPostDataListParam constructor and getters
+ cafeExportRegisterFunc(DownloadPostDataListParam::Construct, "nn_olv", "__ct__Q3_2nn3olv25DownloadPostDataListParamFv", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetLanguageId, "nn_olv", "SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKey, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKeySingle, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchPid, "nn_olv", "SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetPostId, "nn_olv", "SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDate, "nn_olv", "SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDataMaxNum, "nn_olv", "SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataListParam::SetBodyTextMaxLength, "nn_olv", "SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None);
+
+ // URL and downloading functions
+ cafeExportRegisterFunc(DownloadPostDataListParam::GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi", LogType::None);
+ cafeExportRegisterFunc(DownloadPostDataList, "nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", LogType::None);
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h
new file mode 100644
index 00000000..e6078a7a
--- /dev/null
+++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h
@@ -0,0 +1,612 @@
+#pragma once
+#include
+#include "nn_olv_Common.h"
+
+namespace nn
+{
+ namespace olv
+ {
+ struct DownloadedDataBase
+ {
+ enum class FLAGS : uint32
+ {
+ HAS_BODY_TEXT = 0x01,
+ HAS_BODY_MEMO = 0x02,
+ HAS_EXTERNAL_IMAGE = 0x04,
+ HAS_EXTERNAL_BINARY_DATA = 0x08,
+ HAS_MII_DATA = 0x10,
+ HAS_EXTERNAL_URL = 0x20,
+ HAS_APP_DATA = 0x40,
+ HAS_EMPATHY_ADDED = 0x80,
+ IS_AUTOPOST = 0x100,
+ IS_SPOILER = 0x200,
+ IS_NOT_AUTOPOST = 0x400, // autopost flag was explicitly set to false
+ };
+
+ void Reset()
+ {
+ memset(this, 0, sizeof(DownloadedDataBase));
+ }
+
+ void SetFlag(FLAGS flag)
+ {
+ flags = (FLAGS)((uint32)flags.value() | (uint32)flag);
+ }
+
+ betype flags;
+ uint32be userPid;
+ uint8be postId[32]; // string, up to 22 characters but the buffer is 32 bytes
+ uint64be postDate;
+ sint8be feeling;
+ uint8be _padding0031[3];
+ uint32be regionId;
+ uint8be platformId;
+ uint8be languageId;
+ uint8be countryId;
+ uint8be _padding003B;
+ uint16be bodyText[256];
+ uint32be bodyTextLength;
+ uint8be compressedMemoBody[40960];
+ uint32be compressedMemoBodySize;
+ uint16be topicTag[152];
+ uint8be appData[1024];
+ uint32be appDataLength;
+ uint8be externalBinaryUrl[256];
+ uint32be externalBinaryDataSize;
+ uint8be externalImageDataUrl[256];
+ uint32be externalImageDataSize;
+ uint8be externalURL[256];
+ uint8be miiData[96];
+ uint16be miiNickname[16];
+ uint32be _paddingAB00[1344];
+ uint32be uknC000_someVTableMaybe;
+ uint32be uknC004;
+
+ // getters
+ // TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi
+ static bool TestFlags(DownloadedDataBase* _this, DownloadedDataBase::FLAGS flag)
+ {
+ return HAS_FLAG((uint32)_this->flags.value(), (uint32)flag);
+ }
+
+ // GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetUserPid(DownloadedDataBase* _this)
+ {
+ return _this->userPid;
+ }
+
+ // GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint64 GetPostDate(DownloadedDataBase* _this)
+ {
+ return _this->postDate;
+ }
+
+ // GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetFeeling(DownloadedDataBase* _this)
+ {
+ if(_this->feeling >= 6)
+ return 0;
+ return _this->feeling;
+ }
+
+ // GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetRegionId(DownloadedDataBase* _this)
+ {
+ return _this->regionId;
+ }
+
+ // GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetPlatformId(DownloadedDataBase* _this)
+ {
+ return _this->platformId;
+ }
+
+ // GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetLanguageId(DownloadedDataBase* _this)
+ {
+ return _this->languageId;
+ }
+
+ // GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetCountryId(DownloadedDataBase* _this)
+ {
+ return _this->countryId;
+ }
+
+ // GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint8be* GetExternalUrl(DownloadedDataBase* _this)
+ {
+ if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_URL))
+ return nullptr;
+ return _this->externalURL;
+ }
+
+ // GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData
+ static nnResult GetMiiData1(DownloadedDataBase* _this, void* miiDataOut)
+ {
+ if (!TestFlags(_this, FLAGS::HAS_MII_DATA))
+ return OLV_RESULT_MISSING_DATA;
+ if (!miiDataOut)
+ return OLV_RESULT_INVALID_PTR;
+ memcpy(miiDataOut, _this->miiData, 96);
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint8be* GetMiiData2(DownloadedDataBase* _this)
+ {
+ if (!TestFlags(_this, FLAGS::HAS_MII_DATA))
+ return nullptr;
+ return _this->miiData;
+ }
+
+ // GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint16be* GetMiiNickname(DownloadedDataBase* _this)
+ {
+ if (_this->miiNickname[0] == 0)
+ return nullptr;
+ return _this->miiNickname;
+ }
+
+ // GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi
+ static nnResult GetBodyText(DownloadedDataBase* _this, uint16be* bodyTextOut, uint32 maxLength)
+ {
+ if (!bodyTextOut)
+ return OLV_RESULT_INVALID_PTR;
+ if (maxLength == 0)
+ return OLV_RESULT_NOT_ENOUGH_SIZE;
+ if (!TestFlags(_this, FLAGS::HAS_BODY_TEXT))
+ return OLV_RESULT_MISSING_DATA;
+ memset(bodyTextOut, 0, maxLength * sizeof(uint16));
+ uint32 outputLength = std::min(_this->bodyTextLength, maxLength);
+ olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, outputLength);
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi
+ static nnResult GetBodyMemo(DownloadedDataBase* _this, uint8be* bodyMemoOut, uint32* bodyMemoSizeOut, uint32 maxSize)
+ {
+ if (!bodyMemoOut)
+ return OLV_RESULT_INVALID_PTR;
+ if (maxSize < 0x2582C)
+ return OLV_RESULT_NOT_ENOUGH_SIZE;
+ if (!TestFlags(_this, FLAGS::HAS_BODY_MEMO))
+ return OLV_RESULT_MISSING_DATA;
+ // uncompress TGA
+ uLongf decompressedSize = maxSize;
+ if (uncompress((uint8*)bodyMemoOut, &decompressedSize, (uint8*)_this->compressedMemoBody, _this->compressedMemoBodySize) != Z_OK)
+ {
+ cemuLog_log(LogType::Force, "DownloadedSystemTopicData::GetTitleIconData: uncompress failed");
+ return OLV_RESULT_INVALID_TEXT_FIELD; // status
+ }
+ if(bodyMemoSizeOut)
+ *bodyMemoSizeOut = decompressedSize;
+ // todo - verify TGA header
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint16be* GetTopicTag(DownloadedDataBase* _this)
+ {
+ return _this->topicTag;
+ }
+
+ // GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi
+ static nnResult GetAppData(DownloadedDataBase* _this, uint8be* appDataOut, uint32* appDataSizeOut, uint32 maxSize)
+ {
+ if (!appDataOut)
+ return OLV_RESULT_INVALID_PTR;
+ if (!TestFlags(_this, FLAGS::HAS_APP_DATA))
+ return OLV_RESULT_MISSING_DATA;
+ uint32 outputSize = std::min(maxSize, _this->appDataLength);
+ memcpy(appDataOut, _this->appData, outputSize);
+ if(appDataSizeOut)
+ *appDataSizeOut = outputSize;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetAppDataSize(DownloadedDataBase* _this)
+ {
+ return _this->appDataLength;
+ }
+
+ // GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint8be* GetPostId(DownloadedDataBase* _this)
+ {
+ return _this->postId;
+ }
+
+ // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi
+ static nnResult DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize);
+
+ // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv
+ static uint32 GetExternalImageDataSize(DownloadedDataBase* _this)
+ {
+ if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE))
+ return 0;
+ return _this->externalImageDataSize;
+ }
+
+ // todo:
+ // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi (implement downloading)
+ // DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi
+ // GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv
+ };
+
+ static_assert(sizeof(DownloadedDataBase) == 0xC008);
+
+ struct DownloadedPostData
+ {
+ DownloadedDataBase downloadedDataBase;
+ uint32be communityId;
+ uint32be empathyCount;
+ uint32be commentCount;
+ uint32be paddingC014[125]; // probably unused?
+
+ // getters
+ // GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv
+ static uint32 GetCommunityId(DownloadedPostData* _this)
+ {
+ return _this->communityId;
+ }
+
+ // GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv
+ static uint32 GetEmpathyCount(DownloadedPostData* _this)
+ {
+ return _this->empathyCount;
+ }
+
+ // GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv
+ static uint32 GetCommentCount(DownloadedPostData* _this)
+ {
+ return _this->commentCount;
+ }
+
+ // GetPostId__Q3_2nn3olv18DownloadedPostDataCFv
+ static uint8be* GetPostId(DownloadedPostData* _this)
+ {
+ return _this->downloadedDataBase.postId;
+ }
+
+ };
+
+ static_assert(sizeof(DownloadedPostData) == 0xC208);
+
+ struct DownloadedTopicData
+ {
+ enum class FLAGS
+ {
+ IS_RECOMMENDED = 0x01,
+ HAS_TITLE = 0x02,
+ HAS_ICON_DATA = 0x04,
+ };
+ betype flags;
+ uint32be communityId;
+ int ukn[1022];
+
+ void SetFlag(FLAGS flag)
+ {
+ flags = (FLAGS)((uint32)flags.value() | (uint32)flag);
+ }
+
+ // GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv
+ static uint32 GetCommunityId(DownloadedTopicData* _this)
+ {
+ return _this->communityId;
+ }
+ };
+
+ static_assert(sizeof(DownloadedTopicData) == 0x1000);
+
+ namespace hidden
+ {
+ struct DownloadedSystemPostData
+ {
+ DownloadedPostData downloadedPostData;
+ uint64be titleId;
+ uint32be uknC210[124];
+ uint32be uknC400;
+ uint32be uknC404;
+
+ // getters
+ // GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv
+ static uint64 GetTitleId(DownloadedSystemPostData* _this)
+ {
+ return _this->titleId;
+ }
+ };
+
+ static_assert(sizeof(DownloadedSystemPostData) == 0xC408);
+
+ struct DownloadedSystemTopicData
+ {
+ DownloadedTopicData downloadedTopicData;
+ uint64be titleId;
+ uint16be titleText[128];
+ uint8be ukn1108[256];
+ uint8be iconData[0x1002C];
+ uint32be iconDataSize;
+ uint64be titleIds[32];
+ uint32be titleIdsCount;
+ uint32be ukn1133C[1841];
+
+ // implement getters as static methods for compatibility with CafeExportRegisterFunc()
+ // TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi
+ static bool TestFlags(DownloadedSystemTopicData* _this, DownloadedTopicData::FLAGS flag)
+ {
+ return HAS_FLAG((uint32)_this->downloadedTopicData.flags.value(), (uint32)flag);
+ }
+
+ // GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv
+ static uint64 GetTitleId(DownloadedSystemTopicData* _this)
+ {
+ return _this->titleId;
+ }
+
+ // GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv
+ static uint32 GetTitleIdNum(DownloadedSystemTopicData* _this)
+ {
+ return _this->titleIdsCount;
+ }
+
+ // GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi
+ static nnResult GetTitleText(DownloadedSystemTopicData* _this, uint16be* titleTextOut, uint32 maxLength)
+ {
+ if (!TestFlags(_this, DownloadedTopicData::FLAGS::HAS_TITLE))
+ return OLV_RESULT_MISSING_DATA;
+ if (!titleTextOut)
+ return OLV_RESULT_INVALID_PTR;
+ memset(titleTextOut, 0, maxLength * sizeof(uint16be));
+ if (maxLength > 128)
+ maxLength = 128;
+ olv_wstrncpy((char16_t*)titleTextOut, (char16_t*)_this->titleText, maxLength);
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi
+ static nnResult GetTitleIconData(DownloadedSystemTopicData* _this, void* iconDataOut, uint32be* iconSizeOut, uint32 iconDataMaxSize)
+ {
+ if (!TestFlags(_this, DownloadedTopicData::FLAGS::HAS_ICON_DATA))
+ return OLV_RESULT_MISSING_DATA;
+ if (!iconDataOut)
+ return OLV_RESULT_INVALID_PTR;
+ if (iconDataMaxSize < 0x1002C)
+ return OLV_RESULT_NOT_ENOUGH_SIZE;
+ uLongf decompressedSize = iconDataMaxSize;
+ if (uncompress((uint8*)iconDataOut, &decompressedSize, (uint8*)_this->iconData, _this->iconDataSize) != Z_OK)
+ {
+ cemuLog_log(LogType::Force, "DownloadedSystemTopicData::GetTitleIconData: uncompress failed");
+ return OLV_RESULT_INVALID_TEXT_FIELD; // status
+ }
+ *iconSizeOut = decompressedSize;
+ // todo - check for TGA
+ return OLV_RESULT_SUCCESS;
+ }
+ };
+
+ static_assert(sizeof(DownloadedSystemTopicData) == 0x13000);
+
+ struct DownloadedSystemTopicDataList
+ {
+ static constexpr size_t MAX_TOPIC_COUNT = 10;
+ static constexpr size_t MAX_POSTS_PER_TOPIC = 300;
+
+ // 0x134B8 sized wrapper of DownloadedSystemTopicData
+ struct DownloadedSystemTopicWrapped
+ {
+ DownloadedSystemTopicData downloadedSystemTopicData;
+ uint32be postDataNum;
+ MEMPTR postDataList[MAX_POSTS_PER_TOPIC];
+ uint32 uknPadding;
+ };
+ static_assert(offsetof(DownloadedSystemTopicWrapped, postDataNum) == 0x13000);
+ static_assert(sizeof(DownloadedSystemTopicWrapped) == 0x134B8);
+
+ static uint32 GetDownloadedSystemTopicDataNum(DownloadedSystemTopicDataList* _this)
+ {
+ return _this->topicDataNum;
+ };
+
+ static uint32 GetDownloadedSystemPostDataNum(DownloadedSystemTopicDataList* _this, uint32 topicIndex)
+ {
+ if(topicIndex >= MAX_TOPIC_COUNT)
+ return 0;
+ return _this->topicData[topicIndex].postDataNum;
+ };
+
+ static DownloadedSystemTopicData* GetDownloadedSystemTopicData(DownloadedSystemTopicDataList* _this, uint32 topicIndex)
+ {
+ if(topicIndex >= MAX_TOPIC_COUNT)
+ return nullptr;
+ return &_this->topicData[topicIndex].downloadedSystemTopicData;
+ };
+
+ static DownloadedSystemPostData* GetDownloadedSystemPostData(DownloadedSystemTopicDataList* _this, sint32 topicIndex, sint32 postIndex)
+ {
+ if (topicIndex >= MAX_TOPIC_COUNT || postIndex >= MAX_POSTS_PER_TOPIC)
+ return nullptr;
+ return _this->topicData[topicIndex].postDataList[postIndex];
+ }
+
+ // member variables
+ uint32be topicDataNum;
+ uint32be ukn4;
+ DownloadedSystemTopicWrapped topicData[MAX_TOPIC_COUNT];
+ uint32be uknC0F38[50];
+ };
+
+ static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000);
+ }
+
+
+ struct DownloadPostDataListParam
+ {
+ static constexpr size_t MAX_NUM_SEARCH_PID = 12;
+ static constexpr size_t MAX_NUM_SEARCH_KEY = 5;
+ static constexpr size_t MAX_NUM_POST_ID = 20;
+
+ enum class FLAGS
+ {
+ FRIENDS_ONLY = 0x01, // friends only
+ FOLLOWERS_ONLY = 0x02, // followers only
+ SELF_ONLY = 0x04, // self only
+ ONLY_TYPE_TEXT = 0x08,
+ ONLY_TYPE_MEMO = 0x10,
+ UKN_20 = 0x20,
+ WITH_MII = 0x40, // with mii
+ WITH_EMPATHY = 0x80, // with yeahs added
+ UKN_100 = 0x100,
+ UKN_200 = 0x200, // "is_delay" parameter
+ UKN_400 = 0x400, // "is_hot" parameter
+
+
+ };
+
+ struct SearchKey
+ {
+ uint16be str[152];
+ };
+
+ struct PostId
+ {
+ char str[32];
+ };
+
+ betype flags;
+ uint32be communityId;
+ uint32be searchPid[MAX_NUM_SEARCH_PID];
+ uint8 languageId;
+ uint8 hasLanguageId_039;
+ uint8 padding03A[2];
+ uint32be postDataMaxNum;
+ SearchKey searchKeyArray[MAX_NUM_SEARCH_KEY];
+ PostId searchPostId[MAX_NUM_POST_ID];
+ uint64be postDate; // OSTime?
+ uint64be titleId; // only used by System posts?
+ uint32be bodyTextMaxLength;
+ uint8 padding8C4[1852];
+
+ bool _HasFlag(FLAGS flag)
+ {
+ return ((uint32)flags.value() & (uint32)flag) != 0;
+ }
+
+ void _SetFlags(FLAGS flag)
+ {
+ flags = (FLAGS)((uint32)flags.value() | (uint32)flag);
+ }
+
+ // constructor and getters
+ // __ct__Q3_2nn3olv25DownloadPostDataListParamFv
+ static DownloadPostDataListParam* Construct(DownloadPostDataListParam* _this)
+ {
+ memset(_this, 0, sizeof(DownloadPostDataListParam));
+ return _this;
+ }
+
+ // SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi
+ static nnResult SetFlags(DownloadPostDataListParam* _this, FLAGS flags)
+ {
+ // todo - verify flag combos
+ _this->flags = flags;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc
+ static nnResult SetLanguageId(DownloadPostDataListParam* _this, uint8 languageId)
+ {
+ _this->languageId = languageId;
+ _this->hasLanguageId_039 = 1;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi
+ static nnResult SetCommunityId(DownloadPostDataListParam* _this, uint32 communityId)
+ {
+ _this->communityId = communityId;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc
+ static nnResult SetSearchKey(DownloadPostDataListParam* _this, const uint16be* searchKey, uint8 searchKeyIndex)
+ {
+ if (searchKeyIndex >= MAX_NUM_SEARCH_KEY)
+ return OLV_RESULT_INVALID_PARAMETER;
+ memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey));
+ if(olv_wstrnlen((const char16_t*)searchKey, 152) > 50)
+ {
+ cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetSearchKey: searchKey is too long\n");
+ return OLV_RESULT_INVALID_PARAMETER;
+ }
+ SetStringUC2(_this->searchKeyArray[searchKeyIndex].str, searchKey);
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw
+ static nnResult SetSearchKeySingle(DownloadPostDataListParam* _this, const uint16be* searchKey)
+ {
+ return SetSearchKey(_this, searchKey, 0);
+ }
+
+ // SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi
+ static nnResult SetSearchPid(DownloadPostDataListParam* _this, uint32 searchPid)
+ {
+ if(_this->_HasFlag(FLAGS::FRIENDS_ONLY) || _this->_HasFlag(FLAGS::FOLLOWERS_ONLY) || _this->_HasFlag(FLAGS::SELF_ONLY))
+ return OLV_RESULT_INVALID_PARAMETER;
+ _this->searchPid[0] = searchPid;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi
+ static nnResult SetPostId(DownloadPostDataListParam* _this, const char* postId, uint32 postIdIndex)
+ {
+ if (postIdIndex >= MAX_NUM_POST_ID)
+ return OLV_RESULT_INVALID_PARAMETER;
+ memset(&_this->searchPostId[postIdIndex], 0, sizeof(PostId));
+ if (strlen(postId) > 22)
+ {
+ cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetPostId: postId is too long\n");
+ return OLV_RESULT_INVALID_PARAMETER;
+ }
+ strcpy(_this->searchPostId[postIdIndex].str, postId);
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL
+ static nnResult SetPostDate(DownloadPostDataListParam* _this, uint64 postDate)
+ {
+ _this->postDate = postDate;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi
+ static nnResult SetPostDataMaxNum(DownloadPostDataListParam* _this, uint32 postDataMaxNum)
+ {
+ if(postDataMaxNum == 0)
+ return OLV_RESULT_INVALID_PARAMETER;
+ _this->postDataMaxNum = postDataMaxNum;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi
+ static nnResult SetBodyTextMaxLength(DownloadPostDataListParam* _this, uint32 bodyTextMaxLength)
+ {
+ if(bodyTextMaxLength >= 256)
+ return OLV_RESULT_INVALID_PARAMETER;
+ _this->bodyTextMaxLength = bodyTextMaxLength;
+ return OLV_RESULT_SUCCESS;
+ }
+
+ // GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi
+ static nnResult GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize);
+ };
+
+ static_assert(sizeof(DownloadPostDataListParam) == 0x1000);
+
+ // parsing functions
+ bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode);
+
+ void loadOlivePostAndTopicTypes();
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp
index a91e0115..109c00d2 100644
--- a/src/Cafe/OS/libs/nn_save/nn_save.cpp
+++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp
@@ -203,6 +203,11 @@ namespace save
return ConvertACPToSaveStatus(status);
}
+ SAVEStatus SAVEUnmountSaveDir()
+ {
+ return ConvertACPToSaveStatus(acp::ACPUnmountSaveDir());
+ }
+
void _CheckAndMoveLegacySaves()
{
const uint64 titleId = CafeSystem::GetForegroundTitleId();
@@ -1518,6 +1523,8 @@ namespace save
void load()
{
+
+
osLib_addFunction("nn_save", "SAVEInit", export_SAVEInit);
osLib_addFunction("nn_save", "SAVEInitSaveDir", export_SAVEInitSaveDir);
osLib_addFunction("nn_save", "SAVEGetSharedDataTitlePath", export_SAVEGetSharedDataTitlePath);
@@ -1570,5 +1577,15 @@ namespace save
osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync);
osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariationAsync", export_SAVEOpenDirOtherNormalApplicationVariationAsync);
}
+
+ void ResetToDefaultState()
+ {
+ if(g_nn_save->initialized)
+ {
+ SAVEUnmountSaveDir();
+ g_nn_save->initialized = false;
+ }
+ }
+
}
}
diff --git a/src/Cafe/OS/libs/nn_save/nn_save.h b/src/Cafe/OS/libs/nn_save/nn_save.h
index 088fb1b6..5c9b3b52 100644
--- a/src/Cafe/OS/libs/nn_save/nn_save.h
+++ b/src/Cafe/OS/libs/nn_save/nn_save.h
@@ -5,6 +5,7 @@ namespace nn
namespace save
{
void load();
+ void ResetToDefaultState();
bool GetPersistentIdEx(uint8 accountSlot, uint32* persistentId);
}
diff --git a/src/Cafe/OS/libs/nn_spm/nn_spm.cpp b/src/Cafe/OS/libs/nn_spm/nn_spm.cpp
new file mode 100644
index 00000000..911296cd
--- /dev/null
+++ b/src/Cafe/OS/libs/nn_spm/nn_spm.cpp
@@ -0,0 +1,150 @@
+#include "nn_spm.h"
+#include "Cafe/OS/common/OSCommon.h"
+#include "Cafe/OS/libs/nn_common.h"
+
+namespace nn
+{
+ namespace spm
+ {
+
+ struct StorageIndex
+ {
+ void SetInvalid() { idHigh = 0; idLow = 0; }
+ void Set(uint64 id) { idHigh = id >> 32; idLow = id & 0xFFFFFFFF; }
+
+ uint64 Get() const { return ((uint64)idHigh << 32) | (uint64)idLow; }
+
+ uint32be idHigh;
+ uint32be idLow;
+ };
+
+ enum class CemuStorageIndex
+ {
+ MLC = 1,
+ SLC = 2,
+ USB = 3,
+ };
+
+ static_assert(sizeof(StorageIndex) == 8);
+
+ struct VolumeId
+ {
+ char id[16];
+ };
+
+ static_assert(sizeof(VolumeId) == 16);
+
+ enum class StorageType : uint32
+ {
+ RAW,
+ WFS,
+ };
+
+ struct StorageInfo
+ {
+ char mountPath[640]; // For example: /vol/storage_usb01
+ char connectionType[8]; // usb
+ char formatStr[8]; // raw / wfs
+ uint8 ukn[4];
+ betype type;
+ VolumeId volumeId;
+ };
+
+ static_assert(sizeof(StorageInfo) == 680);
+
+ struct StorageListItem
+ {
+ StorageIndex index;
+ uint32be ukn04;
+ betype type;
+ };
+
+ static_assert(sizeof(StorageListItem) == 16);
+
+ sint32 GetDefaultExtendedStorageVolumeId(StorageIndex* storageIndex)
+ {
+ cemuLog_logDebug(LogType::Force, "GetDefaultExtendedStorageVolumeId() - stub");
+ storageIndex->SetInvalid(); // we dont emulate USB storage yet
+ return 0;
+ }
+
+ sint32 GetExtendedStorageIndex(StorageIndex* storageIndex)
+ {
+ cemuLog_logDebug(LogType::Force, "GetExtendedStorageIndex() - stub");
+ storageIndex->SetInvalid(); // we dont emulate USB storage yet
+ return -1; // this fails if there is none?
+ }
+
+ // nn::spm::GetStorageList((nn::spm::StorageListItem *, unsigned int))
+
+ uint32 GetStorageList(StorageListItem* storageList, uint32 maxItems)
+ {
+ cemu_assert(maxItems >= 2);
+ uint32 numItems = 0;
+
+ // This should only return USB storages?
+ // If we return two entries (for SLC and MLC supposedly) then the Wii U menu will complain about two usb storages
+
+// // mlc
+// storageList[numItems].index.Set((uint32)CemuStorageIndex::MLC);
+// storageList[numItems].ukn04 = 0;
+// storageList[numItems].type = StorageType::WFS;
+// numItems++;
+// // slc
+// storageList[numItems].index.Set((uint32)CemuStorageIndex::SLC);
+// storageList[numItems].ukn04 = 0;
+// storageList[numItems].type = StorageType::WFS;
+ numItems++;
+ return numItems;
+ }
+
+ sint32 GetStorageInfo(StorageInfo* storageInfo, StorageIndex* storageIndex)
+ {
+ cemuLog_logDebug(LogType::Force, "GetStorageInfo() - stub");
+ if(storageIndex->Get() == (uint64)CemuStorageIndex::MLC)
+ {
+ cemu_assert_unimplemented();
+ }
+ else if(storageIndex->Get() == (uint64)CemuStorageIndex::SLC)
+ {
+ cemu_assert_unimplemented();
+ }
+ else
+ {
+ cemu_assert_unimplemented();
+ }
+
+ return 0;
+ }
+
+ sint32 VolumeId_Compare(VolumeId* volumeIdThis, VolumeId* volumeIdOther)
+ {
+ auto r = strncmp(volumeIdThis->id, volumeIdOther->id, 16);
+ cemuLog_logDebug(LogType::Force, "VolumeId_Compare(\"{}\", \"{}\")", volumeIdThis->id, volumeIdOther->id);
+ return (sint32)r;
+ }
+
+ sint32 WaitStateUpdated(uint64be* waitState)
+ {
+ // WaitStateUpdated__Q2_2nn3spmFPUL
+ cemuLog_logDebug(LogType::Force, "WaitStateUpdated() called");
+ *waitState = 1;
+ return 0;
+ }
+
+ void load()
+ {
+ cafeExportRegisterFunc(GetDefaultExtendedStorageVolumeId, "nn_spm", "GetDefaultExtendedStorageVolumeId__Q2_2nn3spmFv", LogType::Placeholder);
+ cafeExportRegisterFunc(GetExtendedStorageIndex, "nn_spm", "GetExtendedStorageIndex__Q2_2nn3spmFPQ3_2nn3spm12StorageIndex", LogType::Placeholder);
+ cafeExportRegisterFunc(GetStorageList, "nn_spm", "GetStorageList__Q2_2nn3spmFPQ3_2nn3spm15StorageListItemUi", LogType::Placeholder);
+
+ cafeExportRegisterFunc(GetStorageInfo, "nn_spm", "GetStorageInfo__Q2_2nn3spmFPQ3_2nn3spm11StorageInfoQ3_2nn3spm12StorageIndex", LogType::Placeholder);
+
+
+
+ cafeExportRegisterFunc(VolumeId_Compare, "nn_spm", "Compare__Q3_2nn3spm8VolumeIdCFRCQ3_2nn3spm8VolumeId", LogType::Placeholder);
+
+ cafeExportRegisterFunc(WaitStateUpdated, "nn_spm", "WaitStateUpdated__Q2_2nn3spmFPUL", LogType::Placeholder);
+ }
+ }
+}
diff --git a/src/Cafe/OS/libs/nn_spm/nn_spm.h b/src/Cafe/OS/libs/nn_spm/nn_spm.h
new file mode 100644
index 00000000..27d7bec9
--- /dev/null
+++ b/src/Cafe/OS/libs/nn_spm/nn_spm.h
@@ -0,0 +1,9 @@
+#pragma once
+
+namespace nn
+{
+ namespace spm
+ {
+ void load();
+ }
+}
\ No newline at end of file
diff --git a/src/Cafe/OS/libs/nn_uds/nn_uds.cpp b/src/Cafe/OS/libs/nn_uds/nn_uds.cpp
index b28e4b64..08f67c7d 100644
--- a/src/Cafe/OS/libs/nn_uds/nn_uds.cpp
+++ b/src/Cafe/OS/libs/nn_uds/nn_uds.cpp
@@ -21,4 +21,4 @@ void nnUdsExport___sti___11_uds_Api_cpp_f5d9abb2(PPCInterpreter_t* hCPU)
void nnUds_load()
{
osLib_addFunction("nn_uds", "__sti___11_uds_Api_cpp_f5d9abb2", nnUdsExport___sti___11_uds_Api_cpp_f5d9abb2);
-}
\ No newline at end of file
+}
diff --git a/src/Cafe/OS/libs/snd_core/ax.h b/src/Cafe/OS/libs/snd_core/ax.h
index 9f7a1e1c..d9bbbf18 100644
--- a/src/Cafe/OS/libs/snd_core/ax.h
+++ b/src/Cafe/OS/libs/snd_core/ax.h
@@ -209,6 +209,8 @@ namespace snd_core
};
void AXVPB_Init();
+ void AXResetToDefaultState();
+
sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex);
AXVPB* AXAcquireVoiceEx(uint32 priority, MPTR callbackEx, MPTR userParam);
diff --git a/src/Cafe/OS/libs/snd_core/ax_exports.cpp b/src/Cafe/OS/libs/snd_core/ax_exports.cpp
index e7bb86b2..4ac0c568 100644
--- a/src/Cafe/OS/libs/snd_core/ax_exports.cpp
+++ b/src/Cafe/OS/libs/snd_core/ax_exports.cpp
@@ -8,11 +8,12 @@ namespace snd_core
{
sndGeneric_t sndGeneric;
- void resetToDefaultState()
+ void AXResetToDefaultState()
{
memset(&sndGeneric, 0x00, sizeof(sndGeneric));
resetNumProcessedFrames();
- }
+ AXVBP_Reset();
+ }
bool AXIsInit()
{
@@ -77,14 +78,13 @@ namespace snd_core
void AXQuit()
{
- cemuLog_logDebug(LogType::Force, "AXQuit called from 0x{:08x}", ppcInterpreterCurrentInstance->spr.LR);
- // clean up
- AXResetCallbacks();
- AXVoiceList_ResetFreeVoiceList();
- // todo - should we wait to make sure any active callbacks are finished with execution before we exit AXQuit?
+ AXResetCallbacks();
+ // todo - should we wait to make sure any active callbacks are finished with execution before we exit AXQuit?
+ // request worker thread stop and wait until complete
+ AXIst_StopThread();
+ // clean up subsystems
+ AXVBP_Reset();
sndGeneric.isInitialized = false;
- // request worker thread stop and wait until complete
- AXIst_StopThread();
}
sint32 AXGetMaxVoices()
@@ -500,7 +500,7 @@ namespace snd_core
void loadExports()
{
- resetToDefaultState();
+ AXResetToDefaultState();
loadExportsSndCore1();
loadExportsSndCore2();
@@ -513,7 +513,9 @@ namespace snd_core
void reset()
{
- sndGeneric.isInitialized = false;
+ AXOut_reset();
+ AXResetToDefaultState();
+ sndGeneric.isInitialized = false;
}
}
diff --git a/src/Cafe/OS/libs/snd_core/ax_internal.h b/src/Cafe/OS/libs/snd_core/ax_internal.h
index c2880343..697d7e96 100644
--- a/src/Cafe/OS/libs/snd_core/ax_internal.h
+++ b/src/Cafe/OS/libs/snd_core/ax_internal.h
@@ -194,7 +194,6 @@ namespace snd_core
std::vector& AXVoiceList_GetListByPriority(uint32 priority);
std::vector& AXVoiceList_GetFreeVoices();
- void AXVoiceList_ResetFreeVoiceList();
inline AXVPBInternal_t* GetInternalVoice(const AXVPB* vpb)
{
@@ -206,6 +205,8 @@ namespace snd_core
return (uint32)vpb->index;
}
+ void AXVBP_Reset();
+
// AXIst
void AXIst_InitThread();
OSThread_t* AXIst_GetThread();
diff --git a/src/Cafe/OS/libs/snd_core/ax_ist.cpp b/src/Cafe/OS/libs/snd_core/ax_ist.cpp
index affcb930..30cbdbb1 100644
--- a/src/Cafe/OS/libs/snd_core/ax_ist.cpp
+++ b/src/Cafe/OS/libs/snd_core/ax_ist.cpp
@@ -958,6 +958,7 @@ namespace snd_core
void AXIst_InitThread()
{
+ __AXIstIsProcessingFrame = false;
// create ist message queue
OSInitMessageQueue(__AXIstThreadMsgQueue.GetPtr(), __AXIstThreadMsgArray.GetPtr(), 0x10);
// create thread
diff --git a/src/Cafe/OS/libs/snd_core/ax_voice.cpp b/src/Cafe/OS/libs/snd_core/ax_voice.cpp
index 5bc462c6..8a49b0f4 100644
--- a/src/Cafe/OS/libs/snd_core/ax_voice.cpp
+++ b/src/Cafe/OS/libs/snd_core/ax_voice.cpp
@@ -40,11 +40,6 @@ namespace snd_core
return vpb;
}
- void AXVoiceList_ResetFreeVoiceList()
- {
- __AXFreeVoices.clear();
- }
-
std::vector& AXVoiceList_GetFreeVoices()
{
return __AXFreeVoices;
@@ -80,6 +75,13 @@ namespace snd_core
return __AXVoicesPerPriority[priority];
}
+ void AXVoiceList_Reset()
+ {
+ __AXFreeVoices.clear();
+ for (uint32 i = 0; i < AX_PRIORITY_MAX; i++)
+ __AXVoicesPerPriority[i].clear();
+ }
+
SysAllocator _buffer__AXVPBInternalVoiceArray;
AXVPBInternal_t* __AXVPBInternalVoiceArray;
@@ -445,14 +447,22 @@ namespace snd_core
__AXVoiceListSpinlock.unlock();
}
+ void __AXVPBResetVoices()
+ {
+ __AXVPBInternalVoiceArray = _buffer__AXVPBInternalVoiceArray.GetPtr();
+ __AXVPBInternalVoiceShadowCopyArrayPtr = _buffer__AXVPBInternalVoiceShadowCopyArray.GetPtr();
+ __AXVPBArrayPtr = _buffer__AXVPBArray.GetPtr();
+ __AXVPBItdArrayPtr = _buffer__AXVPBItdArray.GetPtr();
+
+ memset(__AXVPBInternalVoiceShadowCopyArrayPtr, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES);
+ memset(__AXVPBInternalVoiceArray, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES);
+ memset(__AXVPBItdArrayPtr, 0, sizeof(AXVPBItd)*AX_MAX_VOICES);
+ memset(__AXVPBArrayPtr, 0, sizeof(AXVPB)*AX_MAX_VOICES);
+ }
+
void AXVPBInit()
{
- __AXVPBInternalVoiceArray = _buffer__AXVPBInternalVoiceArray.GetPtr();
-
- memset(__AXVPBInternalVoiceShadowCopyArrayPtr, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES);
- memset(__AXVPBInternalVoiceArray, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES);
- memset(__AXVPBItdArrayPtr, 0, sizeof(AXVPBItd)*AX_MAX_VOICES);
- memset(__AXVPBArrayPtr, 0, sizeof(AXVPB)*AX_MAX_VOICES);
+ __AXVPBResetVoices();
for (sint32 i = 0; i < AX_MAX_VOICES; i++)
{
AXVPBItd* itd = __AXVPBItdArrayPtr + i;
@@ -494,12 +504,16 @@ namespace snd_core
void AXVPB_Init()
{
- __AXVPBInternalVoiceShadowCopyArrayPtr = _buffer__AXVPBInternalVoiceShadowCopyArray.GetPtr();
- __AXVPBArrayPtr = _buffer__AXVPBArray.GetPtr();
- __AXVPBItdArrayPtr = _buffer__AXVPBItdArray.GetPtr();
+ __AXVPBResetVoices();
AXVPBInit();
}
+ void AXVBP_Reset()
+ {
+ AXVoiceList_Reset();
+ __AXVPBResetVoices();
+ }
+
sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex)
{
if (device == AX_DEV_TV)
diff --git a/src/Cafe/OS/libs/sysapp/sysapp.cpp b/src/Cafe/OS/libs/sysapp/sysapp.cpp
index 4bf607ca..413d535a 100644
--- a/src/Cafe/OS/libs/sysapp/sysapp.cpp
+++ b/src/Cafe/OS/libs/sysapp/sysapp.cpp
@@ -2,6 +2,7 @@
#include "sysapp.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/OS/libs/coreinit/coreinit_FG.h"
+#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
typedef struct
{
@@ -469,7 +470,6 @@ void sysappExport__SYSGetEShopArgs(PPCInterpreter_t* hCPU)
void sysappExport_SYSGetUPIDFromTitleID(PPCInterpreter_t* hCPU)
{
ppcDefineParamU64(titleId, 0);
- cemuLog_logDebug(LogType::Force, "SYSGetUPIDFromTitleID(0x{:08x}{:08x})", hCPU->gpr[3], hCPU->gpr[4]);
uint32 titleIdHigh = (titleId >> 32);
uint32 titleIdLow = (uint32)(titleId & 0xFFFFFFFF);
if ((titleIdHigh & 0xFFFF0000) != 0x50000)
@@ -541,32 +541,67 @@ void sysappExport_SYSGetVodArgs(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, 1);
}
+struct SysLauncherArgs18
+{
+ uint64be caller_id; // titleId
+ uint64be launch_title; // titleId
+ uint32be mode;
+ uint32be slot_id;
+};
+
+static_assert(sizeof(SysLauncherArgs18) == 0x18);
+
struct SysLauncherArgs28
{
uint32 ukn00;
uint32 ukn04;
uint32 ukn08;
uint32 ukn0C;
- uint32 ukn10; // caller title id? (8 byte)
- uint32 ukn14;
- uint32 ukn18; // launched title id? (8 byte)
- uint32 ukn1C;
- uint32 ukn20; // mode
- uint32 ukn24; // slot
+ // standard args above
+ uint64be caller_id; // titleId
+ uint64be launch_title; // titleId
+ uint32be mode;
+ uint32be slot_id;
};
static_assert(sizeof(SysLauncherArgs28) == 0x28);
-void sysappExport__SYSGetLauncherArgs(PPCInterpreter_t* hCPU)
+uint32 _SYSGetLauncherArgs(void* argsOut)
{
- cemuLog_logDebug(LogType::Force, "_SYSGetLauncherArgs(0x{:08x}) - todo", hCPU->gpr[3]);
+ uint32 sdkVersion = coreinit::__OSGetProcessSDKVersion();
+ if(sdkVersion < 21103)
+ {
+ // old format
+ SysLauncherArgs18* launcherArgs18 = (SysLauncherArgs18*)argsOut;
+ memset(launcherArgs18, 0, sizeof(SysLauncherArgs18));
+ }
+ else
+ {
+ // new format
+ SysLauncherArgs28* launcherArgs28 = (SysLauncherArgs28*)argsOut;
+ memset(launcherArgs28, 0, sizeof(SysLauncherArgs28));
+ }
+ return 0; // return argument is todo
+}
- // todo: Handle OS library version. Older versions used a different struct (only 0x18 bytes?)
- //ppcDefineParamStructPtr(launcherArgs, SysLauncherArgs, 0);
- //memset(launcherArgs, 0, sizeof(SysLauncherArgs));
+struct SysAccountArgs18
+{
+ uint32be ukn00;
+ uint32be ukn04;
+ uint32be ukn08;
+ uint32be ukn0C;
+ // shares part above with Standard arg
+ uint32be slotId; // "slot_id"
+ uint32be mode; // "mode"
+};
+uint32 _SYSGetAccountArgs(SysAccountArgs18* argsOut)
+{
+ memset(argsOut, 0, sizeof(SysAccountArgs18));
- osLib_returnFromFunction(hCPU, 0); // return argument is todo (probably number of args?)
+ // sysDeserializeStandardArguments_t ?
+
+ return 0;
}
void sysappExport_SYSGetStandardResult(PPCInterpreter_t* hCPU)
@@ -574,9 +609,44 @@ void sysappExport_SYSGetStandardResult(PPCInterpreter_t* hCPU)
cemuLog_logDebug(LogType::Force, "SYSGetStandardResult(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]);
memset(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), 0, 4);
+ // r3 = uint32be* output
+ // r4 = pointer to data to parse?
+ // r5 = size to parse?
+
osLib_returnFromFunction(hCPU, 0);
}
+namespace sysapp
+{
+ void SYSClearSysArgs()
+ {
+ cemuLog_logDebug(LogType::Force, "SYSClearSysArgs()");
+ coreinit::__OSClearCopyData();
+ }
+
+ uint32 _SYSLaunchTitleByPathFromLauncher(const char* path, uint32 pathLength)
+ {
+ coreinit::__OSClearCopyData();
+ _SYSAppendCallerInfo();
+ return coreinit::OSLaunchTitleByPathl(path, pathLength, 0);
+ }
+
+ uint32 SYSRelaunchTitle(uint32 argc, MEMPTR* argv)
+ {
+ // calls ACPCheckSelfTitleNotReferAccountLaunch?
+ coreinit::__OSClearCopyData();
+ _SYSAppendCallerInfo();
+ return coreinit::OSRestartGame(argc, argv);
+ }
+
+ void load()
+ {
+ cafeExportRegisterFunc(SYSClearSysArgs, "sysapp", "SYSClearSysArgs", LogType::Placeholder);
+ cafeExportRegisterFunc(_SYSLaunchTitleByPathFromLauncher, "sysapp", "_SYSLaunchTitleByPathFromLauncher", LogType::Placeholder);
+ cafeExportRegisterFunc(SYSRelaunchTitle, "sysapp", "SYSRelaunchTitle", LogType::Placeholder);
+ }
+}
+
// register sysapp functions
void sysapp_load()
{
@@ -592,6 +662,10 @@ void sysapp_load()
osLib_addFunction("sysapp", "SYSGetVodArgs", sysappExport_SYSGetVodArgs);
- osLib_addFunction("sysapp", "_SYSGetLauncherArgs", sysappExport__SYSGetLauncherArgs);
osLib_addFunction("sysapp", "SYSGetStandardResult", sysappExport_SYSGetStandardResult);
+
+ cafeExportRegisterFunc(_SYSGetLauncherArgs, "sysapp", "_SYSGetLauncherArgs", LogType::Placeholder);
+ cafeExportRegisterFunc(_SYSGetAccountArgs, "sysapp", "_SYSGetAccountArgs", LogType::Placeholder);
+
+ sysapp::load();
}
diff --git a/src/Cafe/TitleList/AppType.h b/src/Cafe/TitleList/AppType.h
new file mode 100644
index 00000000..deca7560
--- /dev/null
+++ b/src/Cafe/TitleList/AppType.h
@@ -0,0 +1,23 @@
+#pragma once
+
+enum class APP_TYPE : uint32
+{
+ GAME = 0x80000000,
+ GAME_UPDATE = 0x0800001B,
+ GAME_DLC = 0x0800000E,
+ // data titles
+ VERSION_DATA_TITLE = 0x10000015,
+ DRC_FIRMWARE = 0x10000013,
+ DRC_TEXTURE_ATLAS = 0x1000001A,
+};
+
+// allow direct comparison with uint32
+inline bool operator==(APP_TYPE lhs, uint32 rhs)
+{
+ return static_cast(lhs) == rhs;
+}
+
+inline bool operator==(uint32 lhs, APP_TYPE rhs)
+{
+ return lhs == static_cast(rhs);
+}
diff --git a/src/Cafe/TitleList/BaseInfo.cpp b/src/Cafe/TitleList/BaseInfo.cpp
deleted file mode 100644
index 528ba948..00000000
--- a/src/Cafe/TitleList/BaseInfo.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include "BaseInfo.h"
-
-#include "config/CemuConfig.h"
-#include "Cafe/Filesystem/fsc.h"
-#include "Cafe/Filesystem/FST/FST.h"
-
-sint32 BaseInfo::GetLanguageIndex(std::string_view language)
-{
- if (language == "ja")
- return (sint32)CafeConsoleLanguage::JA;
- else if (language == "en")
- return (sint32)CafeConsoleLanguage::EN;
- else if (language == "fr")
- return (sint32)CafeConsoleLanguage::FR;
- else if (language == "de")
- return (sint32)CafeConsoleLanguage::DE;
- else if (language == "it")
- return (sint32)CafeConsoleLanguage::IT;
- else if (language == "es")
- return (sint32)CafeConsoleLanguage::ES;
- else if (language == "zhs")
- return (sint32)CafeConsoleLanguage::ZH;
- else if (language == "ko")
- return (sint32)CafeConsoleLanguage::KO;
- else if (language == "nl")
- return (sint32)CafeConsoleLanguage::NL;
- else if (language == "pt")
- return (sint32)CafeConsoleLanguage::PT;
- else if (language == "ru")
- return (sint32)CafeConsoleLanguage::RU;
- else if (language == "zht")
- return (sint32)CafeConsoleLanguage::ZH;
- return -1;
-}
-
-
-std::unique_ptr BaseInfo::ReadFSCFile(std::string_view filename, uint32& size) const
-{
- size = 0;
- sint32 fscStatus = 0;
- // load and parse meta.xml
- FSCVirtualFile* file = fsc_open(const_cast(std::string(filename).c_str()), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
- if (file)
- {
- size = fsc_getFileSize(file);
- auto buffer = std::make_unique(size);
- fsc_readFile(file, buffer.get(), size);
- fsc_close(file);
- return buffer;
- }
-
- return nullptr;
-}
-
-std::unique_ptr BaseInfo::ReadVirtualFile(FSTVolume* volume, std::string_view filename, uint32& size) const
-{
- size = 0;
- FSTFileHandle fileHandle;
- if (!volume->OpenFile(filename, fileHandle, true))
- return nullptr;
-
- size = volume->GetFileSize(fileHandle);
- auto buffer = std::make_unique(size);
- volume->ReadFile(fileHandle, 0, size, buffer.get());
-
- return buffer;
-}
-
diff --git a/src/Cafe/TitleList/BaseInfo.h b/src/Cafe/TitleList/BaseInfo.h
deleted file mode 100644
index 27578235..00000000
--- a/src/Cafe/TitleList/BaseInfo.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma once
-
-namespace pugi
-{
- struct xml_parse_result;
- class xml_document;
-}
-
-class BaseInfo
-{
-public:
- enum class GameType
- {
- FSC, // using fsc API
- Directory, // rpx/meta
- Image, // wud/wux
- };
-
- virtual ~BaseInfo() = default;
-
- [[nodiscard]] const fs::path& GetPath() const { return m_type_path; }
- [[nodiscard]] GameType GetGameType() const { return m_type; }
-
-protected:
-
- GameType m_type;
- fs::path m_type_path; // empty / base dir / wud path
-
- virtual void ParseDirectory(const fs::path& filename) = 0;
- virtual bool ParseFile(const fs::path& filename) = 0;
-
-
- [[nodiscard]] std::unique_ptr ReadFSCFile(std::string_view filename, uint32& size) const;
- [[nodiscard]] std::unique_ptr ReadVirtualFile(class FSTVolume* volume, std::string_view filename, uint32& size) const;
-
- [[nodiscard]] static sint32 GetLanguageIndex(std::string_view language);
-};
\ No newline at end of file
diff --git a/src/Cafe/TitleList/GameInfo.h b/src/Cafe/TitleList/GameInfo.h
index c8809a16..d1c557ab 100644
--- a/src/Cafe/TitleList/GameInfo.h
+++ b/src/Cafe/TitleList/GameInfo.h
@@ -20,6 +20,11 @@ public:
return m_base.IsValid(); // at least the base must be valid for this to be a runnable title
}
+ bool IsSystemDataTitle() const
+ {
+ return m_base.IsSystemDataTitle();
+ }
+
void SetBase(const TitleInfo& titleInfo)
{
m_base = titleInfo;
@@ -84,7 +89,7 @@ public:
std::string GetTitleName()
{
cemu_assert_debug(m_base.IsValid());
- return m_base.GetTitleName(); // long name
+ return m_base.GetMetaTitleName(); // long name
}
uint16 GetVersion() const
@@ -94,6 +99,13 @@ public:
return m_base.GetAppTitleVersion();
}
+ uint32 GetSDKVersion() const
+ {
+ if (m_update.IsValid())
+ return m_update.GetAppSDKVersion();
+ return m_base.GetAppSDKVersion();
+ }
+
CafeConsoleRegion GetRegion() const
{
if (m_update.IsValid())
diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp
index 6ffa15c0..8bbb940d 100644
--- a/src/Cafe/TitleList/TitleInfo.cpp
+++ b/src/Cafe/TitleList/TitleInfo.cpp
@@ -127,7 +127,8 @@ TitleInfo::CachedInfo TitleInfo::MakeCacheEntry()
e.subPath = m_subPath;
e.titleId = GetAppTitleId();
e.titleVersion = GetAppTitleVersion();
- e.titleName = GetTitleName();
+ e.sdkVersion = GetAppSDKVersion();
+ e.titleName = GetMetaTitleName();
e.region = GetMetaRegion();
e.group_id = GetAppGroup();
e.app_type = GetAppType();
@@ -427,7 +428,7 @@ void TitleInfo::Unmount(std::string_view virtualPath)
continue;
fsc_unmount(itr.second.c_str(), itr.first);
std::erase(m_mountpoints, itr);
- // if the last mount point got unmounted, delete any open devices
+ // if the last mount point got unmounted, close any open devices
if (m_mountpoints.empty())
{
if (m_wudVolume)
@@ -436,13 +437,12 @@ void TitleInfo::Unmount(std::string_view virtualPath)
delete m_wudVolume;
m_wudVolume = nullptr;
}
- }
- // wua files use reference counting
- if (m_zarchive)
- {
- _ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive);
- if (m_mountpoints.empty())
- m_zarchive = nullptr;
+ if (m_zarchive)
+ {
+ _ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive);
+ if (m_mountpoints.empty())
+ m_zarchive = nullptr;
+ }
}
return;
}
@@ -467,7 +467,7 @@ bool TitleInfo::ParseXmlInfo()
{
cemu_assert(m_isValid);
if (m_hasParsedXmlFiles)
- return m_parsedMetaXml && m_parsedAppXml && m_parsedCosXml;
+ return m_isValid;
m_hasParsedXmlFiles = true;
std::string mountPath = GetUniqueTempMountingPath();
@@ -489,10 +489,16 @@ bool TitleInfo::ParseXmlInfo()
Unmount(mountPath);
- bool hasAnyXml = m_parsedMetaXml || m_parsedAppXml || m_parsedCosXml;
-
- if (!m_parsedMetaXml || !m_parsedAppXml || !m_parsedCosXml)
+ // some system titles dont have a meta.xml file
+ bool allowMissingMetaXml = false;
+ if(m_parsedAppXml && this->IsSystemDataTitle())
{
+ allowMissingMetaXml = true;
+ }
+
+ if ((allowMissingMetaXml == false && !m_parsedMetaXml) || !m_parsedAppXml || !m_parsedCosXml)
+ {
+ bool hasAnyXml = m_parsedMetaXml || m_parsedAppXml || m_parsedCosXml;
if (hasAnyXml)
cemuLog_log(LogType::Force, "Title has missing meta .xml files. Title path: {}", _pathToUtf8(m_fullPath));
delete m_parsedMetaXml;
@@ -531,6 +537,8 @@ bool TitleInfo::ParseAppXml(std::vector& appXmlData)
m_parsedAppXml->app_type = (uint32)std::stoull(child.text().as_string(), nullptr, 16);
else if (name == "group_id")
m_parsedAppXml->group_id = (uint32)std::stoull(child.text().as_string(), nullptr, 16);
+ else if (name == "sdk_version")
+ m_parsedAppXml->sdk_version = (uint32)std::stoull(child.text().as_string(), nullptr, 16);
}
return true;
}
@@ -557,6 +565,17 @@ uint16 TitleInfo::GetAppTitleVersion() const
return 0;
}
+uint32 TitleInfo::GetAppSDKVersion() const
+{
+ cemu_assert_debug(m_isValid);
+ if (m_parsedAppXml)
+ return m_parsedAppXml->sdk_version;
+ if (m_cachedInfo)
+ return m_cachedInfo->sdkVersion;
+ cemu_assert_suspicious();
+ return 0;
+}
+
uint32 TitleInfo::GetAppGroup() const
{
cemu_assert_debug(m_isValid);
@@ -585,7 +604,7 @@ TitleIdParser::TITLE_TYPE TitleInfo::GetTitleType()
return tip.GetType();
}
-std::string TitleInfo::GetTitleName() const
+std::string TitleInfo::GetMetaTitleName() const
{
cemu_assert_debug(m_isValid);
if (m_parsedMetaXml)
@@ -600,7 +619,6 @@ std::string TitleInfo::GetTitleName() const
}
if (m_cachedInfo)
return m_cachedInfo->titleName;
- cemu_assert_suspicious();
return "";
}
@@ -611,7 +629,6 @@ CafeConsoleRegion TitleInfo::GetMetaRegion() const
return m_parsedMetaXml->GetRegion();
if (m_cachedInfo)
return m_cachedInfo->region;
- cemu_assert_suspicious();
return CafeConsoleRegion::JPN;
}
diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h
index 8137ff08..da430adc 100644
--- a/src/Cafe/TitleList/TitleInfo.h
+++ b/src/Cafe/TitleList/TitleInfo.h
@@ -3,6 +3,7 @@
#include "Cafe/Filesystem/fsc.h"
#include "config/CemuConfig.h" // for CafeConsoleRegion. Move to NCrypto?
#include "TitleId.h"
+#include "AppType.h"
#include "ParsedMetaXml.h"
enum class CafeTitleFileType
@@ -22,6 +23,7 @@ struct ParsedAppXml
uint16 title_version;
uint32 app_type;
uint32 group_id;
+ uint32 sdk_version;
};
struct ParsedCosXml
@@ -69,6 +71,7 @@ public:
std::string subPath; // for WUA
uint64 titleId;
uint16 titleVersion;
+ uint32 sdkVersion;
std::string titleName;
CafeConsoleRegion region;
uint32 group_id;
@@ -115,12 +118,21 @@ public:
return m_uid == rhs.m_uid;
}
+ bool IsSystemDataTitle() const
+ {
+ if(!IsValid())
+ return false;
+ uint32 appType = GetAppType();
+ return appType == APP_TYPE::DRC_FIRMWARE || appType == APP_TYPE::DRC_TEXTURE_ATLAS || appType == APP_TYPE::VERSION_DATA_TITLE;
+ }
+
// API which requires parsed meta data or cached info
TitleId GetAppTitleId() const; // from app.xml
uint16 GetAppTitleVersion() const; // from app.xml
+ uint32 GetAppSDKVersion() const; // from app.xml
uint32 GetAppGroup() const; // from app.xml
uint32 GetAppType() const; // from app.xml
- std::string GetTitleName() const; // from meta.xml
+ std::string GetMetaTitleName() const; // from meta.xml
CafeConsoleRegion GetMetaRegion() const; // from meta.xml
uint32 GetOlvAccesskey() const;
@@ -136,7 +148,7 @@ public:
return m_parsedMetaXml;
}
- std::string GetPrintPath() const; // formatted path for log writing
+ std::string GetPrintPath() const; // formatted path including type and WUA subpath. Intended for logging and user-facing information
std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..."
static std::string GetUniqueTempMountingPath();
diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp
index deb4564f..2e50cbf9 100644
--- a/src/Cafe/TitleList/TitleList.cpp
+++ b/src/Cafe/TitleList/TitleList.cpp
@@ -9,7 +9,7 @@ bool sTLInitialized{ false };
fs::path sTLCacheFilePath;
// lists for tracking known titles
-// note: The list may only contain titles with valid meta data. Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion
+// note: The list may only contain titles with valid meta data (except for certain system titles). Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion
std::mutex sTLMutex;
std::vector sTLList;
std::vector sTLListPending;
@@ -65,6 +65,7 @@ void CafeTitleList::LoadCacheFile()
if( !TitleIdParser::ParseFromStr(titleInfoNode.attribute("titleId").as_string(), titleId))
continue;
uint16 titleVersion = titleInfoNode.attribute("version").as_uint();
+ uint32 sdkVersion = titleInfoNode.attribute("sdk_version").as_uint();
TitleInfo::TitleDataFormat format = (TitleInfo::TitleDataFormat)ConvertString(titleInfoNode.child_value("format"));
CafeConsoleRegion region = (CafeConsoleRegion)ConvertString(titleInfoNode.child_value("region"));
std::string name = titleInfoNode.child_value("name");
@@ -76,6 +77,7 @@ void CafeTitleList::LoadCacheFile()
TitleInfo::CachedInfo cacheEntry;
cacheEntry.titleId = titleId;
cacheEntry.titleVersion = titleVersion;
+ cacheEntry.sdkVersion = sdkVersion;
cacheEntry.titleDataFormat = format;
cacheEntry.region = region;
cacheEntry.titleName = std::move(name);
@@ -115,6 +117,7 @@ void CafeTitleList::StoreCacheFile()
auto titleInfoNode = title_list_node.append_child("title");
titleInfoNode.append_attribute("titleId").set_value(fmt::format("{:016x}", info.titleId).c_str());
titleInfoNode.append_attribute("version").set_value(fmt::format("{:}", info.titleVersion).c_str());
+ titleInfoNode.append_attribute("sdk_version").set_value(fmt::format("{:}", info.sdkVersion).c_str());
titleInfoNode.append_attribute("group_id").set_value(fmt::format("{:08x}", info.group_id).c_str());
titleInfoNode.append_attribute("app_type").set_value(fmt::format("{:08x}", info.app_type).c_str());
titleInfoNode.append_child("region").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (uint32)info.region).c_str());
@@ -638,12 +641,15 @@ GameInfo2 CafeTitleList::GetGameInfo(TitleId titleId)
{
TitleId appTitleId = it->GetAppTitleId();
if (appTitleId == baseTitleId)
+ {
gameInfo.SetBase(*it);
+ }
if (hasSeparateUpdateTitleId && appTitleId == updateTitleId)
{
gameInfo.SetUpdate(*it);
}
}
+
// if this title can have AOC content then do a second scan
// todo - get a list of all AOC title ids from the base/update meta information
// for now we assume there is a direct match between the base titleId and the aoc titleId
diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h
index 56f31a03..7152f2c1 100644
--- a/src/Common/precompiled.h
+++ b/src/Common/precompiled.h
@@ -485,6 +485,14 @@ bool future_is_ready(std::future& f)
#endif
}
+// replace with std::scope_exit once available
+struct scope_exit
+{
+ std::function f_;
+ explicit scope_exit(std::function f) noexcept : f_(std::move(f)) {}
+ ~scope_exit() { if (f_) f_(); }
+};
+
// helper function to cast raw pointers to std::atomic
// this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members
diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp
index 73cbcb75..0df90659 100644
--- a/src/gui/CemuApp.cpp
+++ b/src/gui/CemuApp.cpp
@@ -113,13 +113,14 @@ bool CemuApp::OnInit()
wxMessageBox(fmt::format("Cemu can't write to {} !", path.generic_string()), _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr);
NetworkConfig::LoadOnce();
+ g_config.Load();
HandlePostUpdate();
mainEmulatorHLE();
wxInitAllImageHandlers();
- g_config.Load();
+
m_languages = GetAvailableLanguages();
const sint32 language = GetConfig().language;
@@ -156,10 +157,6 @@ bool CemuApp::OnInit()
__fastfail(0);
}
#endif
-
- // init input
- InputManager::instance().load();
-
InitializeGlobalVulkan();
Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this);
diff --git a/src/gui/GameUpdateWindow.cpp b/src/gui/GameUpdateWindow.cpp
index 40bf546e..e90c9dc7 100644
--- a/src/gui/GameUpdateWindow.cpp
+++ b/src/gui/GameUpdateWindow.cpp
@@ -34,8 +34,6 @@ std::string _GetTitleIdTypeStr(TitleId titleId)
return "Unknown";
}
-bool IsRunningInWine();
-
bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath)
{
m_title_info = TitleInfo(metaPath);
@@ -130,15 +128,11 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath)
}
}
- // checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step
- if(!IsRunningInWine())
+ const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath());
+ if (targetSpace.free <= m_required_size)
{
- const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath());
- if (targetSpace.free <= m_required_size)
- {
- auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024));
- throw std::runtime_error(string);
- }
+ auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024));
+ throw std::runtime_error(string);
}
return true;
diff --git a/src/gui/GameUpdateWindow.h b/src/gui/GameUpdateWindow.h
index 60ec5d63..508e6465 100644
--- a/src/gui/GameUpdateWindow.h
+++ b/src/gui/GameUpdateWindow.h
@@ -26,7 +26,7 @@ public:
//bool IsDLC() const { return m_game_info->IsDLC(); }
//bool IsUpdate() const { return m_game_info->IsUpdate(); }
const std::string& GetExceptionMessage() const { return m_thread_exception; }
- const std::string GetGameName() const { return m_title_info.GetTitleName(); }
+ const std::string GetGameName() const { return m_title_info.GetMetaTitleName(); }
uint32 GetTargetVersion() const { return m_title_info.GetAppTitleVersion(); }
fs::path GetTargetPath() const { return fs::path(m_target_path); }
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 33b4b7d6..d6688ef3 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -73,8 +73,6 @@
extern WindowInfo g_window_info;
extern std::shared_mutex g_mutex;
-wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent);
-
enum
{
// ui elements
@@ -170,8 +168,10 @@ enum
MAINFRAME_ID_TIMER1 = 21800,
};
+wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_REQUEST_GAMELIST_REFRESH, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
+wxDEFINE_EVENT(wxEVT_REQUEST_RECREATE_CANVAS, wxCommandEvent);
wxBEGIN_EVENT_TABLE(MainWindow, wxFrame)
EVT_TIMER(MAINFRAME_ID_TIMER1, MainWindow::OnTimer)
@@ -252,6 +252,8 @@ EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_END_UPDATE, MainWindow::OnGameListEndUpdate
EVT_COMMAND(wxID_ANY, wxEVT_ACCOUNTLIST_REFRESH, MainWindow::OnAccountListRefresh)
EVT_COMMAND(wxID_ANY, wxEVT_SET_WINDOW_TITLE, MainWindow::OnSetWindowTitle)
+EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_RECREATE_CANVAS, MainWindow::OnRequestRecreateCanvas)
+
wxEND_EVENT_TABLE()
class wxGameDropTarget : public wxFileDropTarget
@@ -305,6 +307,7 @@ MainWindow::MainWindow()
{
gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_main, this);
g_mainFrame = this;
+ CafeSystem::SetImplementation(this);
RecreateMenu();
SetClientSize(1280, 720);
@@ -345,62 +348,45 @@ MainWindow::MainWindow()
}
}
- if (!quick_launch)
- {
- {
- m_main_panel = new wxPanel(this);
- auto* sizer = new wxBoxSizer(wxVERTICAL);
- // game list
- m_game_list = new wxGameList(m_main_panel, MAINFRAME_GAMELIST_ID);
- m_game_list->Bind(wxEVT_OPEN_SETTINGS, [this](auto&) {OpenSettings(); });
- m_game_list->SetDropTarget(new wxGameDropTarget(this));
- sizer->Add(m_game_list, 1, wxEXPAND);
+ SetSizer(main_sizer);
+ if (!quick_launch)
+ {
+ CreateGameListAndStatusBar();
+ }
+ else
+ {
+ // launching game via -g or -t option. Don't set up or load game list
+ m_game_list = nullptr;
+ m_info_bar = nullptr;
+ }
+ SetSizer(main_sizer);
- // info, warning bar
- m_info_bar = new wxInfoBar(m_main_panel);
- m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND);
- m_info_bar->SetEffectDuration(500);
- sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5);
+ m_last_mouse_move_time = std::chrono::steady_clock::now();
- m_main_panel->SetSizer(sizer);
- main_sizer->Add(m_main_panel, 1, wxEXPAND, 0, nullptr);
- }
- }
- else
- {
- // launching game via -g or -t option. Don't set up or load game list
- m_game_list = nullptr;
- m_info_bar = nullptr;
- }
- SetSizer(main_sizer);
+ m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1);
+ m_timer->Start(500);
- m_last_mouse_move_time = std::chrono::steady_clock::now();
+ LoadSettings();
- m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1);
- m_timer->Start(500);
+ auto& config = GetConfig();
+ #ifdef ENABLE_DISCORD_RPC
+ if (config.use_discord_presence)
+ m_discord = std::make_unique();
+ #endif
- LoadSettings();
+ Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this);
+ Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this);
- auto& config = GetConfig();
-#ifdef ENABLE_DISCORD_RPC
- if (config.use_discord_presence)
- m_discord = std::make_unique();
-#endif
-
- Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this);
- Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this);
-
- if (LaunchSettings::GDBStubEnabled())
- {
- g_gdbstub = std::make_unique(config.gdb_port);
- }
+ if (LaunchSettings::GDBStubEnabled())
+ {
+ g_gdbstub = std::make_unique(config.gdb_port);
+ }
}
MainWindow::~MainWindow()
{
if (m_padView)
{
- //delete m_padView;
m_padView->Destroy();
m_padView = nullptr;
}
@@ -411,13 +397,47 @@ MainWindow::~MainWindow()
g_mainFrame = nullptr;
}
+void MainWindow::CreateGameListAndStatusBar()
+{
+ if(m_main_panel)
+ return; // already displayed
+ m_main_panel = new wxPanel(this);
+ auto* sizer = new wxBoxSizer(wxVERTICAL);
+ // game list
+ m_game_list = new wxGameList(m_main_panel, MAINFRAME_GAMELIST_ID);
+ m_game_list->Bind(wxEVT_OPEN_SETTINGS, [this](auto&) {OpenSettings(); });
+ m_game_list->SetDropTarget(new wxGameDropTarget(this));
+ sizer->Add(m_game_list, 1, wxEXPAND);
+
+ // info, warning bar
+ m_info_bar = new wxInfoBar(m_main_panel);
+ m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND);
+ m_info_bar->SetEffectDuration(500);
+ sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5);
+
+ m_main_panel->SetSizer(sizer);
+
+ auto* main_sizer = this->GetSizer();
+ main_sizer->Add(m_main_panel, 1, wxEXPAND, 0, nullptr);
+}
+
+void MainWindow::DestroyGameListAndStatusBar()
+{
+ if(!m_main_panel)
+ return;
+ m_main_panel->Destroy();
+ m_main_panel = nullptr;
+ m_game_list = nullptr;
+ m_info_bar = nullptr;
+}
+
wxString MainWindow::GetInitialWindowTitle()
{
return BUILD_VERSION_WITH_NAME_STRING;
}
void MainWindow::ShowGettingStartedDialog()
-{
+{
GettingStartedDialog dia(this);
dia.ShowModal();
if (dia.HasGamePathChanged() || dia.HasMLCChanged())
@@ -450,7 +470,7 @@ void MainWindow::OnClose(wxCloseEvent& event)
event.Skip();
- CafeSystem::ShutdownTitle();
+ CafeSystem::Shutdown();
DestroyCanvas();
}
@@ -573,36 +593,13 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY
wxWindowUpdateLocker lock(this);
- auto* main_sizer = GetSizer();
- // remove old gamelist panel
- if (m_main_panel)
- {
- m_main_panel->Hide();
- main_sizer->Detach(m_main_panel);
- }
-
- // create render canvas rendering
- m_game_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS);
- auto* sizer = new wxBoxSizer(wxVERTICAL);
-
- // shouldn't be needed, but who knows
- m_game_panel->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this);
- m_game_panel->Bind(wxEVT_CHAR, &MainWindow::OnChar, this);
-
- m_game_panel->SetSizer(sizer);
- main_sizer->Add(m_game_panel, 1, wxEXPAND, 0, nullptr);
+ DestroyGameListAndStatusBar();
m_game_launched = true;
m_loadMenuItem->Enable(false);
m_installUpdateMenuItem->Enable(false);
m_memorySearcherMenuItem->Enable(true);
- if (m_game_list)
- {
- delete m_game_list;
- m_game_list = nullptr;
- }
-
m_launched_game_name = CafeSystem::GetForegroundTitleName();
#ifdef ENABLE_DISCORD_RPC
if (m_discord)
@@ -690,10 +687,12 @@ void MainWindow::OnFileMenu(wxCommandEvent& event)
}
else if (menuId == MAINFRAME_MENU_ID_FILE_END_EMULATION)
{
- CafeSystem::ShutdownTitle();
+ CafeSystem::ShutdownTitle();
DestroyCanvas();
m_game_launched = false;
RecreateMenu();
+ CreateGameListAndStatusBar();
+ DoLayout();
}
}
@@ -1583,7 +1582,19 @@ void MainWindow::AsyncSetTitle(std::string_view windowTitle)
void MainWindow::CreateCanvas()
{
- if (ActiveSettings::GetGraphicsAPI() == kVulkan)
+ // create panel for canvas
+ m_game_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS);
+ auto* sizer = new wxBoxSizer(wxVERTICAL);
+
+ // shouldn't be needed, but who knows
+ m_game_panel->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this);
+ m_game_panel->Bind(wxEVT_CHAR, &MainWindow::OnChar, this);
+
+ m_game_panel->SetSizer(sizer);
+ this->GetSizer()->Add(m_game_panel, 1, wxEXPAND, 0, nullptr);
+
+ // create canvas
+ if (ActiveSettings::GetGraphicsAPI() == kVulkan)
m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true);
else
m_render_canvas = GLCanvas_Create(m_game_panel, wxSize(1280, 720), true);
@@ -1616,14 +1627,18 @@ void MainWindow::DestroyCanvas()
{
if (m_padView)
{
- m_padView->Destroy();
- m_padView = nullptr;
+ m_padView->DestroyCanvas();
}
if (m_render_canvas)
{
m_render_canvas->Destroy();
m_render_canvas = nullptr;
}
+ if(m_game_panel)
+ {
+ m_game_panel->Destroy();
+ m_game_panel = nullptr;
+ }
}
void MainWindow::OnSizeEvent(wxSizeEvent& event)
@@ -2084,9 +2099,9 @@ void MainWindow::RecreateMenu()
auto& config = GetConfig();
- m_menuBar = new wxMenuBar;
+ m_menuBar = new wxMenuBar();
// file submenu
- m_fileMenu = new wxMenu;
+ m_fileMenu = new wxMenu();
if (!m_game_launched)
{
@@ -2120,7 +2135,7 @@ void MainWindow::RecreateMenu()
{
// add 'Stop emulation' menu entry to file menu
#ifdef CEMU_DEBUG_ASSERT
- m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_END_EMULATION, _("End emulation"));
+ m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_END_EMULATION, _("Stop emulation"));
#endif
}
@@ -2130,7 +2145,7 @@ void MainWindow::RecreateMenu()
m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit"));
m_menuBar->Append(m_fileMenu, _("&File"));
// options->account submenu
- m_optionsAccountMenu = new wxMenu;
+ m_optionsAccountMenu = new wxMenu();
const auto account_id = ActiveSettings::GetPersistentId();
int index = 0;
for(const auto& account : Account::GetAccounts())
@@ -2142,23 +2157,9 @@ void MainWindow::RecreateMenu()
++index;
}
- //optionsAccountMenu->AppendSeparator(); TODO
- //optionsAccountMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 + index, _("Online enabled"))->Check(config.account.online_enabled);
-
- // options->region submenu
- //wxMenu* optionsRegionMenu = new wxMenu;
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_AUTO, _("&Auto"), wxEmptyString)->Check(config.console_region == ConsoleRegion::Auto);
- ////optionsRegionMenu->AppendSeparator();
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_USA, _("&USA"), wxEmptyString)->Check(config.console_region == ConsoleRegion::USA);
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_EUR, _("&Europe"), wxEmptyString)->Check(config.console_region == ConsoleRegion::EUR);
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_JPN, _("&Japan"), wxEmptyString)->Check(config.console_region == ConsoleRegion::JPN);
- //// optionsRegionMenu->Append(MAINFRAME_MENU_ID_OPTIONS_REGION_AUS, wxT("&Australia"), wxEmptyString, wxITEM_RADIO)->Check(config_get()->region==3); -> Was merged into Europe?
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_CHN, _("&China"), wxEmptyString)->Check(config.console_region == ConsoleRegion::CHN);
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_KOR, _("&Korea"), wxEmptyString)->Check(config.console_region == ConsoleRegion::KOR);
- //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_TWN, _("&Taiwan"), wxEmptyString)->Check(config.console_region == ConsoleRegion::TWN);
// options->console language submenu
- wxMenu* optionsConsoleLanguageMenu = new wxMenu;
+ wxMenu* optionsConsoleLanguageMenu = new wxMenu();
optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, _("&English"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::EN);
optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, _("&Japanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::JA);
optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH, _("&French"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::FR);
@@ -2173,12 +2174,11 @@ void MainWindow::RecreateMenu()
optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, _("&Taiwanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::TW);
// options submenu
- wxMenu* optionsMenu = new wxMenu;
+ wxMenu* optionsMenu = new wxMenu();
m_fullscreenMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, _("&Fullscreen"), wxEmptyString);
m_fullscreenMenuItem->Check(ActiveSettings::FullscreenEnabled());
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, _("&Graphic packs"));
- //optionsMenu->AppendSubMenu(optionsVCAMenu, _("&GPU buffer cache accuracy"));
m_padViewMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, _("&Separate GamePad view"), wxEmptyString);
m_padViewMenuItem->Check(GetConfig().pad_open);
optionsMenu->AppendSeparator();
@@ -2187,12 +2187,11 @@ void MainWindow::RecreateMenu()
optionsMenu->AppendSeparator();
optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account"));
- //optionsMenu->AppendSubMenu(optionsRegionMenu, _("&Console region"));
optionsMenu->AppendSubMenu(optionsConsoleLanguageMenu, _("&Console language"));
m_menuBar->Append(optionsMenu, _("&Options"));
// tools submenu
- wxMenu* toolsMenu = new wxMenu;
+ wxMenu* toolsMenu = new wxMenu();
m_memorySearcherMenuItem = toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, _("&Memory searcher"));
m_memorySearcherMenuItem->Enable(false);
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager"));
@@ -2200,7 +2199,7 @@ void MainWindow::RecreateMenu()
m_menuBar->Append(toolsMenu, _("&Tools"));
// cpu timer speed menu
- wxMenu* timerSpeedMenu = new wxMenu;
+ wxMenu* timerSpeedMenu = new wxMenu();
timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_1X, _("&1x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 3);
timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_2X, _("&2x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 2);
timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_4X, _("&4x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 1);
@@ -2210,12 +2209,12 @@ void MainWindow::RecreateMenu()
timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, _("&0.125x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 6);
// cpu submenu
- wxMenu* cpuMenu = new wxMenu;
+ wxMenu* cpuMenu = new wxMenu();
cpuMenu->AppendSubMenu(timerSpeedMenu, _("&Timer speed"));
m_menuBar->Append(cpuMenu, _("&CPU"));
// nfc submenu
- wxMenu* nfcMenu = new wxMenu;
+ wxMenu* nfcMenu = new wxMenu();
m_nfcMenu = nfcMenu;
nfcMenu->Append(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, _("&Scan NFC tag from file"))->Enable(false);
m_menuBar->Append(nfcMenu, _("&NFC"));
@@ -2262,7 +2261,7 @@ void MainWindow::RecreateMenu()
debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, _("&Shaders"), wxEmptyString)->Check(ActiveSettings::DumpShadersEnabled());
debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, _("&nlibcurl HTTP/HTTPS requests"), wxEmptyString);
// debug submenu
- wxMenu* debugMenu = new wxMenu;
+ wxMenu* debugMenu = new wxMenu();
m_debugMenu = debugMenu;
debugMenu->AppendSubMenu(debugLoggingMenu, _("&Logging"));
debugMenu->AppendSubMenu(debugDumpMenu, _("&Dump"));
@@ -2297,7 +2296,7 @@ void MainWindow::RecreateMenu()
m_menuBar->Append(debugMenu, _("&Debug"));
// help menu
- wxMenu* helpMenu = new wxMenu;
+ wxMenu* helpMenu = new wxMenu();
//helpMenu->Append(MAINFRAME_MENU_ID_HELP_WEB, wxT("&Visit website"));
//helpMenu->AppendSeparator();
m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates"));
@@ -2317,8 +2316,8 @@ void MainWindow::RecreateMenu()
m_memorySearcherMenuItem->Enable(true);
m_nfcMenu->Enable(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, true);
-
- // disable OpenGL logging (currently cant be toggled after OpenGL backend is initialized)
+
+ // these options cant be toggled after the renderer backend is initialized:
m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), false);
m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), false);
@@ -2389,31 +2388,9 @@ void MainWindow::RestoreSettingsAfterGameExited()
void MainWindow::UpdateSettingsAfterGameLaunch()
{
m_update_available = {};
- //m_game_launched = true;
RecreateMenu();
}
-std::string MainWindow::GetRegionString(uint32 region) const
-{
- switch (region)
- {
- case 0x1:
- return "JPN";
- case 0x2:
- return "USA";
- case 0x4:
- return "EUR";
- case 0x10:
- return "CHN";
- case 0x20:
- return "KOR";
- case 0x40:
- return "TWN";
- default:
- return std::to_string(region);
- }
-}
-
void MainWindow::OnGraphicWindowClose(wxCloseEvent& event)
{
m_graphic_pack_window->Destroy();
@@ -2424,7 +2401,6 @@ void MainWindow::OnGraphicWindowOpen(wxTitleIdEvent& event)
{
if (m_graphic_pack_window)
return;
-
m_graphic_pack_window = new GraphicPacksWindow2(this, event.GetTitleId());
m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this);
m_graphic_pack_window->Show(true);
@@ -2441,3 +2417,20 @@ void MainWindow::RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIAT
wxLaunchGameEvent evt(filePath, initiatedBy);
wxPostEvent(g_mainFrame, evt);
}
+
+void MainWindow::OnRequestRecreateCanvas(wxCommandEvent& event)
+{
+ CounterSemaphore* sem = (CounterSemaphore*)event.GetClientData();
+ DestroyCanvas();
+ CreateCanvas();
+ sem->increment();
+}
+
+void MainWindow::CafeRecreateCanvas()
+{
+ CounterSemaphore sem;
+ auto* evt = new wxCommandEvent(wxEVT_REQUEST_RECREATE_CANVAS);
+ evt->SetClientData((void*)&sem);
+ wxQueueEvent(g_mainFrame, evt);
+ sem.decrementWithWait();
+}
diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h
index 610fbcac..6f8424f8 100644
--- a/src/gui/MainWindow.h
+++ b/src/gui/MainWindow.h
@@ -15,6 +15,7 @@
#include
#include "Cafe/HW/Espresso/Debugger/GDBStub.h"
+#include "Cafe/CafeSystem.h"
class DebuggerWindow2;
struct GameEntry;
@@ -50,14 +51,17 @@ private:
INITIATED_BY m_initiatedBy;
};
-class MainWindow : public wxFrame
+class MainWindow : public wxFrame, public CafeSystem::SystemImplementation
{
friend class CemuApp;
public:
MainWindow();
~MainWindow();
-
+
+ void CreateGameListAndStatusBar();
+ void DestroyGameListAndStatusBar();
+
void UpdateSettingsAfterGameLaunch();
void RestoreSettingsAfterGameExited();
@@ -152,6 +156,11 @@ private:
void OnTimer(wxTimerEvent& event);
+ // CafeSystem implementation
+ void CafeRecreateCanvas() override;
+
+ void OnRequestRecreateCanvas(wxCommandEvent& event);
+
wxRect GetDesktopRect();
MemorySearcherTool* m_toolWindow = nullptr;
@@ -184,8 +193,6 @@ private:
void LoadSettings();
void SaveSettings();
- std::string GetRegionString(uint32 region) const;
-
void OnGraphicWindowClose(wxCloseEvent& event);
void OnGraphicWindowOpen(wxTitleIdEvent& event);
@@ -196,37 +203,35 @@ private:
wxWindow* m_render_canvas{};
// gamelist
- wxGameList* m_game_list;
- wxInfoBar* m_info_bar;
+ wxGameList* m_game_list{};
+ wxInfoBar* m_info_bar{};
// menu
- wxMenuBar* m_menuBar = nullptr;
+ wxMenuBar* m_menuBar{};
// file
- wxMenu* m_fileMenu;
- wxMenuItem* m_fileMenuSeparator0;
- wxMenuItem* m_fileMenuSeparator1;
- wxMenuItem* m_loadMenuItem;
- wxMenuItem* m_installUpdateMenuItem;
- wxMenuItem* m_exitMenuItem;
+ wxMenu* m_fileMenu{};
+ wxMenuItem* m_fileMenuSeparator0{};
+ wxMenuItem* m_fileMenuSeparator1{};
+ wxMenuItem* m_loadMenuItem{};
+ wxMenuItem* m_installUpdateMenuItem{};
+ wxMenuItem* m_exitMenuItem{};
// options
- //wxMenu* m_gpuBufferCacheAccuracySubmenu;
- wxMenu* m_optionsAccountMenu;
+ wxMenu* m_optionsAccountMenu{};
- wxMenuItem* m_fullscreenMenuItem;
- wxMenuItem* m_padViewMenuItem;
+ wxMenuItem* m_fullscreenMenuItem{};
+ wxMenuItem* m_padViewMenuItem{};
// tools
- wxMenuItem* m_memorySearcherMenuItem;
+ wxMenuItem* m_memorySearcherMenuItem{};
// cpu
- //wxMenu* m_cpuModeSubmenu;
- wxMenu* m_cpuTimerSubmenu;
+ wxMenu* m_cpuTimerSubmenu{};
// nfc
- wxMenu* m_nfcMenu;
- wxMenuItem* m_nfcMenuSeparator0;
+ wxMenu* m_nfcMenu{};
+ wxMenuItem* m_nfcMenuSeparator0{};
// savestates
//wxMenuItem* m_pause;
@@ -235,9 +240,9 @@ private:
//wxMenuItem* m_loadState;
// debug
- wxMenu* m_debugMenu;
- wxMenu* m_loggingSubmenu;
- wxMenuItem* m_asyncCompile;
+ wxMenu* m_debugMenu{};
+ wxMenu* m_loggingSubmenu{};
+ wxMenuItem* m_asyncCompile{};
wxDECLARE_EVENT_TABLE();
};
diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp
index 4db25c5f..744df323 100644
--- a/src/gui/PadViewFrame.cpp
+++ b/src/gui/PadViewFrame.cpp
@@ -90,6 +90,14 @@ void PadViewFrame::InitializeRenderCanvas()
SendSizeEvent();
}
+void PadViewFrame::DestroyCanvas()
+{
+ if(!m_render_canvas)
+ return;
+ m_render_canvas->Destroy();
+ m_render_canvas = nullptr;
+}
+
void PadViewFrame::OnSizeEvent(wxSizeEvent& event)
{
if (!IsMaximized() && !IsFullScreen())
diff --git a/src/gui/PadViewFrame.h b/src/gui/PadViewFrame.h
index f5b364c6..2be173ab 100644
--- a/src/gui/PadViewFrame.h
+++ b/src/gui/PadViewFrame.h
@@ -14,6 +14,7 @@ public:
bool Initialize();
void InitializeRenderCanvas();
+ void DestroyCanvas();
void OnKeyUp(wxKeyEvent& event);
void OnChar(wxKeyEvent& event);
diff --git a/src/gui/canvas/OpenGLCanvas.cpp b/src/gui/canvas/OpenGLCanvas.cpp
index 9f779863..29ff4294 100644
--- a/src/gui/canvas/OpenGLCanvas.cpp
+++ b/src/gui/canvas/OpenGLCanvas.cpp
@@ -36,8 +36,6 @@ public:
OpenGLCanvas(wxWindow* parent, const wxSize& size, bool is_main_window)
: IRenderCanvas(is_main_window), wxGLCanvas(parent, wxID_ANY, g_gl_attribute_list, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{
- cemuLog_logDebug(LogType::Force, "Creating OpenGL canvas");
-
if (m_is_main_window)
{
sGLTVView = this;
diff --git a/src/gui/canvas/VulkanCanvas.cpp b/src/gui/canvas/VulkanCanvas.cpp
index ceddeb52..5463a494 100644
--- a/src/gui/canvas/VulkanCanvas.cpp
+++ b/src/gui/canvas/VulkanCanvas.cpp
@@ -53,8 +53,9 @@ VulkanCanvas::~VulkanCanvas()
if(!m_is_main_window)
{
- if(auto vulkan_renderer = VulkanRenderer::GetInstance())
- vulkan_renderer->StopUsingPadAndWait();
+ VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get();
+ if(vkr)
+ vkr->StopUsingPadAndWait();
}
}
diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp
index a79b0c67..69f74870 100644
--- a/src/gui/components/wxGameList.cpp
+++ b/src/gui/components/wxGameList.cpp
@@ -323,7 +323,7 @@ std::string wxGameList::GetNameByTitleId(uint64 titleId)
return "Unknown title";
std::string name;
if (!GetConfig().GetGameListCustomName(titleId, name))
- name = titleInfo.GetTitleName();
+ name = titleInfo.GetMetaTitleName();
m_name_cache.emplace(titleId, name);
return name;
}
@@ -967,13 +967,11 @@ int wxGameList::FindInsertPosition(TitleId titleId)
void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event)
{
const auto titleId = event.GetTitleId();
- // get GameInfo from title list
GameInfo2 gameInfo = CafeTitleList::GetGameInfo(titleId);
-
- if (!gameInfo.IsValid())
+ if (!gameInfo.IsValid() || gameInfo.IsSystemDataTitle())
{
- // entry no longer exists
- // we dont need to do anything here because all delete operations should trigger a full list refresh
+ // entry no longer exists or is not a valid game
+ // we dont need to remove list entries here because all delete operations should trigger a full list refresh
return;
}
TitleId baseTitleId = gameInfo.GetBaseTitleId();
@@ -1289,17 +1287,18 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) {
}
}
}
+ // 'Icon' accepts spaces in file name, does not accept quoted file paths
+ // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
const auto desktop_entry_string =
fmt::format("[Desktop Entry]\n"
- "Name={}\n"
- "Comment=Play {} on Cemu\n"
- "Exec={} --title-id {:016x}\n"
- "Icon={}\n"
+ "Name={0}\n"
+ "Comment=Play {0} on Cemu\n"
+ "Exec={1:?} --title-id {2:016x}\n"
+ "Icon={3}\n"
"Terminal=false\n"
"Type=Application\n"
"Categories=Game;",
title_name,
- title_name,
_pathToUtf8(exe_path),
title_id,
_pathToUtf8(icon_path.value_or("")));
@@ -1341,4 +1340,4 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) {
}
#endif
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp
index 7ba8d037..6572a702 100644
--- a/src/gui/components/wxTitleManagerList.cpp
+++ b/src/gui/components/wxTitleManagerList.cpp
@@ -242,7 +242,7 @@ boost::optional wxTitleManagerList::GetTi
return {};
}
-void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId)
+void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID)
{
TitleInfo titleInfo_base;
TitleInfo titleInfo_update;
@@ -269,22 +269,26 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId)
{
if (!titleInfo_base.IsValid())
{
- titleInfo_base = TitleInfo(data->entry.path);
- }
- else
- {
- // duplicate entry
+ titleInfo_base = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid);
+ if(data->entry.location_uid == rightClickedUID)
+ break; // prefer the users selection
}
}
if (hasUpdateTitleId && data->entry.title_id == updateTitleId)
{
if (!titleInfo_update.IsValid())
{
- titleInfo_update = TitleInfo(data->entry.path);
+ titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid);
+ if(data->entry.location_uid == rightClickedUID)
+ break;
}
else
{
- // duplicate entry
+ // if multiple updates are present use the newest one
+ if (titleInfo_update.GetAppTitleVersion() < data->entry.version)
+ titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid);
+ if(data->entry.location_uid == rightClickedUID)
+ break;
}
}
}
@@ -293,7 +297,9 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId)
{
if (data->entry.title_id == aocTitleId)
{
- titleInfo_aoc = TitleInfo(data->entry.path);
+ titleInfo_aoc = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid);
+ if(data->entry.location_uid == rightClickedUID)
+ break;
}
}
@@ -301,23 +307,23 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId)
msg.append("\n \n");
if (titleInfo_base.IsValid())
- msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game: {}"))), _pathToUtf8(titleInfo_base.GetPath())));
+ msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game:\n{}"))), titleInfo_base.GetPrintPath()));
else
- msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game: Not installed")))));
+ msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game:\nNot installed")))));
- msg.append("\n");
+ msg.append("\n\n");
if (titleInfo_update.IsValid())
- msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update: {}"))), _pathToUtf8(titleInfo_update.GetPath())));
+ msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update:\n{}"))), titleInfo_update.GetPrintPath()));
else
- msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update: Not installed")))));
+ msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update:\nNot installed")))));
- msg.append("\n");
+ msg.append("\n\n");
if (titleInfo_aoc.IsValid())
- msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC: {}"))), _pathToUtf8(titleInfo_aoc.GetPath())));
+ msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC:\n{}"))), titleInfo_aoc.GetPrintPath()));
else
- msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC: Not installed")))));
+ msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC:\nNot installed")))));
const int answer = wxMessageBox(wxString::FromUTF8(msg), _("Confirmation"), wxOK | wxCANCEL | wxCENTRE | wxICON_QUESTION, this);
if (answer != wxOK)
@@ -884,7 +890,7 @@ void wxTitleManagerList::OnContextMenuSelected(wxCommandEvent& event)
break;
case kContextMenuConvertToWUA:
- OnConvertToCompressedFormat(entry.value().title_id);
+ OnConvertToCompressedFormat(entry.value().title_id, entry.value().location_uid);
break;
}
}
@@ -1005,7 +1011,8 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt
wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath());
ParsedMetaXml* metaInfo = titleInfo.GetMetaInfo();
-
+ if(titleInfo.IsSystemDataTitle())
+ return; // dont show system data titles for now
entry.location_uid = titleInfo.GetUID();
entry.title_id = titleInfo.GetAppTitleId();
std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue());
diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h
index 706de2e7..547310c2 100644
--- a/src/gui/components/wxTitleManagerList.h
+++ b/src/gui/components/wxTitleManagerList.h
@@ -108,7 +108,7 @@ private:
[[nodiscard]] boost::optional GetTitleEntry(const fs::path& path);
bool VerifyEntryFiles(TitleEntry& entry);
- void OnConvertToCompressedFormat(uint64 titleId);
+ void OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID);
bool DeleteEntry(long index, const TitleEntry& entry);
void RemoveItem(long item);
diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp
index ecb77428..63b92626 100644
--- a/src/gui/debugger/BreakpointWindow.cpp
+++ b/src/gui/debugger/BreakpointWindow.cpp
@@ -11,9 +11,11 @@
enum
{
- MENU_ID_CREATE_MEM_BP_READ = 1,
+ MENU_ID_CREATE_CODE_BP_EXECUTION = 1,
+ MENU_ID_CREATE_CODE_BP_LOGGING,
+ MENU_ID_CREATE_MEM_BP_READ,
MENU_ID_CREATE_MEM_BP_WRITE,
-
+ MENU_ID_DELETE_BP,
};
enum ItemColumns
@@ -118,6 +120,8 @@ void BreakpointWindow::OnUpdateView()
const char* typeName = "UKN";
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
typeName = "X";
+ else if (bp->bpType == DEBUGGER_BP_T_LOGGING)
+ typeName = "LOG";
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
typeName = "XS";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
@@ -211,31 +215,56 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
void BreakpointWindow::OnRightDown(wxMouseEvent& event)
{
- wxMenu menu;
+ const auto position = event.GetPosition();
+ const sint32 index = (position.y / m_breakpoints->GetCharHeight()) - 2;
+ if (index < 0 || index >= m_breakpoints->GetItemCount())
+ {
+ wxMenu menu;
+ menu.Append(MENU_ID_CREATE_CODE_BP_EXECUTION, _("Create execution breakpoint"));
+ menu.Append(MENU_ID_CREATE_CODE_BP_LOGGING, _("Create logging breakpoint"));
+ menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)"));
+ menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)"));
- menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)"));
- menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)"));
+ menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this);
+ PopupMenu(&menu);
+ }
+ else
+ {
+ m_breakpoints->SetItemState(index, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
+ m_breakpoints->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
- menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this);
- PopupMenu(&menu);
+ wxMenu menu;
+ menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint"));
+
+ menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClickSelected), nullptr, this);
+ PopupMenu(&menu);
+ }
+}
+
+void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt)
+{
+ if (evt.GetId() == MENU_ID_DELETE_BP)
+ {
+ long sel = m_breakpoints->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (sel != -1)
+ {
+ if (sel >= debuggerState.breakpoints.size())
+ return;
+
+ auto it = debuggerState.breakpoints.begin();
+ std::advance(it, sel);
+
+ debugger_deleteBreakpoint(*it);
+
+ wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
+ wxPostEvent(this->m_parent, evt);
+ }
+ }
}
void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)
{
- switch (evt.GetId())
- {
- case MENU_ID_CREATE_MEM_BP_READ:
- MemoryBreakpointDialog(false);
- return;
- case MENU_ID_CREATE_MEM_BP_WRITE:
- MemoryBreakpointDialog(true);
- return;
- }
-}
-
-void BreakpointWindow::MemoryBreakpointDialog(bool isWrite)
-{
- wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Memory breakpoint"), wxEmptyString);
+ wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Set breakpoint"), wxEmptyString);
if (goto_dialog.ShowModal() == wxID_OK)
{
ExpressionParser parser;
@@ -243,22 +272,34 @@ void BreakpointWindow::MemoryBreakpointDialog(bool isWrite)
auto value = goto_dialog.GetValue().ToStdString();
std::transform(value.begin(), value.end(), value.begin(), tolower);
-
+ uint32_t newBreakpointAddress = 0;
try
{
debugger_addParserSymbols(parser);
- const auto result = (uint32)parser.Evaluate(value);
- debug_printf("goto eval result: %x\n", result);
-
- debugger_createMemoryBreakpoint(result, isWrite == false, isWrite == true);
- this->OnUpdateView();
+ newBreakpointAddress = parser.IsConstantExpression("0x"+value) ? (uint32)parser.Evaluate("0x"+value) : (uint32)parser.Evaluate(value);
}
- catch (const std::exception& e)
+ catch (const std::exception& ex)
{
- //ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString));
- //return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR;
- wxMessageBox(e.what(), "Invalid expression");
+ wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
+ return;
}
-
+
+ switch (evt.GetId())
+ {
+ case MENU_ID_CREATE_CODE_BP_EXECUTION:
+ debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_NORMAL);
+ break;
+ case MENU_ID_CREATE_CODE_BP_LOGGING:
+ debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_LOGGING);
+ break;
+ case MENU_ID_CREATE_MEM_BP_READ:
+ debugger_createMemoryBreakpoint(newBreakpointAddress, true, false);
+ break;
+ case MENU_ID_CREATE_MEM_BP_WRITE:
+ debugger_createMemoryBreakpoint(newBreakpointAddress, false, true);
+ break;
+ }
+
+ this->OnUpdateView();
}
-}
\ No newline at end of file
+}
diff --git a/src/gui/debugger/BreakpointWindow.h b/src/gui/debugger/BreakpointWindow.h
index 4851cb52..ac132950 100644
--- a/src/gui/debugger/BreakpointWindow.h
+++ b/src/gui/debugger/BreakpointWindow.h
@@ -19,8 +19,7 @@ private:
void OnRightDown(wxMouseEvent& event);
void OnContextMenuClick(wxCommandEvent& evt);
-
- void MemoryBreakpointDialog(bool isWrite);
+ void OnContextMenuClickSelected(wxCommandEvent& evt);
wxCheckedListCtrl* m_breakpoints;
};
\ No newline at end of file
diff --git a/src/gui/debugger/DebuggerWindow2.cpp b/src/gui/debugger/DebuggerWindow2.cpp
index f8c5c931..969e40bd 100644
--- a/src/gui/debugger/DebuggerWindow2.cpp
+++ b/src/gui/debugger/DebuggerWindow2.cpp
@@ -118,7 +118,7 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
const auto comment = element.get("Comment", "");
// calculate absolute address
- uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data);
+ uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data;
uint32 address = module_base_address + relative_address;
// don't change anything if there's already a breakpoint
@@ -127,7 +127,9 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
// register breakpoints in debugger
if (type == DEBUGGER_BP_T_NORMAL)
- debugger_createExecuteBreakpoint(address);
+ debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
+ else if (type == DEBUGGER_BP_T_LOGGING)
+ debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
else if (type == DEBUGGER_BP_T_MEMORY_READ)
debugger_createMemoryBreakpoint(address, true, false);
else if (type == DEBUGGER_BP_T_MEMORY_WRITE)
@@ -173,7 +175,7 @@ void DebuggerModuleStorage::Save(XMLConfigParser& parser)
// check whether the breakpoint is part of the current module being saved
RPLModule* address_module;
- if (bp->bpType == DEBUGGER_BP_T_NORMAL) address_module = RPLLoader_FindModuleByCodeAddr(bp->address);
+ if (bp->bpType == DEBUGGER_BP_T_NORMAL || bp->bpType == DEBUGGER_BP_T_LOGGING) address_module = RPLLoader_FindModuleByCodeAddr(bp->address);
else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address);
else continue;
@@ -259,7 +261,7 @@ void DebuggerWindow2::LoadModuleStorage(const RPLModule* module)
bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr& debug) { return debug->GetFilename() == path; });
if (!path.empty() && !already_loaded)
{
- m_modules_storage.emplace_back(std::move(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false })));
+ m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load();
}
}
@@ -522,6 +524,7 @@ void DebuggerWindow2::OnToolClicked(wxCommandEvent& event)
void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event)
{
m_breakpoint_window->OnUpdateView();
+ m_disasm_ctrl->RefreshControl();
UpdateModuleLabel();
}
diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp
index dededf2c..21f6fc1d 100644
--- a/src/gui/debugger/DisasmCtrl.cpp
+++ b/src/gui/debugger/DisasmCtrl.cpp
@@ -147,7 +147,7 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR
else if (is_active_bp)
background_colour = wxColour(0xFF80A0FF);
else if (bp != nullptr)
- background_colour = wxColour(0xFF8080FF);
+ background_colour = wxColour(bp->bpType == DEBUGGER_BP_T_NORMAL ? 0xFF8080FF : 0x80FFFFFF);
else if(virtualAddress == m_lastGotoTarget)
background_colour = wxColour(0xFFE0E0E0);
else
@@ -540,8 +540,6 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
{
debugger_toggleExecuteBreakpoint(*optVirtualAddress);
- RefreshControl();
-
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
}
@@ -767,40 +765,31 @@ void DisasmCtrl::GoToAddressDialog()
auto value = goto_dialog.GetValue().ToStdString();
std::transform(value.begin(), value.end(), value.begin(), tolower);
- const auto module_count = RPLLoader_GetModuleCount();
- const auto module_list = RPLLoader_GetModuleList();
+ debugger_addParserSymbols(parser);
- std::vector module_tmp(module_count);
- for (int i = 0; i < module_count; i++)
+ // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234)
+ if (parser.IsConstantExpression("0x"+value))
{
- const auto module = module_list[i];
- if (module)
- {
- module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
- parser.AddConstant(module->moduleName2, module_tmp[i]);
- }
- }
-
- double grp_tmp[32];
- PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot;
- for (int i = 0; i < 32; i++)
- {
- char var_name[32];
- sprintf(var_name, "r%d", i);
- grp_tmp[i] = ppc_snapshot.gpr[i];
- parser.AddConstant(var_name, grp_tmp[i]);
- }
-
- try
- {
- const auto result = (uint32)parser.Evaluate(value);
- debug_printf("goto eval result: %x\n", result);
+ const auto result = (uint32)parser.Evaluate("0x"+value);
m_lastGotoTarget = result;
CenterOffset(result);
- debuggerWindow_updateViewThreadsafe2();
}
- catch (const std::exception& )
+ else if (parser.IsConstantExpression(value))
{
+ const auto result = (uint32)parser.Evaluate(value);
+ m_lastGotoTarget = result;
+ CenterOffset(result);
+ }
+ else
+ {
+ try
+ {
+ const auto _ = (uint32)parser.Evaluate(value);
+ }
+ catch (const std::exception& ex)
+ {
+ wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/gui/debugger/DumpCtrl.cpp b/src/gui/debugger/DumpCtrl.cpp
index cc1d5fee..16fdd87d 100644
--- a/src/gui/debugger/DumpCtrl.cpp
+++ b/src/gui/debugger/DumpCtrl.cpp
@@ -214,46 +214,36 @@ void DumpCtrl::GoToAddressDialog()
wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString);
if (goto_dialog.ShowModal() == wxID_OK)
{
- try
+ ExpressionParser parser;
+
+ auto value = goto_dialog.GetValue().ToStdString();
+ std::transform(value.begin(), value.end(), value.begin(), tolower);
+
+ debugger_addParserSymbols(parser);
+
+ // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234)
+ if (parser.IsConstantExpression("0x"+value))
{
- ExpressionParser parser;
-
- auto value = goto_dialog.GetValue().ToStdString();
- std::transform(value.begin(), value.end(), value.begin(), tolower);
- //parser.SetExpr(value);
-
- const auto module_count = RPLLoader_GetModuleCount();
- const auto module_list = RPLLoader_GetModuleList();
-
- std::vector module_tmp(module_count);
- for (int i = 0; i < module_count; i++)
- {
- const auto module = module_list[i];
- if (module)
- {
- module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
- parser.AddConstant(module->moduleName2, module_tmp[i]);
- }
- }
-
- double grp_tmp[32];
- PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot;
- for (int i = 0; i < 32; i++)
- {
- char var_name[32];
- sprintf(var_name, "r%d", i);
- grp_tmp[i] = ppc_snapshot.gpr[i];
- parser.AddConstant(var_name, grp_tmp[i]);
- }
-
- const auto result = (uint32)parser.Evaluate(value);
- debug_printf("goto eval result: %x\n", result);
+ const auto result = (uint32)parser.Evaluate("0x"+value);
m_lastGotoOffset = result;
CenterOffset(result);
}
- catch (const std::exception& ex)
+ else if (parser.IsConstantExpression(value))
{
- wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
+ const auto result = (uint32)parser.Evaluate(value);
+ m_lastGotoOffset = result;
+ CenterOffset(result);
+ }
+ else
+ {
+ try
+ {
+ const auto _ = (uint32)parser.Evaluate(value);
+ }
+ catch (const std::exception& ex)
+ {
+ wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
+ }
}
}
}
diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
index 1cbe1f8e..b93cf94e 100644
--- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
+++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
@@ -280,9 +280,7 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp);
void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread)
{
- cemuLog_log(LogType::Force, fmt::format("Dumping stack trace for thread {0:08x} LR: {1:08x}",
- memory_getVirtualOffsetFromPointer(thread),
- _swapEndianU32(thread->context.lr)));
+ cemuLog_log(LogType::Force, "Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr));
DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]));
}
diff --git a/src/imgui/imgui_extension.cpp b/src/imgui/imgui_extension.cpp
index 15e0de82..f1c072fa 100644
--- a/src/imgui/imgui_extension.cpp
+++ b/src/imgui/imgui_extension.cpp
@@ -104,6 +104,11 @@ void ImGui_PrecacheFonts()
}
}
+void ImGui_ClearFonts()
+{
+ g_imgui_fonts.clear();
+}
+
ImFont* ImGui_GetFont(float size)
{
const auto it = g_imgui_fonts.find((int)size);
diff --git a/src/imgui/imgui_extension.h b/src/imgui/imgui_extension.h
index de3bb5ac..ef2ee342 100644
--- a/src/imgui/imgui_extension.h
+++ b/src/imgui/imgui_extension.h
@@ -12,5 +12,6 @@ inline bool operator>=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x >= r
bool ImGui_BeginPadDistinct(const char* name, bool* p_open, ImGuiWindowFlags flags, bool pad);
void ImGui_PrecacheFonts();
+void ImGui_ClearFonts();
ImFont* ImGui_GetFont(float size);
void ImGui_UpdateWindowInformation(bool mainWindow);
\ No newline at end of file
diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt
index ecb88cd2..9f542371 100644
--- a/src/input/CMakeLists.txt
+++ b/src/input/CMakeLists.txt
@@ -44,18 +44,6 @@ add_library(CemuInput
set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>")
if(WIN32)
- # Native wiimote (Win32 only for now)
- target_sources(CemuInput PRIVATE
- api/Wiimote/WiimoteControllerProvider.h
- api/Wiimote/windows/WinWiimoteDevice.cpp
- api/Wiimote/windows/WinWiimoteDevice.h
- api/Wiimote/WiimoteControllerProvider.cpp
- api/Wiimote/WiimoteMessages.h
- api/Wiimote/NativeWiimoteController.h
- api/Wiimote/NativeWiimoteController.cpp
- api/Wiimote/WiimoteDevice.h
- )
-
# XInput
target_sources(CemuInput PRIVATE
api/XInput/XInputControllerProvider.cpp
@@ -73,6 +61,29 @@ if(WIN32)
)
endif()
+if (ENABLE_WIIMOTE)
+ target_sources(CemuInput PRIVATE
+ api/Wiimote/WiimoteControllerProvider.h
+ api/Wiimote/WiimoteControllerProvider.cpp
+ api/Wiimote/WiimoteMessages.h
+ api/Wiimote/NativeWiimoteController.h
+ api/Wiimote/NativeWiimoteController.cpp
+ api/Wiimote/WiimoteDevice.h
+ )
+ if (ENABLE_HIDAPI)
+ target_sources(CemuInput PRIVATE
+ api/Wiimote/hidapi/HidapiWiimote.cpp
+ api/Wiimote/hidapi/HidapiWiimote.h
+ )
+ elseif (WIN32)
+ target_sources(CemuInput PRIVATE
+ api/Wiimote/windows/WinWiimoteDevice.cpp
+ api/Wiimote/windows/WinWiimoteDevice.h
+ )
+ endif()
+endif ()
+
+
target_include_directories(CemuInput PUBLIC "../")
target_link_libraries(CemuInput PRIVATE
@@ -87,6 +98,9 @@ target_link_libraries(CemuInput PRIVATE
pugixml::pugixml
SDL2::SDL2
)
+if (ENABLE_HIDAPI)
+ target_link_libraries(CemuInput PRIVATE hidapi::hidapi)
+endif()
if (ENABLE_WXWIDGETS)
target_link_libraries(CemuInput PRIVATE wx::base wx::core)
diff --git a/src/input/InputManager.h b/src/input/InputManager.h
index 848c4810..345f7ba0 100644
--- a/src/input/InputManager.h
+++ b/src/input/InputManager.h
@@ -3,6 +3,9 @@
#if BOOST_OS_WINDOWS
#include "input/api/DirectInput/DirectInputControllerProvider.h"
#include "input/api/XInput/XInputControllerProvider.h"
+#endif
+
+#if defined(HAS_HIDAPI) || BOOST_OS_WINDOWS
#include "input/api/Wiimote/WiimoteControllerProvider.h"
#endif
diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp
index a742a9d3..0ebf88aa 100644
--- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp
+++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp
@@ -2,7 +2,9 @@
#include "input/api/Wiimote/NativeWiimoteController.h"
#include "input/api/Wiimote/WiimoteMessages.h"
-#if BOOST_OS_WINDOWS
+#ifdef HAS_HIDAPI
+#include "input/api/Wiimote/hidapi/HidapiWiimote.h"
+#elif BOOST_OS_WINDOWS
#include "input/api/Wiimote/windows/WinWiimoteDevice.h"
#endif
@@ -36,7 +38,7 @@ std::vector> WiimoteControllerProvider::get_cont
{
// only add unknown, connected devices to our list
const bool is_new_device = std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(),
- [&device](const auto& it) { return *it.device == *device; });
+ [device](const auto& it) { return *it.device == *device; });
if (is_new_device)
{
m_wiimotes.push_back(std::make_unique(device));
@@ -163,9 +165,7 @@ void WiimoteControllerProvider::reader_thread()
{
case kStatus:
{
-#ifdef WIIMOTE_DEBUG
- printf("WiimoteControllerProvider::read_thread: kStatus\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kStatus");
new_state.buttons = (*(uint16*)data) & (~0x60E0);
data += 2;
new_state.flags = *data;
@@ -183,9 +183,7 @@ void WiimoteControllerProvider::reader_thread()
if (HAS_FLAG(new_state.flags, kExtensionConnected))
{
-#ifdef WIIMOTE_DEBUG
- printf("\tExtension flag is set\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension flag is set");
if(new_state.m_extension.index() == 0)
request_extension(index);
}
@@ -199,9 +197,7 @@ void WiimoteControllerProvider::reader_thread()
break;
case kRead:
{
-#ifdef WIIMOTE_DEBUG
- printf("WiimoteControllerProvider::read_thread: kRead\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kRead");
new_state.buttons = (*(uint16*)data) & (~0x60E0);
data += 2;
const uint8 error_flag = *data & 0xF, size = (*data >> 4) + 1;
@@ -209,10 +205,9 @@ void WiimoteControllerProvider::reader_thread()
if (error_flag)
{
+
// 7 means that wiimote is already enabled or not available
-#ifdef WIIMOTE_DEBUG
- printf("Received error on data read 0x%x\n", error_flag);
-#endif
+ cemuLog_logDebug(LogType::Force,"Received error on data read {:#x}", error_flag);
continue;
}
@@ -220,9 +215,7 @@ void WiimoteControllerProvider::reader_thread()
data += 2;
if (address == (kRegisterCalibration & 0xFFFF))
{
-#ifdef WIIMOTE_DEBUG
- printf("Calibration received\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Calibration received");
cemu_assert(size == 8);
@@ -255,17 +248,10 @@ void WiimoteControllerProvider::reader_thread()
{
if (size == 0xf)
{
-#ifdef WIIMOTE_DEBUG
- printf("Extension type received but no extension connected\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension type received but no extension connected");
continue;
}
-
-#ifdef WIIMOTE_DEBUG
- printf("Extension type received\n");
-#endif
-
cemu_assert(size == 6);
auto be_type = *(betype*)data;
data += 6; // 48
@@ -274,42 +260,38 @@ void WiimoteControllerProvider::reader_thread()
switch (be_type.value())
{
case kExtensionNunchuck:
-#ifdef WIIMOTE_DEBUG
- printf("\tNunchuck\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: Nunchuck");
new_state.m_extension = NunchuckData{};
break;
case kExtensionClassic:
-#ifdef WIIMOTE_DEBUG
- printf("\tClassic\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic");
new_state.m_extension = ClassicData{};
break;
case kExtensionClassicPro:
- break;
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic Pro");
+ break;
case kExtensionGuitar:
- break;
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: Guitar");
+ break;
case kExtensionDrums:
- break;
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: Drums");
+ break;
case kExtensionBalanceBoard:
- break;
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: Balance Board");
+ break;
case kExtensionMotionPlus:
- //m_motion_plus = true;
-#ifdef WIIMOTE_DEBUG
- printf("\tMotion plus detected\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension Type Received: MotionPlus");
set_motion_plus(index, true);
new_state.m_motion_plus = MotionPlusData{};
break;
case kExtensionPartialyInserted:
-#ifdef WIIMOTE_DEBUG
- printf("\tExtension only partially inserted!\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension only partially inserted");
new_state.m_extension = {};
request_status(index);
break;
default:
- new_state.m_extension = {};
+ cemuLog_logDebug(LogType::Force,"Unknown extension: {:#x}", be_type.value());
+ new_state.m_extension = {};
break;
}
@@ -319,9 +301,7 @@ void WiimoteControllerProvider::reader_thread()
else if (address == (kRegisterExtensionCalibration & 0xFFFF))
{
cemu_assert(size == 0x10);
-#ifdef WIIMOTE_DEBUG
- printf("Extension calibration received\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension calibration received");
std::visit(
overloaded
{
@@ -337,9 +317,7 @@ void WiimoteControllerProvider::reader_thread()
std::array zero{};
if (memcmp(zero.data(), data, zero.size()) == 0)
{
-#ifdef WIIMOTE_DEBUG
- printf("\tExtension calibration data is zero!\n");
-#endif
+ cemuLog_logDebug(LogType::Force,"Extension calibration data is zero");
return;
}
@@ -372,15 +350,23 @@ void WiimoteControllerProvider::reader_thread()
}
else
{
-#ifdef WIIMOTE_DEBUG
- printf("Unhandled read data received\n");
-#endif
- continue;
+ cemuLog_logDebug(LogType::Force,"Unhandled read data received");
+ continue;
}
update_report = true;
}
break;
+ case kAcknowledge:
+ {
+ new_state.buttons = *(uint16*)data & (~0x60E0);
+ data += 2;
+ const auto report_id = *data++;
+ const auto error = *data++;
+ if (error)
+ cemuLog_logDebug(LogType::Force, "Error {:#x} from output report {:#x}", error, report_id);
+ break;
+ }
case kDataCore:
{
// 30 BB BB
@@ -476,10 +462,7 @@ void WiimoteControllerProvider::reader_thread()
orientation /= tmp;*/
mp.orientation = orientation;
-#ifdef WIIMOTE_DEBUG
- printf("\tmp: %.2lf %.2lf %.2lf\n", mp.orientation.x, mp.orientation.y,
- mp.orientation.z);
-#endif
+ cemuLog_logDebug(LogType::Force,"MotionPlus: {:.2f}, {:.2f} {:.2f}", mp.orientation.x, mp.orientation.y, mp.orientation.z);
},
[data](NunchuckData& nunchuck) mutable
{
@@ -553,12 +536,11 @@ void WiimoteControllerProvider::reader_thread()
zero3,
zero4
);
-#ifdef WIIMOTE_DEBUG
- printf("\tn: %d,%d | %lf - %lf | %.2lf %.2lf %.2lf\n", nunchuck.z, nunchuck.c,
- nunchuck.axis.x, nunchuck.axis.y,
- RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y),
- RadToDeg(nunchuck.acceleration.z));
-#endif
+ cemuLog_logDebug(LogType::Force,"Nunchuck: Z={}, C={} | {}, {} | {:.2f}, {:.2f}, {:.2f}",
+ nunchuck.z, nunchuck.c,
+ nunchuck.axis.x, nunchuck.axis.y,
+ RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y),
+ RadToDeg(nunchuck.acceleration.z));
},
[data](ClassicData& classic) mutable
{
@@ -592,11 +574,11 @@ void WiimoteControllerProvider::reader_thread()
classic.trigger = classic.raw_trigger;
classic.trigger /= 31.0f;
-#ifdef WIIMOTE_DEBUG
- printf("\tc: %d | %lf - %lf | %lf - %lf | %lf - %lf\n", classic.buttons,
- classic.left_axis.x, classic.left_axis.y, classic.right_axis.x,
- classic.right_axis.y, classic.trigger.x, classic.trigger.y);
-#endif
+ cemuLog_logDebug(LogType::Force,"Classic Controller: Buttons={:b} | {}, {} | {}, {} | {}, {}",
+ classic.buttons, classic.left_axis.x, classic.left_axis.y,
+ classic.right_axis.x, classic.right_axis.y, classic.trigger.x,
+ classic.trigger.y);
+
}
}, new_state.m_extension);
@@ -609,9 +591,7 @@ void WiimoteControllerProvider::reader_thread()
break;
}
default:
-#ifdef WIIMOTE_DEBUG
- printf("unhandled input packet id %d for wiimote\n", data[0]);
-#endif
+ cemuLog_logDebug(LogType::Force,"unhandled input packet id {} for wiimote {}", id, index);
}
// update motion data
@@ -694,7 +674,6 @@ void WiimoteControllerProvider::parse_acceleration(WiimoteState& wiimote_state,
tmp -= calib.zero;
acceleration = (wiimote_state.m_acceleration / tmp);
- //printf("%d, %d, %d\n", (int)m_acceleration.x, (int)m_acceleration.y, (int)m_acceleration.z);
const float pi_2 = (float)std::numbers::pi / 2.0f;
wiimote_state.m_roll = std::atan2(acceleration.z, acceleration.x) - pi_2;
}
@@ -713,7 +692,6 @@ void WiimoteControllerProvider::rotate_ir(WiimoteState& wiimote_state)
i++;
if (!dot.visible)
continue;
- //printf("%d:\t%.02lf | %.02lf\n", i, dot.pos.x, dot.pos.y);
// move to center, rotate and move back
dot.pos -= 0.5f;
dot.pos.x = (dot.pos.x * cos) + (dot.pos.y * (-sin));
@@ -973,9 +951,7 @@ void WiimoteControllerProvider::update_report_type(size_t index)
else
report_type = kDataCore;
-#ifdef WIIMOTE_DEBUG
- printf("Setting report type to %d\n", report_type);
-#endif
+ cemuLog_logDebug(LogType::Force,"Setting report type to {}", report_type);
send_packet(index, {kType, 0x04, report_type});
state.ir_camera.mode = set_ir_camera(index, true);
diff --git a/src/input/api/Wiimote/WiimoteMessages.h b/src/input/api/Wiimote/WiimoteMessages.h
index 712c3a5c..32dd4658 100644
--- a/src/input/api/Wiimote/WiimoteMessages.h
+++ b/src/input/api/Wiimote/WiimoteMessages.h
@@ -8,7 +8,7 @@ enum InputReportId : uint8
kStatus = 0x20,
kRead = 0x21,
- kWrite = 0x22,
+ kAcknowledge = 0x22,
kDataCore = 0x30,
kDataCoreAcc = 0x31,
diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp
new file mode 100644
index 00000000..7baad55d
--- /dev/null
+++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp
@@ -0,0 +1,55 @@
+#include "HidapiWiimote.h"
+
+static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e;
+static constexpr uint16 WIIMOTE_PRODUCT_ID = 0x0306;
+static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330;
+static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22;
+
+HidapiWiimote::HidapiWiimote(hid_device* dev, uint64_t identifier)
+ : m_handle(dev), m_identifier(identifier) {
+
+}
+
+bool HidapiWiimote::write_data(const std::vector &data) {
+ return hid_write(m_handle, data.data(), data.size()) >= 0;
+}
+
+std::optional> HidapiWiimote::read_data() {
+ std::array read_data{};
+ const auto result = hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH);
+ if (result < 0)
+ return {};
+ return {{read_data.cbegin(), read_data.cbegin() + result}};
+}
+
+std::vector HidapiWiimote::get_devices() {
+ std::vector wiimote_devices;
+ hid_init();
+ const auto device_enumeration = hid_enumerate(WIIMOTE_VENDOR_ID, 0x0);
+
+ for (auto it = device_enumeration; it != nullptr; it = it->next){
+ if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID)
+ continue;
+ auto dev = hid_open_path(it->path);
+ if (!dev){
+ cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr)));
+ }
+ else {
+ // Enough to have a unique id for each device within a session
+ uint64_t id = (static_cast(it->interface_number) << 32) |
+ (static_cast(it->usage_page) << 16) |
+ (it->usage);
+ wiimote_devices.push_back(std::make_shared(dev, id));
+ }
+ }
+ hid_free_enumeration(device_enumeration);
+ return wiimote_devices;
+}
+
+bool HidapiWiimote::operator==(WiimoteDevice& o) const {
+ return m_identifier == static_cast(o).m_identifier;
+}
+
+HidapiWiimote::~HidapiWiimote() {
+ hid_close(m_handle);
+}
diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h
new file mode 100644
index 00000000..6bd90dac
--- /dev/null
+++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include
+#include
+
+class HidapiWiimote : public WiimoteDevice {
+public:
+ HidapiWiimote(hid_device* dev, uint64_t identifier);
+ ~HidapiWiimote() override;
+
+ bool write_data(const std::vector &data) override;
+ std::optional> read_data() override;
+ bool operator==(WiimoteDevice& o) const override;
+
+ static std::vector get_devices();
+
+private:
+ hid_device* m_handle;
+ uint64_t m_identifier;
+
+};
+
+using WiimoteDevice_t = HidapiWiimote;
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 2d281d88..f7a66bf9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3,16 +3,16 @@
#include "util/crypto/aes128.h"
#include "gui/MainWindow.h"
#include "Cafe/OS/RPL/rpl.h"
-#include "Cafe/OS/RPL/rpl_symbol_storage.h"
#include "Cafe/OS/libs/gx2/GX2.h"
+#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
+#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "config/CemuConfig.h"
#include "config/NetworkSettings.h"
-#include "gui/CemuApp.h"
-#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "config/LaunchSettings.h"
-#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
+#include "input/InputManager.h"
+#include "gui/CemuApp.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/TitleList/TitleList.h"
@@ -59,67 +59,6 @@ std::atomic_bool g_isGPUInitFinished = false;
std::wstring executablePath;
-void logCPUAndMemoryInfo()
-{
- std::string cpuName = g_CPUFeatures.GetCPUName();
- if (!cpuName.empty())
- cemuLog_log(LogType::Force, "CPU: {}", cpuName);
-
- #if BOOST_OS_WINDOWS
- MEMORYSTATUSEX statex;
- statex.dwLength = sizeof(statex);
- GlobalMemoryStatusEx(&statex);
- uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL);
- cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB);
- #elif BOOST_OS_LINUX
- struct sysinfo info {};
- sysinfo(&info);
- cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL));
- #elif BOOST_OS_MACOS
- int64_t totalRam;
- size_t size = sizeof(totalRam);
- int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0);
- if (result == 0)
- cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL));
- #endif
-}
-
-bool g_running_in_wine = false;
-bool IsRunningInWine()
-{
- return g_running_in_wine;
-}
-
-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)
- {
- g_running_in_wine = true;
- cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version());
- }
- #else
- g_running_in_wine = false;
- #endif
-}
-
-void infoLog_cemuStartup()
-{
- cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING);
- cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base);
- cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath()));
- // check for wine version
- checkForWine();
- // CPU and RAM info
- logCPUAndMemoryInfo();
- cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList());
-}
-
// some implementations of _putenv dont copy the string and instead only store a pointer
// thus we use a helper to keep a permanent copy
std::vector sPutEnvMap;
@@ -158,16 +97,9 @@ void reconfigureVkDrivers()
_putenvSafe("DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1");
}
-void mainEmulatorCommonInit()
+void WindowsInitCwd()
{
- reconfigureGLDrivers();
- reconfigureVkDrivers();
- // crypto init
- AES128_init();
- // init PPC timer (call this as early as possible because it measures frequency of RDTSC using an asynchronous thread over 3 seconds)
- PPCTimer_init();
-
-#if BOOST_OS_WINDOWS
+ #if BOOST_OS_WINDOWS
executablePath.resize(4096);
int i = GetModuleFileName(NULL, executablePath.data(), executablePath.size());
if(i >= 0)
@@ -175,24 +107,50 @@ void mainEmulatorCommonInit()
else
executablePath.clear();
SetCurrentDirectory(executablePath.c_str());
-
// set high priority
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
-#endif
+ #endif
+}
+
+void CemuCommonInit()
+{
+ reconfigureGLDrivers();
+ reconfigureVkDrivers();
+ // crypto init
+ AES128_init();
+ // init PPC timer
+ // call this as early as possible because it measures frequency of RDTSC using an asynchronous thread over 3 seconds
+ PPCTimer_init();
+
+ WindowsInitCwd();
ExceptionHandler_Init();
// read config
g_config.Load();
if (NetworkConfig::XMLExists())
- n_config.Load();
- // symbol storage
- rplSymbolStorage_init();
- // static initialization
- IAudioAPI::InitializeStatic();
- IAudioInputAPI::InitializeStatic();
- // load graphic packs (must happen before config is loaded)
- GraphicPack2::LoadAll();
- // initialize file system
- fsc_init();
+ n_config.Load();
+ // parallelize expensive init code
+ std::future futureInitAudioAPI = std::async(std::launch::async, []{ IAudioAPI::InitializeStatic(); IAudioInputAPI::InitializeStatic(); return 0; });
+ std::future futureInitGraphicPacks = std::async(std::launch::async, []{ GraphicPack2::LoadAll(); return 0; });
+ InputManager::instance().load();
+ futureInitAudioAPI.wait();
+ futureInitGraphicPacks.wait();
+ // init Cafe system
+ CafeSystem::Initialize();
+ // init title list
+ CafeTitleList::Initialize(ActiveSettings::GetUserDataPath("title_list_cache.xml"));
+ for (auto& it : GetConfig().game_paths)
+ CafeTitleList::AddScanPath(it);
+ fs::path mlcPath = ActiveSettings::GetMlcPath();
+ if (!mlcPath.empty())
+ CafeTitleList::SetMLCPath(mlcPath);
+ CafeTitleList::Refresh();
+ // init save list
+ CafeSaveList::Initialize();
+ if (!mlcPath.empty())
+ {
+ CafeSaveList::SetMLCPath(mlcPath);
+ CafeSaveList::Refresh();
+ }
}
void mainEmulatorLLE();
@@ -218,35 +176,7 @@ int mainEmulatorHLE()
#ifdef CEMU_DEBUG_ASSERT
unitTests();
#endif
- // init common
- mainEmulatorCommonInit();
- // reserve memory (no allocations yet)
- memory_init();
- // init ppc core
- PPCCore_init();
- // log Cemu startup info
- infoLog_cemuStartup();
- // init RPL loader
- RPLLoader_InitState();
- // init IOSU components
- iosuCrypto_init();
- // init Cafe system (todo - the stuff above should be part of this too)
- CafeSystem::Initialize();
- // init title list
- CafeTitleList::Initialize(ActiveSettings::GetUserDataPath("title_list_cache.xml"));
- for (auto& it : GetConfig().game_paths)
- CafeTitleList::AddScanPath(it);
- fs::path mlcPath = ActiveSettings::GetMlcPath();
- if (!mlcPath.empty())
- CafeTitleList::SetMLCPath(mlcPath);
- CafeTitleList::Refresh();
- // init save list
- CafeSaveList::Initialize();
- if (!mlcPath.empty())
- {
- CafeSaveList::SetMLCPath(mlcPath);
- CafeSaveList::Refresh();
- }
+ CemuCommonInit();
return 0;
}
diff --git a/src/mainLLE.cpp b/src/mainLLE.cpp
index 2b7c18a0..c32b56ec 100644
--- a/src/mainLLE.cpp
+++ b/src/mainLLE.cpp
@@ -4,7 +4,7 @@
#include "gui/guiWrapper.h"
#include "Common/FileStream.h"
-void mainEmulatorCommonInit();
+void CemuCommonInit();
typedef struct
{
@@ -33,7 +33,7 @@ void loadPPCBootrom()
void mainEmulatorLLE()
{
- mainEmulatorCommonInit();
+ CemuCommonInit();
// memory init
memory_initPhysicalLayout();
diff --git a/src/util/containers/RangeStore.h b/src/util/containers/RangeStore.h
index e20688e6..211bb20f 100644
--- a/src/util/containers/RangeStore.h
+++ b/src/util/containers/RangeStore.h
@@ -122,6 +122,15 @@ public:
return false;
}
+ void clear()
+ {
+ for(auto& bucket : rangeBuckets)
+ {
+ while(!bucket.list_ranges.empty())
+ deleteRange(bucket.list_ranges[0]);
+ }
+ }
+
private:
typedef struct
{
diff --git a/src/util/helpers/StringHelpers.h b/src/util/helpers/StringHelpers.h
index cfe63ead..24e70d49 100644
--- a/src/util/helpers/StringHelpers.h
+++ b/src/util/helpers/StringHelpers.h
@@ -108,5 +108,94 @@ namespace StringHelpers
}
return parsedLen;
}
+
+ class StringLineIterator
+ {
+ public:
+ class Iterator
+ {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = std::string_view;
+ using difference_type = std::ptrdiff_t;
+ using pointer = const std::string_view*;
+ using reference = const std::string_view&;
+
+ Iterator(std::string_view str, sint32 pos) : m_str(str), m_pos(pos)
+ {
+ update_line();
+ }
+
+ reference operator*() const
+ {
+ return m_line;
+ }
+
+ pointer operator->() const
+ {
+ return &m_line;
+ }
+
+ Iterator& operator++()
+ {
+ m_pos = m_nextPos;
+ update_line();
+ return *this;
+ }
+
+ friend bool operator==(const Iterator& lhs, const Iterator& rhs)
+ {
+ return lhs.m_str.data() == rhs.m_str.data() && lhs.m_pos == rhs.m_pos;
+ }
+
+ friend bool operator!=(const Iterator& lhs, const Iterator& rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+ private:
+ void update_line()
+ {
+ if (m_pos >= m_str.size())
+ {
+ m_pos = -1;
+ m_line = {};
+ return;
+ }
+ auto pos = m_str.find('\n', m_pos);
+ m_nextPos = pos != std::string_view::npos ? pos : -1;
+ if(m_nextPos < 0)
+ m_line = m_str.substr(m_pos, std::string::npos);
+ else
+ {
+ m_line = m_str.substr(m_pos, m_nextPos - m_pos);
+ ++m_nextPos; // skip \n
+ }
+ while (!m_line.empty() && m_line.back() == '\r')
+ m_line.remove_suffix(1);
+ }
+
+ std::string_view m_str;
+ sint32 m_pos;
+ sint32 m_nextPos;
+ std::string_view m_line;
+ };
+
+ StringLineIterator(std::string_view str) : m_str(str) {}
+ StringLineIterator(std::span str) : m_str((const char*)str.data(), str.size()) {}
+
+ Iterator begin() const
+ {
+ return Iterator{m_str, 0 };
+ }
+
+ Iterator end() const
+ {
+ return Iterator{m_str, -1 };
+ }
+
+ private:
+ std::string_view m_str;
+ };
};
diff --git a/vcpkg.json b/vcpkg.json
index d0facf8b..940ed748 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -26,6 +26,10 @@
"boost-static-string",
"boost-random",
"fmt",
+ {
+ "name": "hidapi",
+ "platform": "!windows"
+ },
"libpng",
"glm",
{