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