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 01/16] 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; + }; }; From 34ff9da097b2982cb5d7191ac71f05911734204b Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Mon, 31 Jul 2023 01:15:44 +0200 Subject: [PATCH 02/16] cmake: exclude fmt10 (#921) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 26d676bc..57615e86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,7 +114,7 @@ find_package(ZLIB REQUIRED) find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED) find_package(glm REQUIRED) -find_package(fmt 9.1.0 REQUIRED) +find_package(fmt 9.1.0...<10 REQUIRED) find_package(PNG REQUIRED) # glslang versions older than 11.11.0 define targets without a namespace From 7111cbb1033ad47bd7696dc5225dc0f4ae13e08f Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 3 Aug 2023 12:54:16 +0000 Subject: [PATCH 03/16] Quote and escape desktop entry executable path (#917) --- src/gui/components/wxGameList.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 4f3165ab..69f74870 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1287,17 +1287,18 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { } } } + // 'Icon' accepts spaces in file name, does not accept quoted file paths + // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths const auto desktop_entry_string = fmt::format("[Desktop Entry]\n" - "Name={}\n" - "Comment=Play {} on Cemu\n" - "Exec={} --title-id {:016x}\n" - "Icon={}\n" + "Name={0}\n" + "Comment=Play {0} on Cemu\n" + "Exec={1:?} --title-id {2:016x}\n" + "Icon={3}\n" "Terminal=false\n" "Type=Application\n" "Categories=Game;", title_name, - title_name, _pathToUtf8(exe_path), title_id, _pathToUtf8(icon_path.value_or(""))); @@ -1339,4 +1340,4 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { } #endif } -#endif \ No newline at end of file +#endif From 1d1e1e781b06aad34d616e13ccb47485f3c0db14 Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Thu, 3 Aug 2023 14:16:22 +0100 Subject: [PATCH 04/16] Vulkan: Retry instance creation if validation layer is not present (#909) --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 95386284..cfe7d3f4 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -350,7 +350,15 @@ VulkanRenderer::VulkanRenderer() create_info.ppEnabledLayerNames = m_layerNames.data(); create_info.enabledLayerCount = m_layerNames.size(); - if ((err = vkCreateInstance(&create_info, nullptr, &m_instance)) != VK_SUCCESS) + err = vkCreateInstance(&create_info, nullptr, &m_instance); + + if (err == VK_ERROR_LAYER_NOT_PRESENT) { + cemuLog_log(LogType::Force, "Failed to enable vulkan validation (VK_LAYER_KHRONOS_validation)"); + create_info.enabledLayerCount = 0; + err = vkCreateInstance(&create_info, nullptr, &m_instance); + } + + if (err != VK_SUCCESS) throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err)); if (!InitializeInstanceVulkan(m_instance)) From 651e5336b465af11ac892f2f3ea3577665307819 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Thu, 3 Aug 2023 06:45:11 -0700 Subject: [PATCH 05/16] debugger: Add logging breakpoint + misc fixes (#927) --- src/Cafe/HW/Espresso/Debugger/Debugger.cpp | 83 ++++++++++---- src/Cafe/HW/Espresso/Debugger/Debugger.h | 6 +- src/gui/debugger/BreakpointWindow.cpp | 107 ++++++++++++------ src/gui/debugger/BreakpointWindow.h | 3 +- src/gui/debugger/DebuggerWindow2.cpp | 11 +- src/gui/debugger/DisasmCtrl.cpp | 51 ++++----- src/gui/debugger/DumpCtrl.cpp | 60 ++++------ .../DebugPPCThreadsWindow.cpp | 4 +- 8 files changed, 193 insertions(+), 132 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index b6417080..0883c436 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -1,5 +1,6 @@ #include "gui/guiWrapper.h" #include "Debugger.h" +#include "Cafe/OS/RPL/rpl_structs.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cemu/ExpressionParser/ExpressionParser.h" @@ -74,7 +75,7 @@ uint32 debugger_getAddressOriginalOpcode(uint32 address) auto bpItr = debugger_getFirstBP(address); while (bpItr) { - if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_ONE_SHOT) + if (bpItr->isExecuteBP()) return bpItr->originalOpcodeValue; bpItr = bpItr->next; } @@ -121,32 +122,23 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore) } } -void debugger_createExecuteBreakpoint(uint32 address) +void debugger_createCodeBreakpoint(uint32 address, uint8 bpType) { // check if breakpoint already exists auto existingBP = debugger_getFirstBP(address); - if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_NORMAL)) + if (existingBP && debuggerBPChain_hasType(existingBP, bpType)) return; // breakpoint already exists // get original opcode at address uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); // init breakpoint object - DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_NORMAL, true); + DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, bpType, true); debuggerBPChain_add(address, bp); debugger_updateExecutionBreakpoint(address); } -void debugger_createSingleShotExecuteBreakpoint(uint32 address) +void debugger_createExecuteBreakpoint(uint32 address) { - // check if breakpoint already exists - auto existingBP = debugger_getFirstBP(address); - if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_ONE_SHOT)) - return; // breakpoint already exists - // get original opcode at address - uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); - // init breakpoint object - DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_ONE_SHOT, true); - debuggerBPChain_add(address, bp); - debugger_updateExecutionBreakpoint(address); + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } namespace coreinit @@ -218,7 +210,7 @@ void debugger_handleSingleStepException(uint64 dr6) } if (catchBP) { - debugger_createSingleShotExecuteBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4); + debugger_createCodeBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT); } } @@ -250,7 +242,7 @@ void debugger_handleEntryBreakpoint(uint32 address) if (!debuggerState.breakOnEntry) return; - debugger_createExecuteBreakpoint(address); + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } void debugger_deleteBreakpoint(DebuggerBreakpoint* bp) @@ -298,10 +290,12 @@ void debugger_toggleExecuteBreakpoint(uint32 address) { // delete existing breakpoint debugger_deleteBreakpoint(existingBP); - return; } - // create new - debugger_createExecuteBreakpoint(address); + else + { + // create new breakpoint + debugger_createExecuteBreakpoint(address); + } } void debugger_forceBreak() @@ -327,7 +321,7 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b { if (bpItr == bp) { - if (bpItr->bpType == DEBUGGER_BP_T_NORMAL) + if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING) { bp->enabled = state; debugger_updateExecutionBreakpoint(address); @@ -486,7 +480,7 @@ bool debugger_stepOver(PPCInterpreter_t* hCPU) return false; } // create one-shot breakpoint at next instruction - debugger_createSingleShotExecuteBreakpoint(initialIP +4); + debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT); // step over current instruction (to avoid breakpoint) debugger_stepInto(hCPU); debuggerWindow_moveIP(); @@ -506,8 +500,39 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i]; } +void DebugLogStackTrace(OSThread_t* thread, MPTR sp); + void debugger_enterTW(PPCInterpreter_t* hCPU) { + // handle logging points + DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer); + bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT); + while (bp) + { + if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled) + { + std::wstring logName = !bp->comment.empty() ? L"Breakpoint '"+bp->comment+L"'" : fmt::format(L"Breakpoint at 0x{:08X} (no comment)", bp->address); + std::wstring logContext = fmt::format(L"Thread: {:08x} LR: 0x{:08x}", coreinitThread_getCurrentThreadMPTRDepr(hCPU), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? L" Stack Trace:" : L""); + cemuLog_log(LogType::Force, L"[Debugger] {} was executed! {}", logName, logContext); + if (cemuLog_advancedPPCLoggingEnabled()) + DebugLogStackTrace(coreinitThread_getCurrentThreadDepr(hCPU), hCPU->gpr[1]); + break; + } + bp = bp->next; + } + + // return early if it's only a non-pausing logging breakpoint to prevent a modified debugger state and GUI updates + if (!shouldBreak) + { + uint32 backupIP = debuggerState.debugSession.instructionPointer; + debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; + debugger_stepInto(hCPU, false); + PPCInterpreterSlim_executeInstruction(hCPU); + debuggerState.debugSession.instructionPointer = backupIP; + return; + } + + // handle breakpoints debuggerState.debugSession.isTrapped = true; debuggerState.debugSession.debuggedThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(hCPU); debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; @@ -579,6 +604,20 @@ void debugger_shouldBreak(PPCInterpreter_t* hCPU) void debugger_addParserSymbols(class ExpressionParser& ep) { + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + + std::vector module_tmp(module_count); + for (int i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + if (module) + { + module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); + ep.AddConstant(module->moduleName2, module_tmp[i]); + } + } + for (sint32 i = 0; i < 32; i++) ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]); } \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h index 08cbd90a..717df28a 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.h +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -7,6 +7,7 @@ #define DEBUGGER_BP_T_ONE_SHOT 1 // normal breakpoint, deletes itself after trigger (used for stepping) #define DEBUGGER_BP_T_MEMORY_READ 2 // memory breakpoint #define DEBUGGER_BP_T_MEMORY_WRITE 3 // memory breakpoint +#define DEBUGGER_BP_T_LOGGING 4 // logging breakpoint, prints the breakpoint comment and stack trace whenever hit #define DEBUGGER_BP_T_GDBSTUB 1 // breakpoint created by GDBStub #define DEBUGGER_BP_T_DEBUGGER 2 // breakpoint created by Cemu's debugger @@ -42,7 +43,7 @@ struct DebuggerBreakpoint bool isExecuteBP() const { - return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_ONE_SHOT; + return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_LOGGING || bpType == DEBUGGER_BP_T_ONE_SHOT; } bool isMemBP() const @@ -98,8 +99,9 @@ extern debuggerState_t debuggerState; // new API DebuggerBreakpoint* debugger_getFirstBP(uint32 address); -void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint +void debugger_createCodeBreakpoint(uint32 address, uint8 bpType); void debugger_createExecuteBreakpoint(uint32 address); +void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp); void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite); diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp index ecb77428..63b92626 100644 --- a/src/gui/debugger/BreakpointWindow.cpp +++ b/src/gui/debugger/BreakpointWindow.cpp @@ -11,9 +11,11 @@ enum { - MENU_ID_CREATE_MEM_BP_READ = 1, + MENU_ID_CREATE_CODE_BP_EXECUTION = 1, + MENU_ID_CREATE_CODE_BP_LOGGING, + MENU_ID_CREATE_MEM_BP_READ, MENU_ID_CREATE_MEM_BP_WRITE, - + MENU_ID_DELETE_BP, }; enum ItemColumns @@ -118,6 +120,8 @@ void BreakpointWindow::OnUpdateView() const char* typeName = "UKN"; if (bp->bpType == DEBUGGER_BP_T_NORMAL) typeName = "X"; + else if (bp->bpType == DEBUGGER_BP_T_LOGGING) + typeName = "LOG"; else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT) typeName = "XS"; else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ) @@ -211,31 +215,56 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event) void BreakpointWindow::OnRightDown(wxMouseEvent& event) { - wxMenu menu; + const auto position = event.GetPosition(); + const sint32 index = (position.y / m_breakpoints->GetCharHeight()) - 2; + if (index < 0 || index >= m_breakpoints->GetItemCount()) + { + wxMenu menu; + menu.Append(MENU_ID_CREATE_CODE_BP_EXECUTION, _("Create execution breakpoint")); + menu.Append(MENU_ID_CREATE_CODE_BP_LOGGING, _("Create logging breakpoint")); + menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); + menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); - menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); - menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this); + PopupMenu(&menu); + } + else + { + m_breakpoints->SetItemState(index, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); + m_breakpoints->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this); - PopupMenu(&menu); + wxMenu menu; + menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint")); + + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClickSelected), nullptr, this); + PopupMenu(&menu); + } +} + +void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt) +{ + if (evt.GetId() == MENU_ID_DELETE_BP) + { + long sel = m_breakpoints->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (sel != -1) + { + if (sel >= debuggerState.breakpoints.size()) + return; + + auto it = debuggerState.breakpoints.begin(); + std::advance(it, sel); + + debugger_deleteBreakpoint(*it); + + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); + wxPostEvent(this->m_parent, evt); + } + } } void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt) { - switch (evt.GetId()) - { - case MENU_ID_CREATE_MEM_BP_READ: - MemoryBreakpointDialog(false); - return; - case MENU_ID_CREATE_MEM_BP_WRITE: - MemoryBreakpointDialog(true); - return; - } -} - -void BreakpointWindow::MemoryBreakpointDialog(bool isWrite) -{ - wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Memory breakpoint"), wxEmptyString); + wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Set breakpoint"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { ExpressionParser parser; @@ -243,22 +272,34 @@ void BreakpointWindow::MemoryBreakpointDialog(bool isWrite) auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); - + uint32_t newBreakpointAddress = 0; try { debugger_addParserSymbols(parser); - const auto result = (uint32)parser.Evaluate(value); - debug_printf("goto eval result: %x\n", result); - - debugger_createMemoryBreakpoint(result, isWrite == false, isWrite == true); - this->OnUpdateView(); + newBreakpointAddress = parser.IsConstantExpression("0x"+value) ? (uint32)parser.Evaluate("0x"+value) : (uint32)parser.Evaluate(value); } - catch (const std::exception& e) + catch (const std::exception& ex) { - //ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString)); - //return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR; - wxMessageBox(e.what(), "Invalid expression"); + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; } - + + switch (evt.GetId()) + { + case MENU_ID_CREATE_CODE_BP_EXECUTION: + debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_NORMAL); + break; + case MENU_ID_CREATE_CODE_BP_LOGGING: + debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_LOGGING); + break; + case MENU_ID_CREATE_MEM_BP_READ: + debugger_createMemoryBreakpoint(newBreakpointAddress, true, false); + break; + case MENU_ID_CREATE_MEM_BP_WRITE: + debugger_createMemoryBreakpoint(newBreakpointAddress, false, true); + break; + } + + this->OnUpdateView(); } -} \ No newline at end of file +} diff --git a/src/gui/debugger/BreakpointWindow.h b/src/gui/debugger/BreakpointWindow.h index 4851cb52..ac132950 100644 --- a/src/gui/debugger/BreakpointWindow.h +++ b/src/gui/debugger/BreakpointWindow.h @@ -19,8 +19,7 @@ private: void OnRightDown(wxMouseEvent& event); void OnContextMenuClick(wxCommandEvent& evt); - - void MemoryBreakpointDialog(bool isWrite); + void OnContextMenuClickSelected(wxCommandEvent& evt); wxCheckedListCtrl* m_breakpoints; }; \ No newline at end of file diff --git a/src/gui/debugger/DebuggerWindow2.cpp b/src/gui/debugger/DebuggerWindow2.cpp index f8c5c931..969e40bd 100644 --- a/src/gui/debugger/DebuggerWindow2.cpp +++ b/src/gui/debugger/DebuggerWindow2.cpp @@ -118,7 +118,7 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser) const auto comment = element.get("Comment", ""); // calculate absolute address - uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data); + uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data; uint32 address = module_base_address + relative_address; // don't change anything if there's already a breakpoint @@ -127,7 +127,9 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser) // register breakpoints in debugger if (type == DEBUGGER_BP_T_NORMAL) - debugger_createExecuteBreakpoint(address); + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); + else if (type == DEBUGGER_BP_T_LOGGING) + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING); else if (type == DEBUGGER_BP_T_MEMORY_READ) debugger_createMemoryBreakpoint(address, true, false); else if (type == DEBUGGER_BP_T_MEMORY_WRITE) @@ -173,7 +175,7 @@ void DebuggerModuleStorage::Save(XMLConfigParser& parser) // check whether the breakpoint is part of the current module being saved RPLModule* address_module; - if (bp->bpType == DEBUGGER_BP_T_NORMAL) address_module = RPLLoader_FindModuleByCodeAddr(bp->address); + if (bp->bpType == DEBUGGER_BP_T_NORMAL || bp->bpType == DEBUGGER_BP_T_LOGGING) address_module = RPLLoader_FindModuleByCodeAddr(bp->address); else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address); else continue; @@ -259,7 +261,7 @@ void DebuggerWindow2::LoadModuleStorage(const RPLModule* module) bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr& debug) { return debug->GetFilename() == path; }); if (!path.empty() && !already_loaded) { - m_modules_storage.emplace_back(std::move(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))); + m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load(); } } @@ -522,6 +524,7 @@ void DebuggerWindow2::OnToolClicked(wxCommandEvent& event) void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event) { m_breakpoint_window->OnUpdateView(); + m_disasm_ctrl->RefreshControl(); UpdateModuleLabel(); } diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp index dededf2c..21f6fc1d 100644 --- a/src/gui/debugger/DisasmCtrl.cpp +++ b/src/gui/debugger/DisasmCtrl.cpp @@ -147,7 +147,7 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR else if (is_active_bp) background_colour = wxColour(0xFF80A0FF); else if (bp != nullptr) - background_colour = wxColour(0xFF8080FF); + background_colour = wxColour(bp->bpType == DEBUGGER_BP_T_NORMAL ? 0xFF8080FF : 0x80FFFFFF); else if(virtualAddress == m_lastGotoTarget) background_colour = wxColour(0xFFE0E0E0); else @@ -540,8 +540,6 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) { debugger_toggleExecuteBreakpoint(*optVirtualAddress); - RefreshControl(); - wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); } @@ -767,40 +765,31 @@ void DisasmCtrl::GoToAddressDialog() auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); - const auto module_count = RPLLoader_GetModuleCount(); - const auto module_list = RPLLoader_GetModuleList(); + debugger_addParserSymbols(parser); - std::vector module_tmp(module_count); - for (int i = 0; i < module_count; i++) + // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) + if (parser.IsConstantExpression("0x"+value)) { - const auto module = module_list[i]; - if (module) - { - module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); - parser.AddConstant(module->moduleName2, module_tmp[i]); - } - } - - double grp_tmp[32]; - PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot; - for (int i = 0; i < 32; i++) - { - char var_name[32]; - sprintf(var_name, "r%d", i); - grp_tmp[i] = ppc_snapshot.gpr[i]; - parser.AddConstant(var_name, grp_tmp[i]); - } - - try - { - const auto result = (uint32)parser.Evaluate(value); - debug_printf("goto eval result: %x\n", result); + const auto result = (uint32)parser.Evaluate("0x"+value); m_lastGotoTarget = result; CenterOffset(result); - debuggerWindow_updateViewThreadsafe2(); } - catch (const std::exception& ) + else if (parser.IsConstantExpression(value)) { + const auto result = (uint32)parser.Evaluate(value); + m_lastGotoTarget = result; + CenterOffset(result); + } + else + { + try + { + const auto _ = (uint32)parser.Evaluate(value); + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } } } } \ No newline at end of file diff --git a/src/gui/debugger/DumpCtrl.cpp b/src/gui/debugger/DumpCtrl.cpp index cc1d5fee..16fdd87d 100644 --- a/src/gui/debugger/DumpCtrl.cpp +++ b/src/gui/debugger/DumpCtrl.cpp @@ -214,46 +214,36 @@ void DumpCtrl::GoToAddressDialog() wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { - try + ExpressionParser parser; + + auto value = goto_dialog.GetValue().ToStdString(); + std::transform(value.begin(), value.end(), value.begin(), tolower); + + debugger_addParserSymbols(parser); + + // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) + if (parser.IsConstantExpression("0x"+value)) { - ExpressionParser parser; - - auto value = goto_dialog.GetValue().ToStdString(); - std::transform(value.begin(), value.end(), value.begin(), tolower); - //parser.SetExpr(value); - - const auto module_count = RPLLoader_GetModuleCount(); - const auto module_list = RPLLoader_GetModuleList(); - - std::vector module_tmp(module_count); - for (int i = 0; i < module_count; i++) - { - const auto module = module_list[i]; - if (module) - { - module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); - parser.AddConstant(module->moduleName2, module_tmp[i]); - } - } - - double grp_tmp[32]; - PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot; - for (int i = 0; i < 32; i++) - { - char var_name[32]; - sprintf(var_name, "r%d", i); - grp_tmp[i] = ppc_snapshot.gpr[i]; - parser.AddConstant(var_name, grp_tmp[i]); - } - - const auto result = (uint32)parser.Evaluate(value); - debug_printf("goto eval result: %x\n", result); + const auto result = (uint32)parser.Evaluate("0x"+value); m_lastGotoOffset = result; CenterOffset(result); } - catch (const std::exception& ex) + else if (parser.IsConstantExpression(value)) { - wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + const auto result = (uint32)parser.Evaluate(value); + m_lastGotoOffset = result; + CenterOffset(result); + } + else + { + try + { + const auto _ = (uint32)parser.Evaluate(value); + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } } } } diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index 1cbe1f8e..b93cf94e 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -280,9 +280,7 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp); void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread) { - cemuLog_log(LogType::Force, fmt::format("Dumping stack trace for thread {0:08x} LR: {1:08x}", - memory_getVirtualOffsetFromPointer(thread), - _swapEndianU32(thread->context.lr))); + cemuLog_log(LogType::Force, "Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr)); DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1])); } From 67819a68d927b22d573ec8e73cae75a70766f27f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:07:13 +0200 Subject: [PATCH 06/16] nn_act: Handle incorrect slot 0 for PersistentId --- src/Cafe/IOSU/legacy/iosu_act.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index fa683c1e..919b7b0f 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -623,10 +623,19 @@ int iosuAct_thread() } else if (actCemuRequest->requestCode == IOSU_ARC_PERSISTENTID) { - accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); - _cancelIfAccountDoesNotExist(); - actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId; - actCemuRequest->setACTReturnCode(0); + if(actCemuRequest->accountSlot != 0) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId; + actCemuRequest->setACTReturnCode(0); + } + else + { + // F1 Race Stars calls IsSlotOccupied and indirectly GetPersistentId on slot 0 which is not valid + actCemuRequest->resultU32.u32 = 0; + actCemuRequest->setACTReturnCode(0); + } } else if (actCemuRequest->requestCode == IOSU_ARC_COUNTRY) { From 0d96255bae99d074cbe5722c813936b6ed8c3525 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:04:42 +0200 Subject: [PATCH 07/16] nn_olv: More work on post API --- src/Cafe/CMakeLists.txt | 2 + src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp | 3 +- src/Cafe/OS/libs/nn_olv/nn_olv.cpp | 190 +--------------- src/Cafe/OS/libs/nn_olv/nn_olv.h | 1 + src/Cafe/OS/libs/nn_olv/nn_olv_Common.h | 35 +++ .../OS/libs/nn_olv/nn_olv_InitializeTypes.cpp | 10 +- src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp | 215 ++++++++++++++++++ src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h | 16 ++ src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp | 194 ++++++++++++++-- src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h | 186 ++++++++++++++- src/Cafe/TitleList/TitleList.cpp | 2 +- src/Common/precompiled.h | 8 + 12 files changed, 655 insertions(+), 207 deletions(-) create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 59b6aa42..b7656789 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -422,6 +422,8 @@ add_library(CemuCafe OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h OS/libs/nn_olv/nn_olv_PostTypes.cpp OS/libs/nn_olv/nn_olv_PostTypes.h + OS/libs/nn_olv/nn_olv_OfflineDB.cpp + OS/libs/nn_olv/nn_olv_OfflineDB.h OS/libs/nn_pdm/nn_pdm.cpp OS/libs/nn_pdm/nn_pdm.h OS/libs/nn_save/nn_save.cpp diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp index a784e593..b348218f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -546,6 +546,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // get parental online control for online features // note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used + // a non-zero value means network access is restricted through parental access. 0 means allowed // account in slot 1 if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed @@ -561,7 +562,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // miiverse restrictions if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) - memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only?, 2 -> no access?) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only, 2 -> no access) } else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0) { diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp index 50036249..25245b5c 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -5,6 +5,7 @@ #include "nn_olv_DownloadCommunityTypes.h" #include "nn_olv_UploadFavoriteTypes.h" #include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" @@ -13,179 +14,6 @@ namespace nn { namespace olv { - struct DownloadedPostData_t - { - /* +0x0000 */ uint32be flags; - /* +0x0004 */ uint32be userPrincipalId; - /* +0x0008 */ char postId[0x20]; // size guessed - /* +0x0028 */ uint64 postDate; - /* +0x0030 */ uint8 feeling; - /* +0x0031 */ uint8 padding0031[3]; - /* +0x0034 */ uint32be regionId; - /* +0x0038 */ uint8 platformId; - /* +0x0039 */ uint8 languageId; - /* +0x003A */ uint8 countryId; - /* +0x003B */ uint8 padding003B[1]; - /* +0x003C */ uint16be bodyText[0x100]; // actual size is unknown - /* +0x023C */ uint32be bodyTextLength; - /* +0x0240 */ uint8 compressedMemoBody[0xA000]; // 40KB - /* +0xA240 */ uint32be compressedMemoBodyRelated; // size of compressed data? - /* +0xA244 */ uint16be topicTag[0x98]; - // app data - /* +0xA374 */ uint8 appData[0x400]; - /* +0xA774 */ uint32be appDataLength; - // external binary - /* +0xA778 */ uint8 externalBinaryUrl[0x100]; - /* +0xA878 */ uint32be externalBinaryDataSize; - // external image - /* +0xA87C */ uint8 externalImageDataUrl[0x100]; - /* +0xA97C */ uint32be externalImageDataSize; - // external url ? - /* +0xA980 */ char externalUrl[0x100]; - // mii - /* +0xAA80 */ uint8 miiData[0x60]; - /* +0xAAE0 */ uint16be miiNickname[0x20]; - /* +0xAB20 */ uint8 unusedAB20[0x14E0]; - - // everything above is part of DownloadedDataBase - // everything below is part of DownloadedPostData - /* +0xC000 */ uint8 uknDataC000[8]; // ?? - /* +0xC008 */ uint32be communityId; - /* +0xC00C */ uint32be empathyCount; - /* +0xC010 */ uint32be commentCount; - /* +0xC014 */ uint8 unused[0x1F4]; - }; // size: 0xC208 - - static_assert(sizeof(DownloadedPostData_t) == 0xC208, ""); - static_assert(offsetof(DownloadedPostData_t, postDate) == 0x0028, ""); - static_assert(offsetof(DownloadedPostData_t, platformId) == 0x0038, ""); - static_assert(offsetof(DownloadedPostData_t, bodyText) == 0x003C, ""); - static_assert(offsetof(DownloadedPostData_t, compressedMemoBody) == 0x0240, ""); - static_assert(offsetof(DownloadedPostData_t, topicTag) == 0xA244, ""); - static_assert(offsetof(DownloadedPostData_t, appData) == 0xA374, ""); - static_assert(offsetof(DownloadedPostData_t, externalBinaryUrl) == 0xA778, ""); - static_assert(offsetof(DownloadedPostData_t, externalImageDataUrl) == 0xA87C, ""); - static_assert(offsetof(DownloadedPostData_t, externalUrl) == 0xA980, ""); - static_assert(offsetof(DownloadedPostData_t, miiData) == 0xAA80, ""); - static_assert(offsetof(DownloadedPostData_t, miiNickname) == 0xAAE0, ""); - static_assert(offsetof(DownloadedPostData_t, unusedAB20) == 0xAB20, ""); - static_assert(offsetof(DownloadedPostData_t, communityId) == 0xC008, ""); - static_assert(offsetof(DownloadedPostData_t, empathyCount) == 0xC00C, ""); - static_assert(offsetof(DownloadedPostData_t, commentCount) == 0xC010, ""); - - const int POST_DATA_FLAG_HAS_BODY_TEXT = (0x0001); - const int POST_DATA_FLAG_HAS_BODY_MEMO = (0x0002); - - - void export_DownloadPostDataList(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedTopicData, void, 0); // DownloadedTopicData - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 1); // DownloadedPostData - ppcDefineParamTypePtr(downloadedPostDataSize, uint32be, 2); - ppcDefineParamS32(maxCount, 3); - ppcDefineParamTypePtr(listParam, void, 4); // DownloadPostDataListParam - - maxCount = 0; // DISABLED - - // just some test - for (sint32 i = 0; i < maxCount; i++) - { - DownloadedPostData_t* postData = downloadedPostData + i; - memset(postData, 0, sizeof(DownloadedPostData_t)); - postData->userPrincipalId = 0x1000 + i; - // post id - sprintf(postData->postId, "postid-%04x", i+(GetTickCount()%10000)); - postData->bodyTextLength = 12; - postData->bodyText[0] = 'H'; - postData->bodyText[1] = 'e'; - postData->bodyText[2] = 'l'; - postData->bodyText[3] = 'l'; - postData->bodyText[4] = 'o'; - postData->bodyText[5] = ' '; - postData->bodyText[6] = 'w'; - postData->bodyText[7] = 'o'; - postData->bodyText[8] = 'r'; - postData->bodyText[9] = 'l'; - postData->bodyText[10] = 'd'; - postData->bodyText[11] = '!'; - - postData->miiNickname[0] = 'C'; - postData->miiNickname[1] = 'e'; - postData->miiNickname[2] = 'm'; - postData->miiNickname[3] = 'u'; - postData->miiNickname[4] = '-'; - postData->miiNickname[5] = 'M'; - postData->miiNickname[6] = 'i'; - postData->miiNickname[7] = 'i'; - - postData->topicTag[0] = 't'; - postData->topicTag[1] = 'o'; - postData->topicTag[2] = 'p'; - postData->topicTag[3] = 'i'; - postData->topicTag[4] = 'c'; - - postData->flags = POST_DATA_FLAG_HAS_BODY_TEXT; - } - *downloadedPostDataSize = maxCount; - - osLib_returnFromFunction(hCPU, 0); - } - - void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - ppcDefineParamU32(testFlags, 1); - - if (((uint32)downloadedPostData->flags) & testFlags) - osLib_returnFromFunction(hCPU, 1); - else - osLib_returnFromFunction(hCPU, 0); - } - - void exportDownloadPostData_GetPostId(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->postId)); - } - - void exportDownloadPostData_GetMiiNickname(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - if(downloadedPostData->miiNickname[0] == 0 ) - osLib_returnFromFunction(hCPU, MPTR_NULL); - else - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->miiNickname)); - } - - void exportDownloadPostData_GetTopicTag(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->topicTag)); - } - - void exportDownloadPostData_GetBodyText(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - ppcDefineParamWStrBE(strOut, 1); - ppcDefineParamS32(maxLength, 2); - - if (((uint32)downloadedPostData->flags&POST_DATA_FLAG_HAS_BODY_TEXT) == 0) - { - osLib_returnFromFunction(hCPU, 0xC1106800); - return; - } - - memset(strOut, 0, sizeof(uint16be)*maxLength); - sint32 copyLen = std::min(maxLength - 1, (sint32)downloadedPostData->bodyTextLength); - for (sint32 i = 0; i < copyLen; i++) - { - strOut[i] = downloadedPostData->bodyText[i]; - } - strOut[copyLen] = '\0'; - - osLib_returnFromFunction(hCPU, 0); - } - struct PortalAppParam_t { /* +0x1A663B */ char serviceToken[32]; // size is unknown @@ -284,6 +112,10 @@ namespace nn void load() { + g_ReportTypes = 0; + g_IsOnlineMode = false; + g_IsInitialized = false; + g_IsOfflineDBMode = false; loadOliveInitializeTypes(); loadOliveUploadCommunityTypes(); @@ -293,13 +125,6 @@ namespace nn cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::None); - osLib_addFunction("nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", export_DownloadPostDataList); -// osLib_addFunction("nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", exportDownloadPostData_TestFlags); -// osLib_addFunction("nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetPostId); -// osLib_addFunction("nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetMiiNickname); -// osLib_addFunction("nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetTopicTag); -// osLib_addFunction("nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", exportDownloadPostData_GetBodyText); - osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken); cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Force); @@ -314,5 +139,10 @@ namespace nn cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::Force); } + void unload() // not called yet + { + OfflineDB_Shutdown(); + } + } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.h b/src/Cafe/OS/libs/nn_olv/nn_olv.h index c608e391..52474b49 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h @@ -19,5 +19,6 @@ namespace nn sint32 GetOlvAccessKey(uint32_t* pOutKey); void load(); + void unload(); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h index 718c10c3..c598e7ba 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h @@ -69,6 +69,7 @@ namespace nn extern uint32_t g_ReportTypes; extern bool g_IsInitialized; extern bool g_IsOnlineMode; + extern bool g_IsOfflineDBMode; // use offline cache for posts static void InitializeOliveRequest(CurlRequestHelper& req) { @@ -175,5 +176,39 @@ namespace nn bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId); sint32 olv_curlformcode_to_error(CURLFORMcode code); + + // convert and copy utf8 string into UC2 big-endian array + template + uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) + { + if(unescape) + { + // todo + } + std::wstring ws = boost::nowide::widen(sv); + size_t copyLen = std::min(TLength-1, ws.size()); + for(size_t i=0; i + uint32 SetStringUC2(uint16be(&str)[TLength], const uint16be* strIn) + { + size_t copyLen = TLength-1; + for(size_t i=0; im_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0) { - g_IsOnlineMode = true; independentServiceToken_t token; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp new file mode 100644 index 00000000..241630aa --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp @@ -0,0 +1,215 @@ +#include "nn_olv_Common.h" +#include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" +#include "Cemu/ncrypto/ncrypto.h" // for base64 encoder/decoder +#include "util/helpers/helpers.h" +#include "Config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" +#include +#include +#include + +namespace nn +{ + namespace olv + { + std::mutex g_offlineDBMutex; + bool g_offlineDBInitialized = false; + ZArchiveReader* g_offlineDBArchive{nullptr}; + + void OfflineDB_LazyInit() + { + std::scoped_lock _l(g_offlineDBMutex); + if(g_offlineDBInitialized) + return; + // open archive + g_offlineDBArchive = ZArchiveReader::OpenFromFile(ActiveSettings::GetUserDataPath("resources/miiverse/OfflineDB.zar")); + if(!g_offlineDBArchive) + cemuLog_log(LogType::Force, "Failed to open resources/miiverse/OfflineDB.zar. Miiverse posts will not be available"); + g_offlineDBInitialized = true; + } + + void OfflineDB_Shutdown() + { + std::scoped_lock _l(g_offlineDBMutex); + if(!g_offlineDBInitialized) + return; + delete g_offlineDBArchive; + g_offlineDBInitialized = false; + } + + bool CheckForOfflineDBFile(const char* filePath, uint32* fileSize) + { + if(!g_offlineDBArchive) + return false; + ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); + if (!g_offlineDBArchive->IsFile(fileHandle)) + return false; + if(fileSize) + *fileSize = g_offlineDBArchive->GetFileSize(fileHandle); + return true; + } + + bool LoadOfflineDBFile(const char* filePath, std::vector& fileData) + { + fileData.clear(); + if(!g_offlineDBArchive) + return false; + ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); + if (!g_offlineDBArchive->IsFile(fileHandle)) + return false; + fileData.resize(g_offlineDBArchive->GetFileSize(fileHandle)); + g_offlineDBArchive->ReadFromFile(fileHandle, 0, fileData.size(), fileData.data()); + return true; + } + + void TryLoadCompressedMemoImage(DownloadedPostData& downloadedPostData) + { + const unsigned char tgaHeader_320x120_32BPP[] = {0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x1,0x78,0x0,0x20,0x8}; + std::string memoImageFilename = fmt::format("memo/{}", (char*)downloadedPostData.downloadedDataBase.postId); + std::vector bitmaskCompressedImg; + if (!LoadOfflineDBFile(memoImageFilename.c_str(), bitmaskCompressedImg)) + return; + if (bitmaskCompressedImg.size() != (320*120)/8) + return; + std::vector decompressedImage; + decompressedImage.resize(sizeof(tgaHeader_320x120_32BPP) + 320 * 120 * 4); + memcpy(decompressedImage.data(), tgaHeader_320x120_32BPP, sizeof(tgaHeader_320x120_32BPP)); + uint8* pOut = decompressedImage.data() + sizeof(tgaHeader_320x120_32BPP); + for(int i=0; i<320*120; i++) + { + bool isWhite = (bitmaskCompressedImg[i/8] & (1 << (i%8))) != 0; + if(isWhite) + { + pOut[0] = pOut[1] = pOut[2] = pOut[3] = 0xFF; + } + else + { + pOut[0] = pOut[1] = pOut[2] = 0; + pOut[3] = 0xFF; + } + pOut += 4; + } + // store compressed image + uLongf compressedDestLen = 40960; + int r = compress((uint8*)downloadedPostData.downloadedDataBase.compressedMemoBody, &compressedDestLen, decompressedImage.data(), decompressedImage.size()); + if( r != Z_OK) + return; + downloadedPostData.downloadedDataBase.compressedMemoBodySize = compressedDestLen; + downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); + } + + void CheckForExternalImage(DownloadedPostData& downloadedPostData) + { + std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)downloadedPostData.downloadedDataBase.postId); + uint32 fileSize; + if (!CheckForOfflineDBFile(externalImageFilename.c_str(), &fileSize)) + return; + strcpy((char*)downloadedPostData.downloadedDataBase.externalImageDataUrl, externalImageFilename.c_str()); + downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE); + downloadedPostData.downloadedDataBase.externalImageDataSize = fileSize; + } + + nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + + uint64 titleId = CafeSystem::GetForegroundTitleId(); + + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + memset(downloadedPostData, 0, sizeof(DownloadedPostData) * maxCount); + *postCountOut = 0; + + const char* postXmlFilename = nullptr; + if(titleId == 0x0005000010143400 || titleId == 0x0005000010143500 || titleId == 0x0005000010143600) + postXmlFilename = "PostList_WindWakerHD.xml"; + + if (!postXmlFilename) + return OLV_RESULT_SUCCESS; + + // load post XML + std::vector xmlData; + if (!LoadOfflineDBFile(postXmlFilename, xmlData)) + return OLV_RESULT_SUCCESS; + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_buffer(xmlData.data(), xmlData.size()); + if (!result) + return OLV_RESULT_SUCCESS; + // collect list of all post xml nodes + std::vector postXmlNodes; + for (pugi::xml_node postNode = doc.child("posts").child("post"); postNode; postNode = postNode.next_sibling("post")) + postXmlNodes.push_back(postNode); + + // randomly select up to maxCount posts + srand(GetTickCount()); + uint32 postCount = 0; + while(!postXmlNodes.empty() && postCount < maxCount) + { + uint32 index = rand() % postXmlNodes.size(); + pugi::xml_node& postNode = postXmlNodes[index]; + + auto& addedPost = downloadedPostData[postCount]; + memset(&addedPost, 0, sizeof(DownloadedPostData)); + if (!ParseXML_DownloadedPostData(addedPost, postNode) ) + continue; + TryLoadCompressedMemoImage(addedPost); + CheckForExternalImage(addedPost); + postCount++; + // remove from post list + postXmlNodes[index] = postXmlNodes.back(); + postXmlNodes.pop_back(); + } + *postCountOut = postCount; + return OLV_RESULT_SUCCESS; + } + + nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + OfflineDB_LazyInit(); + + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + downloadedTopicData->communityId = param->communityId; + *postCountOut = 0; + + if(param->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) + return OLV_RESULT_SUCCESS; // the offlineDB doesn't contain any self posts + + StackAllocator doneEvent; + coreinit::OSInitEvent(doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList, doneEvent.GetPointer(), downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); + coreinit::OSWaitEvent(doneEvent); + nnResult r = asyncTask.get(); + return r; + } + + nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + + if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE)) + return OLV_RESULT_MISSING_DATA; + + // not all games may use JPEG files? + std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)_this->postId); + std::vector jpegData; + if (!LoadOfflineDBFile(externalImageFilename.c_str(), jpegData)) + return OLV_RESULT_FAILED_REQUEST; + + memcpy(imageDataOut, jpegData.data(), jpegData.size()); + *imageSizeOut = jpegData.size(); + + return OLV_RESULT_SUCCESS; + } + + nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + StackAllocator doneEvent; + coreinit::OSInitEvent(doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData, doneEvent.GetPointer(), _this, imageDataOut, imageSizeOut, maxSize); + coreinit::OSWaitEvent(doneEvent); + nnResult r = asyncTask.get(); + return r; + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h new file mode 100644 index 00000000..ed790479 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h @@ -0,0 +1,16 @@ +#pragma once +#include "Cafe/OS/libs/nn_common.h" +#include "nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + void OfflineDB_Init(); + void OfflineDB_Shutdown(); + + nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param); + nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp index 5056f2fe..722e5584 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp @@ -1,5 +1,6 @@ #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" #include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" #include "Cemu/ncrypto/ncrypto.h" // for base64 decoder #include "util/helpers/helpers.h" #include @@ -9,41 +10,28 @@ 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("topic_tag"); tokenNode) + { + SetStringUC2(obj.topicTag, tokenNode.child_value(), true); + } if(tokenNode = xmlNode.child("feeling_id"); tokenNode) { obj.feeling = ConvertString(tokenNode.child_value()); if(obj.feeling < 0 || obj.feeling >= 5) { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: feeling_id out of range"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: feeling_id out of range"); return false; } } @@ -52,7 +40,7 @@ namespace nn std::string_view id_sv = tokenNode.child_value(); if(id_sv.size() > 22) { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: id too long"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: id too long"); return false; } memcpy(obj.postId, id_sv.data(), id_sv.size()); @@ -67,7 +55,7 @@ namespace nn obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST); else { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: is_autopost has invalid value"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: is_autopost has invalid value"); return false; } } @@ -116,6 +104,36 @@ namespace nn { obj.countryId = ConvertString(tokenNode.child_value()); } + if(tokenNode = xmlNode.child("painting"); tokenNode) + { + if(pugi::xml_node subNode = tokenNode.child("content"); subNode) + { + std::vector paintingData = NCrypto::base64Decode(subNode.child_value()); + if (paintingData.size() > 0xA000) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase painting content is too large"); + return false; + } + memcpy(obj.compressedMemoBody, paintingData.data(), paintingData.size()); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); + } + if(pugi::xml_node subNode = tokenNode.child("size"); subNode) + { + obj.compressedMemoBodySize = ConvertString(subNode.child_value()); + } + } + if(tokenNode = xmlNode.child("app_data"); tokenNode) + { + std::vector appData = NCrypto::base64Decode(tokenNode.child_value()); + if (appData.size() > 0x400) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase AppData is too large"); + return false; + } + memcpy(obj.appData, appData.data(), appData.size()); + obj.appDataLength = appData.size(); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_APP_DATA); + } return true; } @@ -256,6 +274,121 @@ namespace nn return 0; } + nnResult DownloadedDataBase::DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + if(g_IsOfflineDBMode) + return OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(_this, imageDataOut, imageSizeOut, maxSize); + + if(!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) + return OLV_RESULT_MISSING_DATA; + + cemuLog_logDebug(LogType::Force, "DownloadedDataBase::DownloadExternalImageData not implemented"); + return OLV_RESULT_FAILED_REQUEST; // placeholder error + } + + nnResult DownloadPostDataListParam::GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize) + { + if(!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + //if(_this->communityId == 0) + // cemuLog_log(LogType::Force, "DownloadPostDataListParam::GetRawDataUrl called with invalid communityId"); + + // get base url + std::string baseUrl; + baseUrl.append(g_DiscoveryResults.apiEndpoint); + //baseUrl.append(fmt::format("/v1/communities/{}/posts", (uint32)_this->communityId)); + cemu_assert_debug(_this->communityId == 0); + baseUrl.append(fmt::format("/v1/posts.search", (uint32)_this->communityId)); + + // "v1/posts.search" + + // build parameter string + std::string params; + + // this function behaves differently for the Wii U menu? Where it can lookup posts by titleId? + if(_this->titleId != 0) + { + cemu_assert_unimplemented(); // Wii U menu mode + } + + // todo: Generic parameters. Which includes: language_id, limit, type=text/memo + + // handle postIds + for(size_t i=0; i<_this->MAX_NUM_POST_ID; i++) + { + if(_this->searchPostId[i].str[0] == '\0') + continue; + cemu_assert_unimplemented(); // todo + // todo - postId parameter + // handle filters + if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_MII)) + params.append("&with_mii=1"); + if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_EMPATHY)) + params.append("&with_empathy_added=1"); + if(_this->bodyTextMaxLength != 0) + params.append(fmt::format("&max_body_length={}", _this->bodyTextMaxLength)); + } + + if(_this->titleId != 0) + params.append(fmt::format("&title_id={}", (uint64)_this->titleId)); + + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FRIENDS_ONLY)) + params.append("&by=friend"); + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FOLLOWERS_ONLY)) + params.append("&by=followings"); + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) + params.append("&by=self"); + + if(!params.empty()) + params[0] = '?'; // replace the leading ampersand + + baseUrl.append(params); + if(baseUrl.size()+1 > urlMaxSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + strncpy(urlOut, baseUrl.c_str(), urlMaxSize); + return OLV_RESULT_SUCCESS; + } + + nnResult DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + if(g_IsOfflineDBMode) + return OfflineDB_DownloadPostDataListParam_DownloadPostDataList(downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + downloadedTopicData->communityId = param->communityId; + *postCountOut = 0; + + char urlBuffer[2048]; + if (NN_RESULT_IS_FAILURE(DownloadPostDataListParam::GetRawDataUrl(param, urlBuffer, sizeof(urlBuffer)))) + return OLV_RESULT_INVALID_PARAMETER; + + /* + CurlRequestHelper req; + req.initate(urlBuffer, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + bool reqResult = req.submitRequest(); + if (!reqResult) + { + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + cemuLog_log(LogType::Force, "Failed request: {} ({})", urlBuffer, httpCode); + if (!(httpCode >= 400)) + return OLV_RESULT_FAILED_REQUEST; + } + pugi::xml_document doc; + if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in community download response")); + return OLV_RESULT_INVALID_XML; + } + */ + + *postCountOut = 0; + + return OLV_RESULT_SUCCESS; + } + void loadOlivePostAndTopicTypes() { cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::None); @@ -279,6 +412,8 @@ namespace nn cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::DownloadExternalImageData, "nn_olv", "DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetExternalImageDataSize, "nn_olv", "GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); // DownloadedPostData getters cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); @@ -305,6 +440,23 @@ namespace nn cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None); cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::None); + // DownloadPostDataListParam constructor and getters + cafeExportRegisterFunc(DownloadPostDataListParam::Construct, "nn_olv", "__ct__Q3_2nn3olv25DownloadPostDataListParamFv", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetLanguageId, "nn_olv", "SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKey, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKeySingle, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchPid, "nn_olv", "SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostId, "nn_olv", "SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDate, "nn_olv", "SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDataMaxNum, "nn_olv", "SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetBodyTextMaxLength, "nn_olv", "SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + + // URL and downloading functions + cafeExportRegisterFunc(DownloadPostDataListParam::GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataList, "nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", LogType::None); + } } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h index 3ca4f87e..e6078a7a 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h @@ -1,5 +1,6 @@ #pragma once #include +#include "nn_olv_Common.h" namespace nn { @@ -154,8 +155,11 @@ namespace nn return OLV_RESULT_INVALID_PTR; if (maxLength == 0) return OLV_RESULT_NOT_ENOUGH_SIZE; + if (!TestFlags(_this, FLAGS::HAS_BODY_TEXT)) + return OLV_RESULT_MISSING_DATA; + memset(bodyTextOut, 0, maxLength * sizeof(uint16)); uint32 outputLength = std::min(_this->bodyTextLength, maxLength); - olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, _this->bodyTextLength); + olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, outputLength); return OLV_RESULT_SUCCESS; } @@ -213,9 +217,19 @@ namespace nn return _this->postId; } - // todo: // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi + static nnResult DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); + // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetExternalImageDataSize(DownloadedDataBase* _this) + { + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) + return 0; + return _this->externalImageDataSize; + } + + // todo: + // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi (implement downloading) // DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi // GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv }; @@ -425,6 +439,174 @@ namespace nn static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000); } + + struct DownloadPostDataListParam + { + static constexpr size_t MAX_NUM_SEARCH_PID = 12; + static constexpr size_t MAX_NUM_SEARCH_KEY = 5; + static constexpr size_t MAX_NUM_POST_ID = 20; + + enum class FLAGS + { + FRIENDS_ONLY = 0x01, // friends only + FOLLOWERS_ONLY = 0x02, // followers only + SELF_ONLY = 0x04, // self only + ONLY_TYPE_TEXT = 0x08, + ONLY_TYPE_MEMO = 0x10, + UKN_20 = 0x20, + WITH_MII = 0x40, // with mii + WITH_EMPATHY = 0x80, // with yeahs added + UKN_100 = 0x100, + UKN_200 = 0x200, // "is_delay" parameter + UKN_400 = 0x400, // "is_hot" parameter + + + }; + + struct SearchKey + { + uint16be str[152]; + }; + + struct PostId + { + char str[32]; + }; + + betype flags; + uint32be communityId; + uint32be searchPid[MAX_NUM_SEARCH_PID]; + uint8 languageId; + uint8 hasLanguageId_039; + uint8 padding03A[2]; + uint32be postDataMaxNum; + SearchKey searchKeyArray[MAX_NUM_SEARCH_KEY]; + PostId searchPostId[MAX_NUM_POST_ID]; + uint64be postDate; // OSTime? + uint64be titleId; // only used by System posts? + uint32be bodyTextMaxLength; + uint8 padding8C4[1852]; + + bool _HasFlag(FLAGS flag) + { + return ((uint32)flags.value() & (uint32)flag) != 0; + } + + void _SetFlags(FLAGS flag) + { + flags = (FLAGS)((uint32)flags.value() | (uint32)flag); + } + + // constructor and getters + // __ct__Q3_2nn3olv25DownloadPostDataListParamFv + static DownloadPostDataListParam* Construct(DownloadPostDataListParam* _this) + { + memset(_this, 0, sizeof(DownloadPostDataListParam)); + return _this; + } + + // SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetFlags(DownloadPostDataListParam* _this, FLAGS flags) + { + // todo - verify flag combos + _this->flags = flags; + return OLV_RESULT_SUCCESS; + } + + // SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc + static nnResult SetLanguageId(DownloadPostDataListParam* _this, uint8 languageId) + { + _this->languageId = languageId; + _this->hasLanguageId_039 = 1; + return OLV_RESULT_SUCCESS; + } + + // SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetCommunityId(DownloadPostDataListParam* _this, uint32 communityId) + { + _this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + + // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc + static nnResult SetSearchKey(DownloadPostDataListParam* _this, const uint16be* searchKey, uint8 searchKeyIndex) + { + if (searchKeyIndex >= MAX_NUM_SEARCH_KEY) + return OLV_RESULT_INVALID_PARAMETER; + memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey)); + if(olv_wstrnlen((const char16_t*)searchKey, 152) > 50) + { + cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetSearchKey: searchKey is too long\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + SetStringUC2(_this->searchKeyArray[searchKeyIndex].str, searchKey); + return OLV_RESULT_SUCCESS; + } + + // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw + static nnResult SetSearchKeySingle(DownloadPostDataListParam* _this, const uint16be* searchKey) + { + return SetSearchKey(_this, searchKey, 0); + } + + // SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetSearchPid(DownloadPostDataListParam* _this, uint32 searchPid) + { + if(_this->_HasFlag(FLAGS::FRIENDS_ONLY) || _this->_HasFlag(FLAGS::FOLLOWERS_ONLY) || _this->_HasFlag(FLAGS::SELF_ONLY)) + return OLV_RESULT_INVALID_PARAMETER; + _this->searchPid[0] = searchPid; + return OLV_RESULT_SUCCESS; + } + + // SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi + static nnResult SetPostId(DownloadPostDataListParam* _this, const char* postId, uint32 postIdIndex) + { + if (postIdIndex >= MAX_NUM_POST_ID) + return OLV_RESULT_INVALID_PARAMETER; + memset(&_this->searchPostId[postIdIndex], 0, sizeof(PostId)); + if (strlen(postId) > 22) + { + cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetPostId: postId is too long\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + strcpy(_this->searchPostId[postIdIndex].str, postId); + return OLV_RESULT_SUCCESS; + } + + // SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL + static nnResult SetPostDate(DownloadPostDataListParam* _this, uint64 postDate) + { + _this->postDate = postDate; + return OLV_RESULT_SUCCESS; + } + + // SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetPostDataMaxNum(DownloadPostDataListParam* _this, uint32 postDataMaxNum) + { + if(postDataMaxNum == 0) + return OLV_RESULT_INVALID_PARAMETER; + _this->postDataMaxNum = postDataMaxNum; + return OLV_RESULT_SUCCESS; + } + + // SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetBodyTextMaxLength(DownloadPostDataListParam* _this, uint32 bodyTextMaxLength) + { + if(bodyTextMaxLength >= 256) + return OLV_RESULT_INVALID_PARAMETER; + _this->bodyTextMaxLength = bodyTextMaxLength; + return OLV_RESULT_SUCCESS; + } + + // GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi + static nnResult GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize); + }; + + static_assert(sizeof(DownloadPostDataListParam) == 0x1000); + + // parsing functions + bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode); + void loadOlivePostAndTopicTypes(); } } \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 7f42d17c..2e50cbf9 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -9,7 +9,7 @@ bool sTLInitialized{ false }; fs::path sTLCacheFilePath; // lists for tracking known titles -// note: The list may only contain titles with valid meta data. Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion +// note: The list may only contain titles with valid meta data (except for certain system titles). Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion std::mutex sTLMutex; std::vector sTLList; std::vector sTLListPending; diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 56f31a03..7152f2c1 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -485,6 +485,14 @@ bool future_is_ready(std::future& f) #endif } +// replace with std::scope_exit once available +struct scope_exit +{ + std::function f_; + explicit scope_exit(std::function f) noexcept : f_(std::move(f)) {} + ~scope_exit() { if (f_) f_(); } +}; + // helper function to cast raw pointers to std::atomic // this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members From 6268a24a4b66dcdee6f5a466240976557a0814dd Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:22:53 +0200 Subject: [PATCH 08/16] Fix crash in title manager --- src/gui/components/wxTitleManagerList.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index 7ba8d037..257f84d2 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -1005,7 +1005,8 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath()); ParsedMetaXml* metaInfo = titleInfo.GetMetaInfo(); - + if(titleInfo.IsSystemDataTitle()) + return; // dont show system data titles for now entry.location_uid = titleInfo.GetUID(); entry.title_id = titleInfo.GetAppTitleId(); std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue()); From 0f469eb2b93147d981a61357e25763459122dcea Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:45:00 +0200 Subject: [PATCH 09/16] Small cleanup + Fix memory base logged as 0 --- src/Cafe/CafeSystem.cpp | 74 ++++++++++++++++--- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 3 - .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 1 - src/gui/GameUpdateWindow.cpp | 14 +--- src/main.cpp | 65 ---------------- 5 files changed, 67 insertions(+), 90 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 6726a62c..ce46dc71 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -4,17 +4,16 @@ #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "audio/IAudioAPI.h" #include "audio/IAudioInputAPI.h" -#include "Cafe/HW/Espresso/Debugger/Debugger.h" - #include "config/ActiveSettings.h" #include "Cafe/TitleList/GameInfo.h" -#include "util/helpers/SystemException.h" #include "Cafe/GraphicPack/GraphicPack2.h" - +#include "util/helpers/SystemException.h" +#include "Common/cpu_features.h" #include "input/InputManager.h" - #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/GameInfo.h" @@ -22,14 +21,9 @@ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/HW/Latte/Core/Latte.h" - #include "Cafe/Filesystem/FST/FST.h" - #include "Common/FileStream.h" - #include "GamePatch.h" - -#include #include "HW/Espresso/Debugger/GDBStub.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" @@ -70,6 +64,15 @@ // dependency to be removed #include "gui/guiWrapper.h" +#include + +#if BOOST_OS_LINUX +#include +#elif BOOST_OS_MACOS +#include +#include +#endif + std::string _pathToExecutable; std::string _pathToBaseExecutable; @@ -441,17 +444,66 @@ namespace CafeSystem GameInfo2 sGameInfo_ForegroundTitle; - // initialize all subsystems which are persistent and don't depend on a game running + + static void _CheckForWine() + { + #if BOOST_OS_WINDOWS + const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); + if (!hmodule) + return; + + const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); + if (pwine_get_version) + { + cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version()); + } + #endif + } + + void logCPUAndMemoryInfo() + { + std::string cpuName = g_CPUFeatures.GetCPUName(); + if (!cpuName.empty()) + cemuLog_log(LogType::Force, "CPU: {}", cpuName); + #if BOOST_OS_WINDOWS + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + GlobalMemoryStatusEx(&statex); + uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); + cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB); + #elif BOOST_OS_LINUX + struct sysinfo info {}; + sysinfo(&info); + cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL)); + #elif BOOST_OS_MACOS + int64_t totalRam; + size_t size = sizeof(totalRam); + int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0); + if (result == 0) + cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); + #endif + } + + // initialize all subsystems which are persistent and don't depend on a game running void Initialize() { if (s_initialized) return; s_initialized = true; // init core systems + cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); fsc_init(); memory_init(); + cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); PPCCore_init(); RPLLoader_InitState(); + cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); + _CheckForWine(); + // CPU and RAM info + logCPUAndMemoryInfo(); + cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); + // misc systems + rplSymbolStorage_init(); // allocate memory for all SysAllocators // must happen before COS module init, but also before iosu::kernel::Initialize() SysAllocatorContainer::GetInstance().Initialize(); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 4dfdc52b..5269be64 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -363,9 +363,6 @@ void OpenGLRenderer::NotifyLatteCommandProcessorIdle() glFlush(); } - -bool IsRunningInWine(); - void OpenGLRenderer::GetVendorInformation() { // example vendor strings: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index cfe7d3f4..937e3266 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -179,7 +179,6 @@ std::vector VulkanRenderer::GetDevices() } -bool IsRunningInWine(); void VulkanRenderer::DetermineVendor() { VkPhysicalDeviceProperties2 properties{}; diff --git a/src/gui/GameUpdateWindow.cpp b/src/gui/GameUpdateWindow.cpp index 40bf546e..e90c9dc7 100644 --- a/src/gui/GameUpdateWindow.cpp +++ b/src/gui/GameUpdateWindow.cpp @@ -34,8 +34,6 @@ std::string _GetTitleIdTypeStr(TitleId titleId) return "Unknown"; } -bool IsRunningInWine(); - bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) { m_title_info = TitleInfo(metaPath); @@ -130,15 +128,11 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) } } - // checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step - if(!IsRunningInWine()) + const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath()); + if (targetSpace.free <= m_required_size) { - const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath()); - if (targetSpace.free <= m_required_size) - { - auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); - throw std::runtime_error(string); - } + auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); + throw std::runtime_error(string); } return true; diff --git a/src/main.cpp b/src/main.cpp index 032c23bc..f7a66bf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,7 +3,6 @@ #include "util/crypto/aes128.h" #include "gui/MainWindow.h" #include "Cafe/OS/RPL/rpl.h" -#include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" @@ -60,66 +59,6 @@ std::atomic_bool g_isGPUInitFinished = false; std::wstring executablePath; -void logCPUAndMemoryInfo() -{ - std::string cpuName = g_CPUFeatures.GetCPUName(); - if (!cpuName.empty()) - cemuLog_log(LogType::Force, "CPU: {}", cpuName); - - #if BOOST_OS_WINDOWS - MEMORYSTATUSEX statex; - statex.dwLength = sizeof(statex); - GlobalMemoryStatusEx(&statex); - uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); - cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB); - #elif BOOST_OS_LINUX - struct sysinfo info {}; - sysinfo(&info); - cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL)); - #elif BOOST_OS_MACOS - int64_t totalRam; - size_t size = sizeof(totalRam); - int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0); - if (result == 0) - cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); - #endif -} - -bool g_running_in_wine = false; -bool IsRunningInWine() -{ - return g_running_in_wine; -} - -void checkForWine() -{ - #if BOOST_OS_WINDOWS - const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); - if (!hmodule) - return; - - const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); - if (pwine_get_version) - { - g_running_in_wine = true; - cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version()); - } - #else - g_running_in_wine = false; - #endif -} - -void infoLog_cemuStartup() -{ - cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); - cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); - cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); - checkForWine(); - // CPU and RAM info - logCPUAndMemoryInfo(); - cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); -} - // some implementations of _putenv dont copy the string and instead only store a pointer // thus we use a helper to keep a permanent copy std::vector sPutEnvMap; @@ -189,16 +128,12 @@ void CemuCommonInit() g_config.Load(); if (NetworkConfig::XMLExists()) n_config.Load(); - // symbol storage - rplSymbolStorage_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 From 911573e0ddf368d839fee3f56bcb61a318574da3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 3 Aug 2023 17:00:01 +0200 Subject: [PATCH 10/16] TitleList: Use narrower filter for identifying data titles Previous code accidentally caught some game updates and dlc titles --- src/Cafe/TitleList/AppType.h | 23 +++++++++++++++++++++++ src/Cafe/TitleList/TitleInfo.h | 5 ++--- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 src/Cafe/TitleList/AppType.h diff --git a/src/Cafe/TitleList/AppType.h b/src/Cafe/TitleList/AppType.h new file mode 100644 index 00000000..deca7560 --- /dev/null +++ b/src/Cafe/TitleList/AppType.h @@ -0,0 +1,23 @@ +#pragma once + +enum class APP_TYPE : uint32 +{ + GAME = 0x80000000, + GAME_UPDATE = 0x0800001B, + GAME_DLC = 0x0800000E, + // data titles + VERSION_DATA_TITLE = 0x10000015, + DRC_FIRMWARE = 0x10000013, + DRC_TEXTURE_ATLAS = 0x1000001A, +}; + +// allow direct comparison with uint32 +inline bool operator==(APP_TYPE lhs, uint32 rhs) +{ + return static_cast(lhs) == rhs; +} + +inline bool operator==(uint32 lhs, APP_TYPE rhs) +{ + return lhs == static_cast(rhs); +} diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index b8b781a4..9b8fa722 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -3,6 +3,7 @@ #include "Cafe/Filesystem/fsc.h" #include "config/CemuConfig.h" // for CafeConsoleRegion. Move to NCrypto? #include "TitleId.h" +#include "AppType.h" #include "ParsedMetaXml.h" enum class CafeTitleFileType @@ -122,9 +123,7 @@ public: 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; + return appType == APP_TYPE::DRC_FIRMWARE || appType == APP_TYPE::DRC_TEXTURE_ATLAS || appType == APP_TYPE::VERSION_DATA_TITLE; } // API which requires parsed meta data or cached info From a17111e6b0e4802044c90f4bedd66478de689070 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 3 Aug 2023 19:53:46 +0200 Subject: [PATCH 11/16] TitleManager: Improvements for .wua conversion - Print more detailed paths in confirmation dialogue - Prefer the title right clicked by the user - When sourcing titles from other .wua files, use the correct subpath Fix include path --- src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp | 2 +- src/Cafe/TitleList/TitleInfo.h | 2 +- src/gui/components/wxTitleManagerList.cpp | 42 +++++++++++--------- src/gui/components/wxTitleManagerList.h | 2 +- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp index 241630aa..e6cea082 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp @@ -3,7 +3,7 @@ #include "nn_olv_OfflineDB.h" #include "Cemu/ncrypto/ncrypto.h" // for base64 encoder/decoder #include "util/helpers/helpers.h" -#include "Config/ActiveSettings.h" +#include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" #include #include diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index 9b8fa722..da430adc 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -148,7 +148,7 @@ public: return m_parsedMetaXml; } - std::string GetPrintPath() const; // formatted path for log writing + std::string GetPrintPath() const; // formatted path including type and WUA subpath. Intended for logging and user-facing information std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..." static std::string GetUniqueTempMountingPath(); diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index 257f84d2..6572a702 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -242,7 +242,7 @@ boost::optional wxTitleManagerList::GetTi return {}; } -void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) +void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID) { TitleInfo titleInfo_base; TitleInfo titleInfo_update; @@ -269,22 +269,26 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) { if (!titleInfo_base.IsValid()) { - titleInfo_base = TitleInfo(data->entry.path); - } - else - { - // duplicate entry + titleInfo_base = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; // prefer the users selection } } if (hasUpdateTitleId && data->entry.title_id == updateTitleId) { if (!titleInfo_update.IsValid()) { - titleInfo_update = TitleInfo(data->entry.path); + titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; } else { - // duplicate entry + // if multiple updates are present use the newest one + if (titleInfo_update.GetAppTitleVersion() < data->entry.version) + titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; } } } @@ -293,7 +297,9 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) { if (data->entry.title_id == aocTitleId) { - titleInfo_aoc = TitleInfo(data->entry.path); + titleInfo_aoc = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; } } @@ -301,23 +307,23 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) msg.append("\n \n"); if (titleInfo_base.IsValid()) - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game: {}"))), _pathToUtf8(titleInfo_base.GetPath()))); + msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game:\n{}"))), titleInfo_base.GetPrintPath())); else - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game: Not installed"))))); + msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game:\nNot installed"))))); - msg.append("\n"); + msg.append("\n\n"); if (titleInfo_update.IsValid()) - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update: {}"))), _pathToUtf8(titleInfo_update.GetPath()))); + msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update:\n{}"))), titleInfo_update.GetPrintPath())); else - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update: Not installed"))))); + msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update:\nNot installed"))))); - msg.append("\n"); + msg.append("\n\n"); if (titleInfo_aoc.IsValid()) - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC: {}"))), _pathToUtf8(titleInfo_aoc.GetPath()))); + msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC:\n{}"))), titleInfo_aoc.GetPrintPath())); else - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC: Not installed"))))); + msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC:\nNot installed"))))); const int answer = wxMessageBox(wxString::FromUTF8(msg), _("Confirmation"), wxOK | wxCANCEL | wxCENTRE | wxICON_QUESTION, this); if (answer != wxOK) @@ -884,7 +890,7 @@ void wxTitleManagerList::OnContextMenuSelected(wxCommandEvent& event) break; case kContextMenuConvertToWUA: - OnConvertToCompressedFormat(entry.value().title_id); + OnConvertToCompressedFormat(entry.value().title_id, entry.value().location_uid); break; } } diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 706de2e7..547310c2 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -108,7 +108,7 @@ private: [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path); bool VerifyEntryFiles(TitleEntry& entry); - void OnConvertToCompressedFormat(uint64 titleId); + void OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID); bool DeleteEntry(long index, const TitleEntry& entry); void RemoveItem(long item); From 22bf6420d265399d8a31d518026e39328ab15812 Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Tue, 8 Aug 2023 22:22:22 +0100 Subject: [PATCH 12/16] Log platform info (#931) --- src/Cafe/CafeSystem.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index ce46dc71..10b49c60 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -484,6 +484,28 @@ namespace CafeSystem #endif } + void logPlatformInfo() + { + const char* platform = NULL; + #if BOOST_OS_WINDOWS + platform = "Windows"; + #elif BOOST_OS_LINUX + if (getenv ("APPIMAGE")) + platform = "Linux (AppImage)"; + else if (getenv ("SNAP")) + platform = "Linux (Snap)"; + else if (platform = getenv ("container")) + if (strcmp (platform, "flatpak") == 0) + platform = "Linux (Flatpak)"; + else + platform = "Linux"; + #elif BOOST_OS_MACOS + platform = "MacOS"; + #endif + cemuLog_log(LogType::Force, "Platform: {}", platform); + + } + // initialize all subsystems which are persistent and don't depend on a game running void Initialize() { @@ -501,6 +523,7 @@ namespace CafeSystem _CheckForWine(); // CPU and RAM info logCPUAndMemoryInfo(); + logPlatformInfo(); cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); // misc systems rplSymbolStorage_init(); From 890df997cb57bdfd92d93e30ed046127d81583b8 Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Tue, 8 Aug 2023 22:23:18 +0100 Subject: [PATCH 13/16] Simplify appstream summary description (#932) --- dist/linux/info.cemu.Cemu.metainfo.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dist/linux/info.cemu.Cemu.metainfo.xml b/dist/linux/info.cemu.Cemu.metainfo.xml index f056555b..ef59427f 100644 --- a/dist/linux/info.cemu.Cemu.metainfo.xml +++ b/dist/linux/info.cemu.Cemu.metainfo.xml @@ -3,15 +3,15 @@ info.cemu.Cemu Cemu - Software to emulate Wii U games and applications on PC - Software zum emulieren von Wii U Spielen und Anwendungen auf dem PC - Application pour émuler des jeux et applications Wii U sur PC - Applicatie om Wii U spellen en applicaties te emuleren op PC - Πρόγραμμα προσομοίωσης παιχνιδιών και εφαρμογών Wii U στον υπολογιστή - Software para emular juegos y aplicaciones de Wii U en PC - Software para emular jogos e aplicativos de Wii U no PC - Software per emulare giochi e applicazioni per Wii U su PC - Ojelmisto Wii U -pelien ja -sovellusten emulointiin PC:llä + Nintendo Wii U Emulator + Nintendo Wii U Emulator + Émulateur Nintendo Wii U + Nintendo Wii U Emulator + Εξομοιωτής Nintendo Wii U + Emulador de Nintendo Wii U + Emulador Nintendo Wii U + Emulatore Nintendo Wii U + Nintendo Wii U Emulaattori Cemu Project info.cemu.Cemu.desktop CC0-1.0 From 892ae13680a30d71b489ca754ddaa2b17828f85e Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 13 Aug 2023 14:48:54 +0200 Subject: [PATCH 14/16] Log Windows version + Fix logging crash on Linux --- src/Cafe/CafeSystem.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 10b49c60..8c2344ce 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -484,26 +484,53 @@ namespace CafeSystem #endif } + #if BOOST_OS_WINDOWS + std::string GetWindowsNamedVersion(uint32& buildNumber) + { + static char productName[256]; + HKEY hKey; + DWORD dwType = REG_SZ; + DWORD dwSize = sizeof(productName); + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)productName, &dwSize) != ERROR_SUCCESS) + strcpy(productName, "Windows"); + RegCloseKey(hKey); + } + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + buildNumber = osvi.dwBuildNumber; + return std::string(productName); + } + #endif + void logPlatformInfo() { + std::string buffer; const char* platform = NULL; #if BOOST_OS_WINDOWS - platform = "Windows"; + uint32 buildNumber; + std::string windowsVersionName = GetWindowsNamedVersion(buildNumber); + buffer = fmt::format("{} (Build {})", windowsVersionName, buildNumber); + platform = buffer.c_str(); #elif BOOST_OS_LINUX if (getenv ("APPIMAGE")) platform = "Linux (AppImage)"; else if (getenv ("SNAP")) platform = "Linux (Snap)"; else if (platform = getenv ("container")) + { if (strcmp (platform, "flatpak") == 0) platform = "Linux (Flatpak)"; + } else platform = "Linux"; #elif BOOST_OS_MACOS platform = "MacOS"; #endif cemuLog_log(LogType::Force, "Platform: {}", platform); - } // initialize all subsystems which are persistent and don't depend on a game running From 85aa4f095b119e98620451a0c19c80f656d944a6 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Tue, 15 Aug 2023 07:37:37 +0000 Subject: [PATCH 15/16] Linux/MacOS: Add wiimote support via HIDAPI (#934) --- CMakeLists.txt | 9 ++ src/input/CMakeLists.txt | 38 ++++-- src/input/InputManager.h | 3 + .../api/Wiimote/WiimoteControllerProvider.cpp | 128 +++++++----------- src/input/api/Wiimote/WiimoteMessages.h | 2 +- .../api/Wiimote/hidapi/HidapiWiimote.cpp | 55 ++++++++ src/input/api/Wiimote/hidapi/HidapiWiimote.h | 23 ++++ vcpkg.json | 4 + 8 files changed, 173 insertions(+), 89 deletions(-) create mode 100644 src/input/api/Wiimote/hidapi/HidapiWiimote.cpp create mode 100644 src/input/api/Wiimote/hidapi/HidapiWiimote.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 57615e86..34a28a06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,9 @@ if (WIN32) option(ENABLE_XINPUT "Enables the usage of XInput" ON) option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON) add_compile_definitions(HAS_DIRECTINPUT) + set(ENABLE_WIIMOTE ON) +elseif (UNIX) + option(ENABLE_HIDAPI "Build with HIDAPI" ON) endif() option(ENABLE_SDL "Enables the SDLController backend" ON) @@ -155,6 +158,12 @@ if (ENABLE_DISCORD_RPC) target_include_directories(discord-rpc INTERFACE ./dependencies/discord-rpc/include) endif() +if (ENABLE_HIDAPI) + find_package(hidapi REQUIRED) + set(ENABLE_WIIMOTE ON) + add_compile_definitions(HAS_HIDAPI) +endif () + if(UNIX AND NOT APPLE) if(ENABLE_FERAL_GAMEMODE) add_compile_definitions(ENABLE_FERAL_GAMEMODE) diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index ecb88cd2..9f542371 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -44,18 +44,6 @@ add_library(CemuInput set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") if(WIN32) - # Native wiimote (Win32 only for now) - target_sources(CemuInput PRIVATE - api/Wiimote/WiimoteControllerProvider.h - api/Wiimote/windows/WinWiimoteDevice.cpp - api/Wiimote/windows/WinWiimoteDevice.h - api/Wiimote/WiimoteControllerProvider.cpp - api/Wiimote/WiimoteMessages.h - api/Wiimote/NativeWiimoteController.h - api/Wiimote/NativeWiimoteController.cpp - api/Wiimote/WiimoteDevice.h - ) - # XInput target_sources(CemuInput PRIVATE api/XInput/XInputControllerProvider.cpp @@ -73,6 +61,29 @@ if(WIN32) ) endif() +if (ENABLE_WIIMOTE) + target_sources(CemuInput PRIVATE + api/Wiimote/WiimoteControllerProvider.h + api/Wiimote/WiimoteControllerProvider.cpp + api/Wiimote/WiimoteMessages.h + api/Wiimote/NativeWiimoteController.h + api/Wiimote/NativeWiimoteController.cpp + api/Wiimote/WiimoteDevice.h + ) + if (ENABLE_HIDAPI) + target_sources(CemuInput PRIVATE + api/Wiimote/hidapi/HidapiWiimote.cpp + api/Wiimote/hidapi/HidapiWiimote.h + ) + elseif (WIN32) + target_sources(CemuInput PRIVATE + api/Wiimote/windows/WinWiimoteDevice.cpp + api/Wiimote/windows/WinWiimoteDevice.h + ) + endif() +endif () + + target_include_directories(CemuInput PUBLIC "../") target_link_libraries(CemuInput PRIVATE @@ -87,6 +98,9 @@ target_link_libraries(CemuInput PRIVATE pugixml::pugixml SDL2::SDL2 ) +if (ENABLE_HIDAPI) + target_link_libraries(CemuInput PRIVATE hidapi::hidapi) +endif() if (ENABLE_WXWIDGETS) target_link_libraries(CemuInput PRIVATE wx::base wx::core) diff --git a/src/input/InputManager.h b/src/input/InputManager.h index 848c4810..345f7ba0 100644 --- a/src/input/InputManager.h +++ b/src/input/InputManager.h @@ -3,6 +3,9 @@ #if BOOST_OS_WINDOWS #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/XInput/XInputControllerProvider.h" +#endif + +#if defined(HAS_HIDAPI) || BOOST_OS_WINDOWS #include "input/api/Wiimote/WiimoteControllerProvider.h" #endif diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index a742a9d3..0ebf88aa 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -2,7 +2,9 @@ #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" -#if BOOST_OS_WINDOWS +#ifdef HAS_HIDAPI +#include "input/api/Wiimote/hidapi/HidapiWiimote.h" +#elif BOOST_OS_WINDOWS #include "input/api/Wiimote/windows/WinWiimoteDevice.h" #endif @@ -36,7 +38,7 @@ std::vector> WiimoteControllerProvider::get_cont { // only add unknown, connected devices to our list const bool is_new_device = std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), - [&device](const auto& it) { return *it.device == *device; }); + [device](const auto& it) { return *it.device == *device; }); if (is_new_device) { m_wiimotes.push_back(std::make_unique(device)); @@ -163,9 +165,7 @@ void WiimoteControllerProvider::reader_thread() { case kStatus: { -#ifdef WIIMOTE_DEBUG - printf("WiimoteControllerProvider::read_thread: kStatus\n"); -#endif + cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kStatus"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; new_state.flags = *data; @@ -183,9 +183,7 @@ void WiimoteControllerProvider::reader_thread() if (HAS_FLAG(new_state.flags, kExtensionConnected)) { -#ifdef WIIMOTE_DEBUG - printf("\tExtension flag is set\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension flag is set"); if(new_state.m_extension.index() == 0) request_extension(index); } @@ -199,9 +197,7 @@ void WiimoteControllerProvider::reader_thread() break; case kRead: { -#ifdef WIIMOTE_DEBUG - printf("WiimoteControllerProvider::read_thread: kRead\n"); -#endif + cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kRead"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; const uint8 error_flag = *data & 0xF, size = (*data >> 4) + 1; @@ -209,10 +205,9 @@ void WiimoteControllerProvider::reader_thread() if (error_flag) { + // 7 means that wiimote is already enabled or not available -#ifdef WIIMOTE_DEBUG - printf("Received error on data read 0x%x\n", error_flag); -#endif + cemuLog_logDebug(LogType::Force,"Received error on data read {:#x}", error_flag); continue; } @@ -220,9 +215,7 @@ void WiimoteControllerProvider::reader_thread() data += 2; if (address == (kRegisterCalibration & 0xFFFF)) { -#ifdef WIIMOTE_DEBUG - printf("Calibration received\n"); -#endif + cemuLog_logDebug(LogType::Force,"Calibration received"); cemu_assert(size == 8); @@ -255,17 +248,10 @@ void WiimoteControllerProvider::reader_thread() { if (size == 0xf) { -#ifdef WIIMOTE_DEBUG - printf("Extension type received but no extension connected\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension type received but no extension connected"); continue; } - -#ifdef WIIMOTE_DEBUG - printf("Extension type received\n"); -#endif - cemu_assert(size == 6); auto be_type = *(betype*)data; data += 6; // 48 @@ -274,42 +260,38 @@ void WiimoteControllerProvider::reader_thread() switch (be_type.value()) { case kExtensionNunchuck: -#ifdef WIIMOTE_DEBUG - printf("\tNunchuck\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: Nunchuck"); new_state.m_extension = NunchuckData{}; break; case kExtensionClassic: -#ifdef WIIMOTE_DEBUG - printf("\tClassic\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic"); new_state.m_extension = ClassicData{}; break; case kExtensionClassicPro: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic Pro"); + break; case kExtensionGuitar: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Guitar"); + break; case kExtensionDrums: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Drums"); + break; case kExtensionBalanceBoard: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Balance Board"); + break; case kExtensionMotionPlus: - //m_motion_plus = true; -#ifdef WIIMOTE_DEBUG - printf("\tMotion plus detected\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: MotionPlus"); set_motion_plus(index, true); new_state.m_motion_plus = MotionPlusData{}; break; case kExtensionPartialyInserted: -#ifdef WIIMOTE_DEBUG - printf("\tExtension only partially inserted!\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension only partially inserted"); new_state.m_extension = {}; request_status(index); break; default: - new_state.m_extension = {}; + cemuLog_logDebug(LogType::Force,"Unknown extension: {:#x}", be_type.value()); + new_state.m_extension = {}; break; } @@ -319,9 +301,7 @@ void WiimoteControllerProvider::reader_thread() else if (address == (kRegisterExtensionCalibration & 0xFFFF)) { cemu_assert(size == 0x10); -#ifdef WIIMOTE_DEBUG - printf("Extension calibration received\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension calibration received"); std::visit( overloaded { @@ -337,9 +317,7 @@ void WiimoteControllerProvider::reader_thread() std::array zero{}; if (memcmp(zero.data(), data, zero.size()) == 0) { -#ifdef WIIMOTE_DEBUG - printf("\tExtension calibration data is zero!\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension calibration data is zero"); return; } @@ -372,15 +350,23 @@ void WiimoteControllerProvider::reader_thread() } else { -#ifdef WIIMOTE_DEBUG - printf("Unhandled read data received\n"); -#endif - continue; + cemuLog_logDebug(LogType::Force,"Unhandled read data received"); + continue; } update_report = true; } break; + case kAcknowledge: + { + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + const auto report_id = *data++; + const auto error = *data++; + if (error) + cemuLog_logDebug(LogType::Force, "Error {:#x} from output report {:#x}", error, report_id); + break; + } case kDataCore: { // 30 BB BB @@ -476,10 +462,7 @@ void WiimoteControllerProvider::reader_thread() orientation /= tmp;*/ mp.orientation = orientation; -#ifdef WIIMOTE_DEBUG - printf("\tmp: %.2lf %.2lf %.2lf\n", mp.orientation.x, mp.orientation.y, - mp.orientation.z); -#endif + cemuLog_logDebug(LogType::Force,"MotionPlus: {:.2f}, {:.2f} {:.2f}", mp.orientation.x, mp.orientation.y, mp.orientation.z); }, [data](NunchuckData& nunchuck) mutable { @@ -553,12 +536,11 @@ void WiimoteControllerProvider::reader_thread() zero3, zero4 ); -#ifdef WIIMOTE_DEBUG - printf("\tn: %d,%d | %lf - %lf | %.2lf %.2lf %.2lf\n", nunchuck.z, nunchuck.c, - nunchuck.axis.x, nunchuck.axis.y, - RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), - RadToDeg(nunchuck.acceleration.z)); -#endif + cemuLog_logDebug(LogType::Force,"Nunchuck: Z={}, C={} | {}, {} | {:.2f}, {:.2f}, {:.2f}", + nunchuck.z, nunchuck.c, + nunchuck.axis.x, nunchuck.axis.y, + RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), + RadToDeg(nunchuck.acceleration.z)); }, [data](ClassicData& classic) mutable { @@ -592,11 +574,11 @@ void WiimoteControllerProvider::reader_thread() classic.trigger = classic.raw_trigger; classic.trigger /= 31.0f; -#ifdef WIIMOTE_DEBUG - printf("\tc: %d | %lf - %lf | %lf - %lf | %lf - %lf\n", classic.buttons, - classic.left_axis.x, classic.left_axis.y, classic.right_axis.x, - classic.right_axis.y, classic.trigger.x, classic.trigger.y); -#endif + cemuLog_logDebug(LogType::Force,"Classic Controller: Buttons={:b} | {}, {} | {}, {} | {}, {}", + classic.buttons, classic.left_axis.x, classic.left_axis.y, + classic.right_axis.x, classic.right_axis.y, classic.trigger.x, + classic.trigger.y); + } }, new_state.m_extension); @@ -609,9 +591,7 @@ void WiimoteControllerProvider::reader_thread() break; } default: -#ifdef WIIMOTE_DEBUG - printf("unhandled input packet id %d for wiimote\n", data[0]); -#endif + cemuLog_logDebug(LogType::Force,"unhandled input packet id {} for wiimote {}", id, index); } // update motion data @@ -694,7 +674,6 @@ void WiimoteControllerProvider::parse_acceleration(WiimoteState& wiimote_state, tmp -= calib.zero; acceleration = (wiimote_state.m_acceleration / tmp); - //printf("%d, %d, %d\n", (int)m_acceleration.x, (int)m_acceleration.y, (int)m_acceleration.z); const float pi_2 = (float)std::numbers::pi / 2.0f; wiimote_state.m_roll = std::atan2(acceleration.z, acceleration.x) - pi_2; } @@ -713,7 +692,6 @@ void WiimoteControllerProvider::rotate_ir(WiimoteState& wiimote_state) i++; if (!dot.visible) continue; - //printf("%d:\t%.02lf | %.02lf\n", i, dot.pos.x, dot.pos.y); // move to center, rotate and move back dot.pos -= 0.5f; dot.pos.x = (dot.pos.x * cos) + (dot.pos.y * (-sin)); @@ -973,9 +951,7 @@ void WiimoteControllerProvider::update_report_type(size_t index) else report_type = kDataCore; -#ifdef WIIMOTE_DEBUG - printf("Setting report type to %d\n", report_type); -#endif + cemuLog_logDebug(LogType::Force,"Setting report type to {}", report_type); send_packet(index, {kType, 0x04, report_type}); state.ir_camera.mode = set_ir_camera(index, true); diff --git a/src/input/api/Wiimote/WiimoteMessages.h b/src/input/api/Wiimote/WiimoteMessages.h index 712c3a5c..32dd4658 100644 --- a/src/input/api/Wiimote/WiimoteMessages.h +++ b/src/input/api/Wiimote/WiimoteMessages.h @@ -8,7 +8,7 @@ enum InputReportId : uint8 kStatus = 0x20, kRead = 0x21, - kWrite = 0x22, + kAcknowledge = 0x22, kDataCore = 0x30, kDataCoreAcc = 0x31, diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp new file mode 100644 index 00000000..7baad55d --- /dev/null +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -0,0 +1,55 @@ +#include "HidapiWiimote.h" + +static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e; +static constexpr uint16 WIIMOTE_PRODUCT_ID = 0x0306; +static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330; +static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22; + +HidapiWiimote::HidapiWiimote(hid_device* dev, uint64_t identifier) + : m_handle(dev), m_identifier(identifier) { + +} + +bool HidapiWiimote::write_data(const std::vector &data) { + return hid_write(m_handle, data.data(), data.size()) >= 0; +} + +std::optional> HidapiWiimote::read_data() { + std::array read_data{}; + const auto result = hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH); + if (result < 0) + return {}; + return {{read_data.cbegin(), read_data.cbegin() + result}}; +} + +std::vector HidapiWiimote::get_devices() { + std::vector wiimote_devices; + hid_init(); + const auto device_enumeration = hid_enumerate(WIIMOTE_VENDOR_ID, 0x0); + + for (auto it = device_enumeration; it != nullptr; it = it->next){ + if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID) + continue; + auto dev = hid_open_path(it->path); + if (!dev){ + cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr))); + } + else { + // Enough to have a unique id for each device within a session + uint64_t id = (static_cast(it->interface_number) << 32) | + (static_cast(it->usage_page) << 16) | + (it->usage); + wiimote_devices.push_back(std::make_shared(dev, id)); + } + } + hid_free_enumeration(device_enumeration); + return wiimote_devices; +} + +bool HidapiWiimote::operator==(WiimoteDevice& o) const { + return m_identifier == static_cast(o).m_identifier; +} + +HidapiWiimote::~HidapiWiimote() { + hid_close(m_handle); +} diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h new file mode 100644 index 00000000..6bd90dac --- /dev/null +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class HidapiWiimote : public WiimoteDevice { +public: + HidapiWiimote(hid_device* dev, uint64_t identifier); + ~HidapiWiimote() override; + + bool write_data(const std::vector &data) override; + std::optional> read_data() override; + bool operator==(WiimoteDevice& o) const override; + + static std::vector get_devices(); + +private: + hid_device* m_handle; + uint64_t m_identifier; + +}; + +using WiimoteDevice_t = HidapiWiimote; \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index d0facf8b..940ed748 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,6 +26,10 @@ "boost-static-string", "boost-random", "fmt", + { + "name": "hidapi", + "platform": "!windows" + }, "libpng", "glm", { From d8b9a74d861dbf857d2debc00f9ba12ea890c6ad Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:52:06 +0200 Subject: [PATCH 16/16] Latte: rendertarget is a bitmask (#942) --- src/Cafe/HW/Latte/Core/Latte.h | 4 ++++ src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index ed2116d0..f13abca0 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -84,6 +84,10 @@ extern uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring bu void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd); // render target + +#define RENDER_TARGET_TV (1 << 0) +#define RENDER_TARGET_DRC (1 << 2) + void LatteRenderTarget_updateScissorBox(); void LatteRenderTarget_trackUpdates(); diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 1d9adfe3..3a52f641 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -1048,9 +1048,9 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin } } - if (renderTarget == 4 && g_renderer->IsPadWindowActive()) + if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive()) LatteRenderTarget_copyToBackbuffer(texView, true); - if ((renderTarget == 1 && !showDRC) || (renderTarget == 4 && showDRC)) + if (((renderTarget & RENDER_TARGET_TV) && !showDRC) || ((renderTarget & RENDER_TARGET_DRC) && showDRC)) LatteRenderTarget_copyToBackbuffer(texView, false); }