From 2200cc0ddf61d345c774daccdbadd92a00133890 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:54:07 +0200 Subject: [PATCH] Initial support for title switching + better Wii U menu compatibility (#907) --- src/Cafe/Account/Account.cpp | 16 +- src/Cafe/CMakeLists.txt | 8 +- src/Cafe/CafeSystem.cpp | 229 +++++++--- src/Cafe/CafeSystem.h | 16 + src/Cafe/Filesystem/fsc.cpp | 36 +- src/Cafe/Filesystem/fsc.h | 1 - src/Cafe/Filesystem/fscDeviceHostFS.cpp | 7 - src/Cafe/HW/Espresso/PPCScheduler.cpp | 5 - src/Cafe/HW/Espresso/PPCState.h | 1 - .../HW/Espresso/Recompiler/PPCRecompiler.cpp | 56 ++- .../HW/Espresso/Recompiler/PPCRecompiler.h | 1 + src/Cafe/HW/Latte/Core/Latte.h | 3 +- src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteBufferCache.cpp | 69 ++- src/Cafe/HW/Latte/Core/LatteBufferCache.h | 1 + .../HW/Latte/Core/LatteCommandProcessor.cpp | 4 +- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 1 + src/Cafe/HW/Latte/Core/LatteShader.cpp | 29 +- src/Cafe/HW/Latte/Core/LatteShader.h | 6 +- src/Cafe/HW/Latte/Core/LatteShaderCache.cpp | 63 ++- src/Cafe/HW/Latte/Core/LatteThread.cpp | 19 +- .../Renderer/OpenGL/RendererShaderGL.cpp | 33 +- .../Latte/Renderer/OpenGL/RendererShaderGL.h | 3 +- src/Cafe/HW/Latte/Renderer/Renderer.cpp | 1 + .../Renderer/Vulkan/RendererShaderVk.cpp | 6 + .../Latte/Renderer/Vulkan/RendererShaderVk.h | 3 +- .../Vulkan/VulkanPipelineStableCache.cpp | 9 + .../Vulkan/VulkanPipelineStableCache.h | 1 + src/Cafe/HW/MMU/MMU.cpp | 11 +- src/Cafe/HW/MMU/MMU.h | 1 + src/Cafe/IOSU/ODM/iosu_odm.cpp | 155 +++++++ src/Cafe/IOSU/ODM/iosu_odm.h | 10 + src/Cafe/IOSU/PDM/iosu_pdm.cpp | 3 +- src/Cafe/IOSU/legacy/iosu_act.cpp | 2 +- src/Cafe/IOSU/legacy/iosu_mcp.cpp | 13 +- src/Cafe/IOSU/legacy/iosu_mcp.h | 3 +- src/Cafe/IOSU/legacy/iosu_nim.cpp | 20 +- src/Cafe/OS/RPL/rpl.cpp | 6 +- src/Cafe/OS/RPL/rpl_symbol_storage.cpp | 1 + src/Cafe/OS/common/OSCommon.cpp | 2 + src/Cafe/OS/libs/coreinit/coreinit.cpp | 26 +- src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp | 24 +- src/Cafe/OS/libs/coreinit/coreinit_Alarm.h | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Init.cpp | 21 +- src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp | 138 ++++-- src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp | 18 +- src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp | 122 ++++- src/Cafe/OS/libs/coreinit/coreinit_Misc.h | 4 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 43 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 1 + src/Cafe/OS/libs/gx2/GX2_Command.cpp | 12 +- src/Cafe/OS/libs/gx2/GX2_Command.h | 3 +- src/Cafe/OS/libs/gx2/GX2_Event.cpp | 11 + src/Cafe/OS/libs/gx2/GX2_Event.h | 3 +- src/Cafe/OS/libs/gx2/GX2_Misc.cpp | 3 + src/Cafe/OS/libs/nn_acp/nn_acp.cpp | 14 +- src/Cafe/OS/libs/nn_acp/nn_acp.h | 1 + src/Cafe/OS/libs/nn_boss/nn_boss.cpp | 10 +- src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp | 88 +++- src/Cafe/OS/libs/nn_nim/nn_nim.cpp | 52 ++- src/Cafe/OS/libs/nn_olv/nn_olv.cpp | 12 +- src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp | 311 +++++++++++++ src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h | 430 ++++++++++++++++++ src/Cafe/OS/libs/nn_save/nn_save.cpp | 17 + src/Cafe/OS/libs/nn_save/nn_save.h | 1 + src/Cafe/OS/libs/nn_spm/nn_spm.cpp | 150 ++++++ src/Cafe/OS/libs/nn_spm/nn_spm.h | 9 + src/Cafe/OS/libs/nn_uds/nn_uds.cpp | 2 +- src/Cafe/OS/libs/snd_core/ax.h | 2 + src/Cafe/OS/libs/snd_core/ax_exports.cpp | 24 +- src/Cafe/OS/libs/snd_core/ax_internal.h | 3 +- src/Cafe/OS/libs/snd_core/ax_ist.cpp | 1 + src/Cafe/OS/libs/snd_core/ax_voice.cpp | 42 +- src/Cafe/OS/libs/sysapp/sysapp.cpp | 102 ++++- src/Cafe/TitleList/BaseInfo.cpp | 68 --- src/Cafe/TitleList/BaseInfo.h | 37 -- src/Cafe/TitleList/GameInfo.h | 14 +- src/Cafe/TitleList/TitleInfo.cpp | 49 +- src/Cafe/TitleList/TitleInfo.h | 15 +- src/Cafe/TitleList/TitleList.cpp | 6 + src/gui/CemuApp.cpp | 7 +- src/gui/GameUpdateWindow.h | 2 +- src/gui/MainWindow.cpp | 251 +++++----- src/gui/MainWindow.h | 55 ++- src/gui/PadViewFrame.cpp | 8 + src/gui/PadViewFrame.h | 1 + src/gui/canvas/OpenGLCanvas.cpp | 2 - src/gui/canvas/VulkanCanvas.cpp | 5 +- src/gui/components/wxGameList.cpp | 10 +- src/imgui/imgui_extension.cpp | 5 + src/imgui/imgui_extension.h | 1 + src/main.cpp | 99 ++-- src/mainLLE.cpp | 4 +- src/util/containers/RangeStore.h | 9 + src/util/helpers/StringHelpers.h | 89 ++++ 95 files changed, 2549 insertions(+), 746 deletions(-) create mode 100644 src/Cafe/IOSU/ODM/iosu_odm.cpp create mode 100644 src/Cafe/IOSU/ODM/iosu_odm.h create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h create mode 100644 src/Cafe/OS/libs/nn_spm/nn_spm.cpp create mode 100644 src/Cafe/OS/libs/nn_spm/nn_spm.h delete mode 100644 src/Cafe/TitleList/BaseInfo.cpp delete mode 100644 src/Cafe/TitleList/BaseInfo.h 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..59b6aa42 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,8 @@ 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_pdm/nn_pdm.cpp OS/libs/nn_pdm/nn_pdm.h OS/libs/nn_save/nn_save.cpp @@ -464,8 +470,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 8ca86adf..6726a62c 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -45,8 +45,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 +62,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" @@ -284,7 +286,7 @@ struct static_assert(sizeof(SharedDataEntry) == 0x1C); -uint32 loadSharedData() +uint32 LoadSharedData() { // check if font files are dumped bool hasAllShareddataFiles = true; @@ -421,49 +423,40 @@ 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; + // 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 + fsc_init(); + memory_init(); + PPCCore_init(); + RPLLoader_InitState(); // 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(); @@ -476,6 +469,7 @@ namespace CafeSystem iosu::boss_init(); iosu::nim::Initialize(); iosu::pdm::Initialize(); + iosu::odm::Initialize(); // init Cafe OS avm::Initialize(); drmapp::Initialize(); @@ -493,14 +487,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()) { @@ -559,10 +585,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) @@ -597,26 +646,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; } @@ -655,10 +715,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; } @@ -703,6 +764,13 @@ namespace CafeSystem return sGameInfo_ForegroundTitle.GetVersion(); } + uint32 GetForegroundTitleSDKVersion() + { + if (sLaunchModeIsStandalone) + return 999999; + return sGameInfo_ForegroundTitle.GetSDKVersion(); + } + CafeConsoleRegion GetForegroundTitleRegion() { if (sLaunchModeIsStandalone) @@ -740,6 +808,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() { @@ -756,39 +844,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; } @@ -838,10 +919,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)) @@ -868,6 +946,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; @@ -878,4 +966,9 @@ namespace CafeSystem return currentUpdatedApplicationHash; } + void RequestRecreateCanvas() + { + s_implementation->CafeRecreateCanvas(); + } + } diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index 236cf44a..336c2f40 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(); @@ -37,6 +51,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 b2500667..031f2fb2 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 2854a301..09c1f508 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -205,7 +205,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 f63d2920..85a04afe 100644 --- a/src/Cafe/Filesystem/fscDeviceHostFS.cpp +++ b/src/Cafe/Filesystem/fscDeviceHostFS.cpp @@ -289,13 +289,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/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..ed2116d0 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -173,4 +173,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..1d9adfe3 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<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/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/MMU/MMU.cpp b/src/Cafe/HW/MMU/MMU.cpp index 1a776d6b..04ee8877 100644 --- a/src/Cafe/HW/MMU/MMU.cpp +++ b/src/Cafe/HW/MMU/MMU.cpp @@ -103,7 +103,7 @@ void MMURange::mapMem() void MMURange::unmapMem() { - cemu_assert_debug(false); + MemMapper::FreeMemory(memory_base + baseAddress, size, true); m_isMapped = false; } @@ -196,6 +196,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 222e8c0e..794785fa 100644 --- a/src/Cafe/HW/MMU/MMU.h +++ b/src/Cafe/HW/MMU/MMU.h @@ -2,6 +2,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..fa683c1e 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); } 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..a784e593 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); } } @@ -552,6 +587,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 b97f896e..59bd034e 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 @@ -198,8 +196,6 @@ namespace coreinit return __currentCoreThread[currentInstance->spr.UPIR]; } - MPTR funcPtr_threadEntry = 0; - void threadEntry(PPCInterpreter_t* hCPU) { OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(hCPU); @@ -223,12 +219,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 @@ -236,7 +226,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) @@ -257,10 +247,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(); @@ -277,8 +263,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; @@ -360,8 +346,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 @@ -1018,6 +1004,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 @@ -1202,7 +1200,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); @@ -1384,9 +1382,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 7c1e618b..e2f5bef2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -606,6 +606,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..50036249 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -4,6 +4,7 @@ #include "nn_olv_UploadCommunityTypes.h" #include "nn_olv_DownloadCommunityTypes.h" #include "nn_olv_UploadFavoriteTypes.h" +#include "nn_olv_PostTypes.h" #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" @@ -288,15 +289,16 @@ namespace nn 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", "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); 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..5056f2fe --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp @@ -0,0 +1,311 @@ +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" +#include "nn_olv_PostTypes.h" +#include "Cemu/ncrypto/ncrypto.h" // for base64 decoder +#include "util/helpers/helpers.h" +#include +#include + +namespace nn +{ + namespace olv + { + + 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 0) + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT); + } + if(tokenNode = xmlNode.child("feeling_id"); tokenNode) + { + obj.feeling = ConvertString(tokenNode.child_value()); + if(obj.feeling < 0 || obj.feeling >= 5) + { + cemuLog_log(LogType::Force, "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, "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, "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()); + } + 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; + } + + 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); + + // 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); + + } + + } +} \ 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..3ca4f87e --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h @@ -0,0 +1,430 @@ +#pragma once +#include + +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; + uint32 outputLength = std::min(_this->bodyTextLength, maxLength); + olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, _this->bodyTextLength); + 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; + } + + // todo: + // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi + // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + // 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); + } + + 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/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..b8b781a4 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -22,6 +22,7 @@ struct ParsedAppXml uint16 title_version; uint32 app_type; uint32 group_id; + uint32 sdk_version; }; struct ParsedCosXml @@ -69,6 +70,7 @@ public: std::string subPath; // for WUA uint64 titleId; uint16 titleVersion; + uint32 sdkVersion; std::string titleName; CafeConsoleRegion region; uint32 group_id; @@ -115,12 +117,23 @@ public: return m_uid == rhs.m_uid; } + bool IsSystemDataTitle() const + { + if(!IsValid()) + return false; + uint32 appType = GetAppType(); + if(appType == 0) + return false; // not a valid app_type, but handle this in case some users use placeholder .xml data with fields zeroed-out + return ((appType>>24)&0x80) == 0; + } + // 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; diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index deb4564f..7f42d17c 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -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/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.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 142d5ef3..95d9bcdb 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 @@ -161,8 +159,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) @@ -237,6 +237,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 @@ -290,6 +292,7 @@ MainWindow::MainWindow() { gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_main, this); g_mainFrame = this; + CafeSystem::SetImplementation(this); RecreateMenu(); SetClientSize(1280, 720); @@ -330,62 +333,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; } @@ -396,13 +382,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()) @@ -435,7 +455,7 @@ void MainWindow::OnClose(wxCloseEvent& event) event.Skip(); - CafeSystem::ShutdownTitle(); + CafeSystem::Shutdown(); DestroyCanvas(); } @@ -558,36 +578,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) @@ -675,10 +672,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(); } } @@ -1547,7 +1546,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); @@ -1580,14 +1591,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) @@ -2048,9 +2063,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) { @@ -2084,7 +2099,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 } @@ -2094,7 +2109,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()) @@ -2106,23 +2121,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); @@ -2137,12 +2138,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(); @@ -2151,12 +2151,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")); @@ -2164,7 +2163,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); @@ -2174,12 +2173,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")); @@ -2216,7 +2215,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")); @@ -2251,7 +2250,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")); @@ -2271,8 +2270,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); @@ -2343,31 +2342,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(); @@ -2378,7 +2355,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); @@ -2395,3 +2371,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 735f73af..7597c2b2 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(); @@ -151,6 +155,11 @@ private: void OnTimer(wxTimerEvent& event); + // CafeSystem implementation + void CafeRecreateCanvas() override; + + void OnRequestRecreateCanvas(wxCommandEvent& event); + wxRect GetDesktopRect(); MemorySearcherTool* m_toolWindow = nullptr; @@ -183,8 +192,6 @@ private: void LoadSettings(); void SaveSettings(); - std::string GetRegionString(uint32 region) const; - void OnGraphicWindowClose(wxCloseEvent& event); void OnGraphicWindowOpen(wxTitleIdEvent& event); @@ -195,42 +202,40 @@ 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{}; // 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..4f3165ab 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(); 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/main.cpp b/src/main.cpp index 2d281d88..032c23bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,14 +5,15 @@ #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" @@ -113,7 +114,6 @@ 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(); @@ -158,16 +158,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 +168,54 @@ 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(); + 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(); + // 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(); + // log Cemu startup info + infoLog_cemuStartup(); + // 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 +241,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; + }; };