diff --git a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md index 05eebe54..61bd5d06 100644 --- a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md +++ b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md @@ -11,13 +11,13 @@ assignees: '' Please keep in mind Cemu is EXPERIMENTAL SOFTWARE. Please read the FAQ: -https://cemu.info/faq.html/ +https://cemu.info/faq.html THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO: -https://discord.com/invite/5psYsup/ +https://discord.com/invite/5psYsup If the FAQ does not answer your question, please go to: -https://discord.com/invite/5psYsup/ +https://discord.com/invite/5psYsup When submitting an issue, please check the following: diff --git a/.gitignore b/.gitignore index 51700872..e7f104d2 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ bin/debugger/* bin/sdcard/* bin/screenshots/* bin/dump/* +bin/cafeLibs/* !bin/shaderCache/info.txt bin/shaderCache/* diff --git a/bin/gameProfiles/default/000500001010EB00.ini b/bin/gameProfiles/default/000500001010EB00.ini index 281d21db..782398b5 100644 --- a/bin/gameProfiles/default/000500001010EB00.ini +++ b/bin/gameProfiles/default/000500001010EB00.ini @@ -1,4 +1 @@ -# Mario Kart 8 (JPN) - -[CPU] -cpuMode = Singlecore-Recompiler +# Mario Kart 8 (JPN) \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ec00.ini b/bin/gameProfiles/default/000500001010ec00.ini index 697aace3..5d2dc9d8 100644 --- a/bin/gameProfiles/default/000500001010ec00.ini +++ b/bin/gameProfiles/default/000500001010ec00.ini @@ -1,4 +1 @@ -# Mario Kart 8 (USA) - -[CPU] -cpuMode = Singlecore-Recompiler +# Mario Kart 8 (USA) \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ed00.ini b/bin/gameProfiles/default/000500001010ed00.ini index 23e1dffc..dc1f4d52 100644 --- a/bin/gameProfiles/default/000500001010ed00.ini +++ b/bin/gameProfiles/default/000500001010ed00.ini @@ -1,4 +1 @@ -# Mario Kart 8 (EUR) - -[CPU] -cpuMode = Singlecore-Recompiler +# Mario Kart 8 (EUR) \ No newline at end of file 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 d8e3c644..a1dbc97c 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 @@ -406,6 +410,18 @@ add_library(CemuCafe OS/libs/nn_nim/nn_nim.h OS/libs/nn_olv/nn_olv.cpp OS/libs/nn_olv/nn_olv.h + OS/libs/nn_olv/nn_olv_Common.cpp + OS/libs/nn_olv/nn_olv_Common.h + OS/libs/nn_olv/nn_olv_InitializeTypes.cpp + OS/libs/nn_olv/nn_olv_InitializeTypes.h + OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp + OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h + OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp + OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h + OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp + OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h + OS/libs/nn_olv/nn_olv_PostTypes.cpp + OS/libs/nn_olv/nn_olv_PostTypes.h OS/libs/nn_pdm/nn_pdm.cpp OS/libs/nn_pdm/nn_pdm.h OS/libs/nn_save/nn_save.cpp @@ -454,8 +470,6 @@ add_library(CemuCafe OS/RPL/rpl_structs.h OS/RPL/rpl_symbol_storage.cpp OS/RPL/rpl_symbol_storage.h - TitleList/BaseInfo.cpp - TitleList/BaseInfo.h TitleList/GameInfo.h TitleList/ParsedMetaXml.h TitleList/SaveInfo.cpp diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 4b314bb6..8b2c3189 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -44,8 +44,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" @@ -60,6 +61,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" @@ -280,7 +282,7 @@ struct static_assert(sizeof(SharedDataEntry) == 0x1C); -uint32 loadSharedData() +uint32 LoadSharedData() { // check if font files are dumped bool hasAllShareddataFiles = true; @@ -419,25 +421,6 @@ 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 @@ -458,24 +441,34 @@ namespace CafeSystem void InitVirtualMlcStorage(); void MlcStorageMountTitle(TitleInfo& titleInfo); + void MlcStorageUnmountAllTitles(); - bool sLaunchModeIsStandalone = false; + static bool s_initialized = false; + static SystemImplementation* s_implementation{nullptr}; + bool sLaunchModeIsStandalone = false; + std::optional> s_overrideArgs; bool sSystemRunning = false; TitleId sForegroundTitleId = 0; GameInfo2 sGameInfo_ForegroundTitle; + // initialize all subsystems which are persistent and don't depend on a game running void Initialize() { - static bool s_initialized = false; if (s_initialized) return; s_initialized = true; + // init core systems + fsc_init(); + memory_init(); + PPCCore_init(); + RPLLoader_InitState(); // allocate memory for all SysAllocators - // must happen before all COS modules, but also before iosu::kernel::Init() + // must happen before COS module init, but also before iosu::kernel::Initialize() SysAllocatorContainer::GetInstance().Initialize(); // init IOSU + iosuCrypto_init(); iosu::kernel::Initialize(); iosu::fsa::Initialize(); iosuIoctl_init(); @@ -488,6 +481,7 @@ namespace CafeSystem iosu::boss_init(); iosu::nim::Initialize(); iosu::pdm::Initialize(); + iosu::odm::Initialize(); // init Cafe OS avm::Initialize(); drmapp::Initialize(); @@ -505,14 +499,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()) { @@ -571,10 +597,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) @@ -609,26 +658,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; } @@ -667,10 +727,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; } @@ -717,6 +778,13 @@ namespace CafeSystem return sGameInfo_ForegroundTitle.GetVersion(); } + uint32 GetForegroundTitleSDKVersion() + { + if (sLaunchModeIsStandalone) + return 999999; + return sGameInfo_ForegroundTitle.GetSDKVersion(); + } + CafeConsoleRegion GetForegroundTitleRegion() { if (sLaunchModeIsStandalone) @@ -737,6 +805,13 @@ namespace CafeSystem return applicationName; } + uint32 GetForegroundTitleOlvAccesskey() + { + if (sLaunchModeIsStandalone) + return -1; + return sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetOlvAccesskey(); + } + std::string GetForegroundTitleArgStr() { if (sLaunchModeIsStandalone) @@ -747,6 +822,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() { @@ -763,39 +858,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; } @@ -845,10 +933,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)) @@ -875,6 +960,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; @@ -885,4 +980,9 @@ namespace CafeSystem return currentUpdatedApplicationHash; } + void RequestRecreateCanvas() + { + s_implementation->CafeRecreateCanvas(); + } + } diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index 130ff988..a1ee557a 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, @@ -26,17 +32,26 @@ namespace CafeSystem CafeSystemCallbacks* getCafeSystemCallbacks(); 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(); std::string GetForegroundTitleArgStr(); + uint32 GetForegroundTitleOlvAccesskey(); void ShutdownTitle(); @@ -47,6 +62,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 3b1565a8..031f2fb2 100644 --- a/src/Cafe/Filesystem/fsc.cpp +++ b/src/Cafe/Filesystem/fsc.cpp @@ -6,7 +6,7 @@ struct FSCMountPathNode std::string path; std::vector subnodes; FSCMountPathNode* parent; - // device target and path (if subnodes is empty) + // associated device target and path fscDeviceC* device{ nullptr }; void* ctx{ nullptr }; std::string deviceTargetPath; // the destination base path for the device, utf8 @@ -17,6 +17,25 @@ struct FSCMountPathNode { } + void AssignDevice(fscDeviceC* device, void* ctx, std::string_view deviceBasePath) + { + this->device = device; + this->ctx = ctx; + this->deviceTargetPath = deviceBasePath; + } + + void UnassignDevice() + { + this->device = nullptr; + this->ctx = nullptr; + this->deviceTargetPath.clear(); + } + + bool IsRootNode() const + { + return !parent; + } + ~FSCMountPathNode() { for (auto& itr : subnodes) @@ -141,9 +160,7 @@ sint32 fsc_mount(std::string_view mountPath, std::string_view targetPath, fscDev fscLeave(); return FSC_STATUS_INVALID_PATH; } - node->device = fscDevice; - node->ctx = ctx; - node->deviceTargetPath = std::move(targetPathWithSlash); + node->AssignDevice(fscDevice, ctx, targetPathWithSlash); fscLeave(); return FSC_STATUS_OK; } @@ -160,14 +177,13 @@ bool fsc_unmount(std::string_view mountPath, sint32 priority) } cemu_assert(mountPathNode->priority == priority); cemu_assert(mountPathNode->device); - // delete node - while (mountPathNode && mountPathNode->parent) + // unassign device + mountPathNode->UnassignDevice(); + // prune empty branch + while (mountPathNode && !mountPathNode->IsRootNode() && mountPathNode->subnodes.empty() && !mountPathNode->device) { FSCMountPathNode* parent = mountPathNode->parent; - cemu_assert(!(!mountPathNode->subnodes.empty() && mountPathNode->device)); - if (!mountPathNode->subnodes.empty()) - break; - parent->subnodes.erase(std::find(parent->subnodes.begin(), parent->subnodes.end(), mountPathNode)); + std::erase(parent->subnodes, mountPathNode); delete mountPathNode; mountPathNode = parent; } @@ -302,6 +318,15 @@ public: return true; } + bool fscRewindDir() override + { + if (!dirIterator) + return true; + + dirIterator->index = 0; + return true; + } + void addUniqueDirEntry(const FSCDirEntry& dirEntry) { // skip if already in list @@ -378,6 +403,7 @@ FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* { // return first found file cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)); + fscVirtualFile->m_isAppend = HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::IS_APPEND); fscLeave(); return fscVirtualFile; } @@ -598,6 +624,9 @@ uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size) fscLeave(); return 0; } + if (fscFile->m_isAppend) + fsc_setFileSeek(fscFile, fsc_getFileSize(fscFile)); + uint32 fscStatus = fscFile->fscWriteData(buffer, size); fscLeave(); return fscStatus; diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h index 235d6bea..1daa1c2e 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -24,7 +24,10 @@ enum class FSC_ACCESS_FLAG : uint8 // which types can be opened // invalid operation if neither is set OPEN_DIR = (1 << 4), - OPEN_FILE = (1 << 5) + OPEN_FILE = (1 << 5), + + // Writing seeks to the end of the file if set + IS_APPEND = (1 << 6) }; DEFINE_ENUM_FLAG_OPERATORS(FSC_ACCESS_FLAG); @@ -149,7 +152,15 @@ struct FSCVirtualFile return false; } + virtual bool fscRewindDir() + { + cemu_assert_unimplemented(); + return false; + } + FSCDirIteratorState* dirIterator{}; + + bool m_isAppend{ false }; }; #define FSC_PRIORITY_BASE (0) @@ -194,7 +205,6 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority); // hostFS device -void fscDeviceHostFS_mapBaseDirectories_deprecated(); bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority); // redirect device diff --git a/src/Cafe/Filesystem/fscDeviceHostFS.cpp b/src/Cafe/Filesystem/fscDeviceHostFS.cpp index f63d2920..85a04afe 100644 --- a/src/Cafe/Filesystem/fscDeviceHostFS.cpp +++ b/src/Cafe/Filesystem/fscDeviceHostFS.cpp @@ -289,13 +289,6 @@ public: } }; -void fscDeviceHostFS_mapBaseDirectories_deprecated() -{ - const auto mlc = ActiveSettings::GetMlcPath(); - fsc_mount("/cemuBossStorage/", _pathToUtf8(mlc / "usr/boss/"), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE); - fsc_mount("/vol/storage_mlc01/", _pathToUtf8(mlc / ""), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE); -} - bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority) { return fsc_mount(mountPath, hostTargetPath, &fscDeviceHostFSC::instance(), nullptr, priority) == FSC_STATUS_OK; diff --git a/src/Cafe/HW/Espresso/PPCScheduler.cpp b/src/Cafe/HW/Espresso/PPCScheduler.cpp index ab662150..2a3a4aaa 100644 --- a/src/Cafe/HW/Espresso/PPCScheduler.cpp +++ b/src/Cafe/HW/Espresso/PPCScheduler.cpp @@ -100,11 +100,6 @@ PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR) return hCPU; } -void PPCCore_deleteAllThreads() -{ - assert_dbg(); -} - void PPCCore_init() { } diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index a9f2d3ee..2b30326b 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -236,7 +236,6 @@ HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex); // HLE scheduler -void PPCCore_deleteAllThreads(); void PPCInterpreter_relinquishTimeslice(); void PPCCore_boostQuantum(sint32 numCycles); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 4fac51ad..6b830563 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -289,11 +289,16 @@ void PPCRecompiler_recompileAtAddress(uint32 address) bool r = PPCRecompiler_makeRecompiledFunctionActive(address, range, func, functionEntryPoints); } +std::thread s_threadRecompiler; +std::atomic_bool s_recompilerThreadStopSignal{false}; + void PPCRecompiler_thread() { SetThreadName("PPCRecompiler_thread"); while (true) { + if(s_recompilerThreadStopSignal) + return; std::this_thread::sleep_for(std::chrono::milliseconds(10)); // asynchronous recompilation: // 1) take address from queue @@ -326,7 +331,12 @@ void PPCRecompiler_thread() #define PPC_REC_ALLOC_BLOCK_SIZE (4*1024*1024) // 4MB -std::bitset<(MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE) / PPC_REC_ALLOC_BLOCK_SIZE> ppcRecompiler_reservedBlockMask; +constexpr uint32 PPCRecompiler_GetNumAddressSpaceBlocks() +{ + return (MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE + PPC_REC_ALLOC_BLOCK_SIZE - 1) / PPC_REC_ALLOC_BLOCK_SIZE; +} + +std::bitset ppcRecompiler_reservedBlockMask; void PPCRecompiler_reserveLookupTableBlock(uint32 offset) { @@ -496,16 +506,9 @@ void PPCRecompiler_init() MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true); PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); - uint32 codeRegionEnd = RPLLoader_GetMaxCodeOffset(); - codeRegionEnd = (codeRegionEnd + PPC_REC_ALLOC_BLOCK_SIZE - 1) & ~(PPC_REC_ALLOC_BLOCK_SIZE - 1); - - uint32 codeRegionSize = codeRegionEnd - PPC_REC_CODE_AREA_START; - cemuLog_logDebug(LogType::Force, "Allocating recompiler tables for range 0x{:08x}-0x{:08x}", PPC_REC_CODE_AREA_START, codeRegionEnd); - - for (uint32 i = 0; i < codeRegionSize; i += PPC_REC_ALLOC_BLOCK_SIZE) - { - PPCRecompiler_reserveLookupTableBlock(i); - } + PPCRecompiler_allocateRange(0, 0x1000); // the first entry is used for fallback to interpreter + PPCRecompiler_allocateRange(mmuRange_TRAMPOLINE_AREA.getBase(), mmuRange_TRAMPOLINE_AREA.getSize()); + PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize()); // init x64 recompiler instance data ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[0] = 1ULL << 63ULL; @@ -589,6 +592,33 @@ void PPCRecompiler_init() ppcRecompilerEnabled = true; // launch recompilation thread - std::thread t_recompiler(PPCRecompiler_thread); - t_recompiler.detach(); + s_recompilerThreadStopSignal = false; + s_threadRecompiler = std::thread(PPCRecompiler_thread); } + +void PPCRecompiler_Shutdown() +{ + // shut down recompiler thread + s_recompilerThreadStopSignal = true; + if(s_threadRecompiler.joinable()) + s_threadRecompiler.join(); + // clean up queues + while(!PPCRecompilerState.targetQueue.empty()) + PPCRecompilerState.targetQueue.pop(); + PPCRecompilerState.invalidationRanges.clear(); + // clean range store + rangeStore_ppcRanges.clear(); + // clean up memory + uint32 numBlocks = PPCRecompiler_GetNumAddressSpaceBlocks(); + for(uint32 i=0; ippcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true); + MemMapper::FreeMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true); + // mark as unmapped + ppcRecompiler_reservedBlockMask[i] = false; + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h index 4f89b985..2e40f19d 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h @@ -373,6 +373,7 @@ extern PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; extern bool ppcRecompilerEnabled; void PPCRecompiler_init(); +void PPCRecompiler_Shutdown(); void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size); diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index 36268645..ed2116d0 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -173,4 +173,5 @@ void LatteRenderTarget_updateViewport(); // Latte emulation control void Latte_Start(); void Latte_Stop(); -bool Latte_IsActive(); +bool Latte_GetStopSignal(); // returns true if stop was requested or if in stopped state +void LatteThread_Exit(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp index cf2d53cc..4b114ddf 100644 --- a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp +++ b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp @@ -96,7 +96,7 @@ void LatteAsyncCommands_waitUntilAllProcessed() void LatteAsyncCommands_checkAndExecute() { // quick check if queue is empty (requires no lock) - if (!Latte_IsActive()) + if (Latte_GetStopSignal()) LatteThread_Exit(); if (LatteAsyncCommandQueue.empty()) return; diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp index 1196376e..92c2d1b0 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp @@ -257,6 +257,11 @@ public: } } + bool empty() const + { + return m_map.empty(); + } + const std::map& getAll() const { return m_map; }; }; @@ -455,48 +460,6 @@ public: } if(m_invalidationRangeEnd <= m_invalidationRangeBegin) m_hasInvalidation = false; - - //if (resRangeBegin <= m_invalidationRangeBegin) - //{ - // // shrink/replace invalidation range from the bottom - // uint32 uploadBegin = m_invalidationRangeBegin;//std::max(m_invalidationRangeBegin, resRangeBegin); - // uint32 uploadEnd = std::min(resRangeEnd, m_invalidationRangeEnd); - // cemu_assert_debug(uploadEnd >= uploadBegin); - // if (uploadBegin != uploadEnd) - // checkAndSyncModifications(uploadBegin, uploadEnd, true); - // m_invalidationRangeBegin = uploadEnd; - // cemu_assert_debug(m_invalidationRangeBegin <= m_invalidationRangeEnd); - // if (m_invalidationRangeBegin >= m_invalidationRangeEnd) - // m_hasInvalidation = false; - //} - //else if (resRangeEnd >= m_invalidationRangeEnd) - //{ - // // shrink/replace invalidation range from the top - // uint32 uploadBegin = std::max(m_invalidationRangeBegin, resRangeBegin); - // uint32 uploadEnd = m_invalidationRangeEnd;// std::min(resRangeEnd, m_invalidationRangeEnd); - // cemu_assert_debug(uploadEnd >= uploadBegin); - // if (uploadBegin != uploadEnd) - // checkAndSyncModifications(uploadBegin, uploadEnd, true); - // m_invalidationRangeEnd = uploadBegin; - // cemu_assert_debug(m_invalidationRangeBegin <= m_invalidationRangeEnd); - // if (m_invalidationRangeBegin >= m_invalidationRangeEnd) - // m_hasInvalidation = false; - //} - //else - //{ - // // since we cant cut holes into the range upload it in it's entirety - // cemu_assert_debug(m_invalidationRangeEnd <= m_rangeEnd); - // cemu_assert_debug(m_invalidationRangeBegin >= m_rangeBegin); - // cemu_assert_debug(m_invalidationRangeBegin < m_invalidationRangeEnd); - // checkAndSyncModifications(m_invalidationRangeBegin, m_invalidationRangeEnd, true); - // m_hasInvalidation = false; - //} - - - - // todo - dont re-upload the whole range immediately - // under ideal circumstances we would only upload the data range requested for the current draw call - // but this is a hot path so we can't check } } @@ -827,6 +790,21 @@ private: static std::vector g_deallocateQueue; public: + static void UnloadAll() + { + size_t i = 0; + while (i < s_allCacheNodes.size()) + { + BufferCacheNode* node = s_allCacheNodes[i]; + node->ReleaseCacheMemoryImmediately(); + LatteBufferCache_removeSingleNodeFromTree(node); + delete node; + } + for(auto& it : s_allCacheNodes) + delete it; + s_allCacheNodes.clear(); + g_deallocateQueue.clear(); + } static void ProcessDeallocations() { @@ -931,7 +909,6 @@ public: }; std::vector BufferCacheNode::g_deallocateQueue; - IntervalTree2 g_gpuBufferCache; void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node) @@ -1009,10 +986,16 @@ void LatteBufferCache_processDeallocations() void LatteBufferCache_init(size_t bufferSize) { + cemu_assert_debug(g_gpuBufferCache.empty()); g_gpuBufferHeap.reset(new VHeap(nullptr, (uint32)bufferSize)); g_renderer->bufferCache_init((uint32)bufferSize); } +void LatteBufferCache_UnloadAll() +{ + BufferCacheNode::UnloadAll(); +} + void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum) { g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.h b/src/Cafe/HW/Latte/Core/LatteBufferCache.h index da285192..62ae3f1f 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferCache.h +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.h @@ -1,6 +1,7 @@ #pragma once void LatteBufferCache_init(size_t bufferSize); +void LatteBufferCache_UnloadAll(); uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size); void LatteBufferCache_copyStreamoutDataToCache(MPTR physAddress, uint32 size, uint32 streamoutBufferOffset); diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 671ba496..37ce8ff9 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -125,7 +125,7 @@ uint32 LatteCP_readU32Deprc() readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); if (readDistance != 0) break; - if (!Latte_IsActive()) + if (Latte_GetStopSignal()) LatteThread_Exit(); // still no command data available, do some other tasks @@ -172,7 +172,7 @@ void LatteCP_waitForNWords(uint32 numWords) if (readDistance >= waitDistance) break; - if (!Latte_IsActive()) + if (Latte_GetStopSignal()) LatteThread_Exit(); // still no command data available, do some other tasks diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp index 09a8cc58..22f4e262 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp @@ -605,42 +605,3 @@ void LatteOverlay_updateStats(double fps, sint32 drawcalls) // update vram g_renderer->GetVRAMInfo(g_state.vramUsage, g_state.vramTotal); } - -void LatteOverlay_updateStatsPerFrame() -{ - if (!ActiveSettings::FrameProfilerEnabled()) - return; - // update frametime graph - uint32 frameTime_total = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_frameTime.getPreviousFrameValue()); - uint32 frameTime_idle = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_idleTime.getPreviousFrameValue()); - uint32 frameTime_dcStageTextures = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageTextures.getPreviousFrameValue()); - uint32 frameTime_dcStageVertexMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageVertexMgr.getPreviousFrameValue()); - uint32 frameTime_dcStageShaderAndUniformMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr.getPreviousFrameValue()); - uint32 frameTime_dcStageIndexMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageIndexMgr.getPreviousFrameValue()); - uint32 frameTime_dcStageMRT = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageMRT.getPreviousFrameValue()); - uint32 frameTime_dcStageDrawcallAPI = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageDrawcallAPI.getPreviousFrameValue()); - uint32 frameTime_waitForAsync = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_waitForAsync.getPreviousFrameValue()); - - // make sure total frame time is not less than it's sums - uint32 minimumExpectedFrametime = - frameTime_idle + - frameTime_dcStageTextures + - frameTime_dcStageVertexMgr + - frameTime_dcStageShaderAndUniformMgr + - frameTime_dcStageIndexMgr + - frameTime_dcStageMRT + - frameTime_dcStageDrawcallAPI + - frameTime_waitForAsync; - frameTime_total = std::max(frameTime_total, minimumExpectedFrametime); - - //g_state.frametimeGraph.appendEntry(); - //g_state.frametimeGraph.setCurrentEntryValue(0xFF404040, frameTime_idle); - //g_state.frametimeGraph.setCurrentEntryValue(0xFFFFC0FF, frameTime_waitForAsync); - //g_state.frametimeGraph.setCurrentEntryValue(0xFF000040, frameTime_dcStageTextures); // dark red - //g_state.frametimeGraph.setCurrentEntryValue(0xFF004000, frameTime_dcStageVertexMgr); // dark green - //g_state.frametimeGraph.setCurrentEntryValue(0xFFFFFF80, frameTime_dcStageShaderAndUniformMgr); // blueish - //g_state.frametimeGraph.setCurrentEntryValue(0xFF800080, frameTime_dcStageIndexMgr); // purple - //g_state.frametimeGraph.setCurrentEntryValue(0xFF00FF00, frameTime_dcStageMRT); // green - //g_state.frametimeGraph.setCurrentEntryValue(0xFF00FFFF, frameTime_dcStageDrawcallAPI); // yellow - //g_state.frametimeGraph.setCurrentEntryValue(0xFFBBBBBB, frameTime_total - minimumExpectedFrametime); -} diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.h b/src/Cafe/HW/Latte/Core/LatteOverlay.h index 3df83d1d..e497abb0 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.h +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.h @@ -3,6 +3,5 @@ void LatteOverlay_init(); void LatteOverlay_render(bool pad_view); void LatteOverlay_updateStats(double fps, sint32 drawcalls); -void LatteOverlay_updateStatsPerFrame(); void LatteOverlay_pushNotification(const std::string& text, sint32 duration); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp index 8d10765f..7ed59522 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp @@ -116,7 +116,6 @@ void LattePerformanceMonitor_frameEnd() cafeSystemCallbacks->updateWindowTitles(false, false, fps); } } - LatteOverlay_updateStatsPerFrame(); } void LattePerformanceMonitor_frameBegin() diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 0d293ecd..f835140c 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -373,6 +373,7 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade if ((colorBufferWidth < (sint32)scissorAccessWidth) || (colorBufferHeight < (sint32)scissorAccessHeight)) { + // log this? colorBufferMask &= ~(1<shaderType); @@ -156,10 +156,14 @@ bool LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader) } else if (baseIt->second == shader) { - if (baseIt->second->next) - cache.emplace(shader->baseHash, baseIt->second->next); - else - cache.erase(baseIt); + cemu_assert_debug(baseIt->second == shader); + cache.erase(baseIt); + if (shader->next) + { + cemu_assert_debug(shader->baseHash == shader->next->baseHash); + cache.emplace(shader->baseHash, shader->next); + } + shader->next = 0; removed = true; } else @@ -176,7 +180,7 @@ bool LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader) } } } - return removed; + cemu_assert(removed); } void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type) @@ -1009,3 +1013,16 @@ void LatteSHRC_Init() cemu_assert_debug(sGeometryShaders.empty()); cemu_assert_debug(sPixelShaders.empty()); } + +void LatteSHRC_UnloadAll() +{ + while(!sVertexShaders.empty()) + LatteShader_free(sVertexShaders.begin()->second); + cemu_assert_debug(sVertexShaders.empty()); + while(!sGeometryShaders.empty()) + LatteShader_free(sGeometryShaders.begin()->second); + cemu_assert_debug(sGeometryShaders.empty()); + while(!sPixelShaders.empty()) + LatteShader_free(sPixelShaders.begin()->second); + cemu_assert_debug(sPixelShaders.empty()); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteShader.h b/src/Cafe/HW/Latte/Core/LatteShader.h index eb623d85..f8dc6d1a 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.h +++ b/src/Cafe/HW/Latte/Core/LatteShader.h @@ -3,6 +3,7 @@ #include "Cafe/HW/Latte/ISA/RegDefines.h" void LatteSHRC_Init(); +void LatteSHRC_UnloadAll(); void LatteSHRC_ResetCachedShaderHash(); void LatteShaderSHRC_UpdateFetchShader(); @@ -117,11 +118,12 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen); // shader cache file -void LatteShaderCache_load(); +void LatteShaderCache_Load(); +void LatteShaderCache_Close(); void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader); void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount); void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader); -// todo - sort this +// todo - refactor this sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp index 58fdf191..5e092c55 100644 --- a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp @@ -51,7 +51,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 @@ -60,7 +60,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); @@ -212,7 +212,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; @@ -244,21 +244,21 @@ void LatteShaderCache_load() // 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 @@ -297,13 +297,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; @@ -313,7 +313,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++; @@ -336,7 +336,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); #if !__ANDROID__ g_renderer->BeginFrame(true); @@ -369,6 +369,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; @@ -489,13 +491,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() @@ -513,7 +517,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 @@ -532,12 +536,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 @@ -557,12 +561,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) @@ -578,7 +582,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) @@ -751,3 +755,20 @@ bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderIn return LatteShaderCache_readSeparablePixelShader(streamReader, version); 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(); +} diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 21abb284..fd51c2e7 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -182,9 +182,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 @@ -195,7 +194,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; @@ -231,20 +230,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/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index 53d04de5..d5cec237 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -1201,20 +1201,10 @@ void OpenGLRenderer::draw_beginSequence() void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) { bool isMinimal = !isFirst; - if (ActiveSettings::FrameProfilerEnabled()) - { - if (isMinimal) - draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); - else - draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); - } - else - { - if (isMinimal) - draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); - else - draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); - } + if (isMinimal) + draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); + else + draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); } void OpenGLRenderer::draw_endSequence() 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 8a876157..7c0dba72 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Renderer.cpp @@ -58,6 +58,7 @@ void Renderer::Shutdown() // imgui ImGui::DestroyContext(imguiTVContext); ImGui::DestroyContext(imguiPadContext); + ImGui_ClearFonts(); delete imguiFontAtlas; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 604fba0d..804d03cc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -451,3 +451,9 @@ void RendererShaderVk::ShaderCacheLoading_end() // keep g_spirvCache open since we will write to it while the game is running s_isLoadingShadersVk = false; } + +void RendererShaderVk::ShaderCacheLoading_Close() +{ + delete s_spirvCache; + s_spirvCache = nullptr; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h index b1883c3d..561145f9 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h @@ -22,7 +22,8 @@ class RendererShaderVk : public RendererShader public: static void ShaderCacheLoading_begin(uint64 cacheTitleId); - static void ShaderCacheLoading_end(); + static void ShaderCacheLoading_end(); + static void ShaderCacheLoading_Close(); RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode); virtual ~RendererShaderVk(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 7586a1b7..74247b9a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -117,6 +117,15 @@ void VulkanPipelineStableCache::EndLoading() // keep cache file open for writing of new pipelines } +void VulkanPipelineStableCache::Close() +{ + if(s_cache) + { + delete s_cache; + s_cache = nullptr; + } +} + struct CachedPipeline { struct ShaderHash diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h index a18bb982..7cba2930 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h @@ -41,6 +41,7 @@ public: bool UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders); void EndLoading(); void LoadPipelineFromCache(std::span fileData); + void Close(); // called on title exit bool HasPipelineCached(uint64 baseHash, uint64 pipelineStateHash); void AddCurrentStateToCache(uint64 baseHash, uint64 pipelineStateHash); diff --git a/src/Cafe/HW/MMU/MMU.cpp b/src/Cafe/HW/MMU/MMU.cpp index 97a8e952..d5751050 100644 --- a/src/Cafe/HW/MMU/MMU.cpp +++ b/src/Cafe/HW/MMU/MMU.cpp @@ -101,7 +101,7 @@ void MMURange::mapMem() void MMURange::unmapMem() { - cemu_assert_debug(false); + MemMapper::FreeMemory(memory_base + baseAddress, size, true); m_isMapped = false; } @@ -193,6 +193,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 03238aaa..794785fa 100644 --- a/src/Cafe/HW/MMU/MMU.h +++ b/src/Cafe/HW/MMU/MMU.h @@ -2,6 +2,7 @@ void memory_init(); void memory_mapForCurrentTitle(); +void memory_unmapForCurrentTitle(); void memory_logModifiedMemoryRanges(); void memory_enableOverlayArena(); @@ -263,4 +264,6 @@ namespace MMU uint16 ReadMMIO_32(PAddr address); uint16 ReadMMIO_16(PAddr address); -} \ No newline at end of file +} + +#define MMU_IsInPPCMemorySpace(__ptr) ((const uint8*)(__ptr) >= memory_base && (const uint8*)(__ptr) < (memory_base + 0x100000000)) \ No newline at end of file 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/fsa/fsa_types.h b/src/Cafe/IOSU/fsa/fsa_types.h index 2825e421..a7757486 100644 --- a/src/Cafe/IOSU/fsa/fsa_types.h +++ b/src/Cafe/IOSU/fsa/fsa_types.h @@ -17,9 +17,11 @@ enum class FS_RESULT : sint32 // aka FSStatus enum class FSA_RESULT : sint32 // aka FSError/FSAStatus { - SUCCESS = 0, - END_DIR = -0x30000 - 0x04, - END_FILE = -0x30000 - 0x05, + OK = 0, + NOT_INIT = -0x30000 - 0x01, + END_OF_DIRECTORY = -0x30000 - 0x04, + END_OF_FILE = -0x30000 - 0x05, + MAX_CLIENTS = -0x30000 - 0x12, MAX_FILES = -0x30000 - 0x13, MAX_DIRS = -0x30000 - 0x14, ALREADY_EXISTS = -0x30000 - 0x16, @@ -34,6 +36,7 @@ enum class FSA_RESULT : sint32 // aka FSError/FSAStatus INVALID_DIR_HANDLE = -0x30000 - 0x27, NOT_FILE = -0x30000 - 0x28, NOT_DIR = -0x30000 - 0x29, + OUT_OF_RESOURCES = -0x30000 - 0x2C, FATAL_ERROR = -0x30000 - 0x400, }; @@ -46,6 +49,7 @@ enum class FSA_CMD_OPERATION_TYPE : uint32 RENAME = 0x9, OPENDIR = 0xA, READDIR = 0xB, + REWINDDIR = 0xC, CLOSEDIR = 0xD, OPENFILE = 0xE, READ = 0xF, @@ -55,6 +59,7 @@ enum class FSA_CMD_OPERATION_TYPE : uint32 ISEOF = 0x13, GETSTATFILE = 0x14, CLOSEFILE = 0x15, + FLUSHFILE = 0x17, QUERYINFO = 0x18, APPENDFILE = 0x19, TRUNCATEFILE = 0x1A, @@ -81,6 +86,7 @@ enum class FSFlag : uint32 { NONE = 0, IS_DIR = 0x80000000, + IS_FILE = 0x01000000, }; DEFINE_ENUM_FLAG_OPERATORS(FSFlag); @@ -111,8 +117,18 @@ struct FSDirEntry_t static_assert(sizeof(FSDirEntry_t) == 0xE4); +struct FSADeviceInfo_t +{ + uint8 ukn0[0x8]; + uint64be deviceSizeInSectors; + uint32be deviceSectorSize; + uint8 ukn014[0x14]; +}; +static_assert(sizeof(FSADeviceInfo_t) == 0x28); + #pragma pack() // query types for QueryInfo #define FSA_QUERY_TYPE_FREESPACE 0 +#define FSA_QUERY_TYPE_DEVICE_INFO 4 #define FSA_QUERY_TYPE_STAT 5 diff --git a/src/Cafe/IOSU/fsa/iosu_fsa.cpp b/src/Cafe/IOSU/fsa/iosu_fsa.cpp index 17d86266..1429d083 100644 --- a/src/Cafe/IOSU/fsa/iosu_fsa.cpp +++ b/src/Cafe/IOSU/fsa/iosu_fsa.cpp @@ -54,7 +54,7 @@ namespace iosu FSA_RESULT FSA_convertFSCtoFSAStatus(sint32 fscError) { if (fscError == FSC_STATUS_OK) - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; else if (fscError == FSC_STATUS_FILE_NOT_FOUND) return FSA_RESULT::NOT_FOUND; else if (fscError == FSC_STATUS_ALREADY_EXISTS) @@ -108,14 +108,30 @@ namespace iosu if ((input.size() - idx) >= 3 && input[idx + 1] == '.' && input[idx + 2] == '/') { // "../" - cemu_assert_unimplemented(); + while(!tmp.empty()) + { + if(tmp.back() == '/') + { + tmp.pop_back(); + break; + } + tmp.pop_back(); + } idx += 3; continue; } else if ((input.size() - idx) == 2 && input[idx + 1] == '.') { // ".." at the end - cemu_assert_unimplemented(); + while(!tmp.empty()) + { + if(tmp.back() == '/') + { + tmp.pop_back(); + break; + } + tmp.pop_back(); + } idx += 2; continue; } @@ -175,7 +191,7 @@ namespace iosu it.isAllocated = true; uint32 handleVal = ((uint32)i << 16) | (uint32)checkValue; handleOut = (FSResHandle)handleVal; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } cemuLog_log(LogType::Force, "FSA: Ran out of file handles"); return FSA_RESULT::FATAL_ERROR; @@ -194,7 +210,7 @@ namespace iosu return FSA_RESULT::INVALID_FILE_HANDLE; it.fscFile = nullptr; it.isAllocated = false; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSCVirtualFile* GetByHandle(FSResHandle handle) @@ -229,10 +245,9 @@ namespace iosu accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION; else if (strcmp(accessModifierStr, "r+") == 0) { - // r+ will create a new file if it doesn't exist // the cursor will be set to the beginning of the file // allows read and write access - accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE; // create if non exists, read, write + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION; // read, write } else if (strcmp(accessModifierStr, "w") == 0) { @@ -252,10 +267,12 @@ namespace iosu } else if (strcmp(accessModifierStr, "a+") == 0) { - cemu_assert_debug(false); // a+ is kind of special. Writing always happens at the end but the read cursor can dynamically move - // but Cafe OS might not support this. Needs investigation. - // this also used to be FILE_ALWAYS_CREATE in 1.26.2 and before - accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE; + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE | FSC_ACCESS_FLAG::IS_APPEND; + isAppend = true; + } + else if (strcmp(accessModifierStr, "a") == 0) + { + accessModifier = FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE | FSC_ACCESS_FLAG::IS_APPEND; isAppend = true; } else @@ -275,7 +292,7 @@ namespace iosu fsc_setFileSeek(fscFile, fsc_getFileSize(fscFile)); FSResHandle fsFileHandle; FSA_RESULT r = sFileHandleTable.AllocateHandle(fsFileHandle, fscFile); - if (r != FSA_RESULT::SUCCESS) + if (r != FSA_RESULT::OK) { cemuLog_log(LogType::Force, "Exceeded maximum number of FSA file handles"); delete fscFile; @@ -283,7 +300,7 @@ namespace iosu } *fileHandle = fsFileHandle; cemuLog_log(LogType::CoreinitFile, "Open file {} (access: {} result: ok handle: 0x{})", path, accessModifierStr, (uint32)*fileHandle); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT __FSAOpenDirectory(FSAClient* client, std::string_view path, sint32* dirHandle) @@ -300,14 +317,14 @@ namespace iosu } FSResHandle fsDirHandle; FSA_RESULT r = sDirHandleTable.AllocateHandle(fsDirHandle, fscFile); - if (r != FSA_RESULT::SUCCESS) + if (r != FSA_RESULT::OK) { delete fscFile; return FSA_RESULT::MAX_DIRS; } *dirHandle = fsDirHandle; cemuLog_log(LogType::CoreinitFile, "Open directory {} (result: ok handle: 0x{})", path, (uint32)*dirHandle); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT __FSACloseFile(uint32 fileHandle) @@ -322,7 +339,7 @@ namespace iosu // unregister file sFileHandleTable.ReleaseHandle(fileHandle); // todo - use the error code of this fsc_close(fscFile); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_remove(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -365,6 +382,7 @@ namespace iosu else if (fsc_isFile(fscFile)) { fsStatOut->size = fsc_getFileSize(fscFile); + statFlag |= FSFlag::IS_FILE; } else { @@ -383,7 +401,7 @@ namespace iosu return FSA_convertFSCtoFSAStatus(fscStatus); __FSA_GetStatFromFSCFile(fscFile, fsStatOut); delete fscFile; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_queryInfo(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -407,7 +425,16 @@ namespace iosu betype* fsStatSize = &shimBuffer->response.cmdQueryInfo.queryFreeSpace.freespace; *fsStatSize = 30ull * 1024 * 1024 * 1024; // placeholder value. How is this determined? delete fscFile; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; + } + else if (queryType == FSA_QUERY_TYPE_DEVICE_INFO) + { + FSADeviceInfo_t* deviceInfo = &shimBuffer->response.cmdQueryInfo.queryDeviceInfo.info; + // always report hardcoded values for now. + deviceInfo->deviceSectorSize = 512; + deviceInfo->deviceSizeInSectors = (32ull * 1024 * 1024 * 1024) / deviceInfo->deviceSectorSize; + cemu_assert_suspicious(); + return FSA_RESULT::OK; } else cemu_assert_unimplemented(); @@ -423,7 +450,7 @@ namespace iosu return FSA_RESULT::NOT_FOUND; cemu_assert_debug(fsc_isFile(fscFile)); __FSA_GetStatFromFSCFile(fscFile, statOut); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_read(FSAClient* client, FSAShimBuffer* shimBuffer, MEMPTR destPtr, uint32be transferSize) @@ -444,7 +471,7 @@ namespace iosu // todo: File permissions uint32 bytesSuccessfullyRead = fsc_readFile(fscFile, destPtr, bytesToRead); if (transferElementSize == 0) - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; LatteBufferCache_notifyDCFlush(destPtr.GetMPTR(), bytesToRead); @@ -485,7 +512,7 @@ namespace iosu if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; fsc_setFileSeek(fscFile, filePos); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_getPos(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -496,7 +523,7 @@ namespace iosu return FSA_RESULT::INVALID_FILE_HANDLE; uint32 filePos = fsc_getFileSeek(fscFile); shimBuffer->response.cmdGetPosFile.filePos = filePos; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_openFile(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -528,7 +555,7 @@ namespace iosu FSDirEntry_t* dirEntryOut = &shimBuffer->response.cmdReadDir.dirEntry; FSCDirEntry fscDirEntry; if (fsc_nextDir(fscFile, &fscDirEntry) == false) - return FSA_RESULT::END_DIR; + return FSA_RESULT::END_OF_DIRECTORY; strcpy(dirEntryOut->name, fscDirEntry.path); FSFlag statFlag = FSFlag::NONE; dirEntryOut->stat.size = 0; @@ -538,11 +565,12 @@ namespace iosu } else if (fscDirEntry.isFile) { + statFlag |= FSFlag::IS_FILE; dirEntryOut->stat.size = fscDirEntry.fileSize; } dirEntryOut->stat.flag = statFlag; dirEntryOut->stat.permissions = 0x777; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_closeDir(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -555,24 +583,42 @@ namespace iosu } sDirHandleTable.ReleaseHandle(shimBuffer->request.cmdReadDir.dirHandle); fsc_close(fscFile); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_flushQuota(FSAClient* client, FSAShimBuffer* shimBuffer) { - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; + } + + FSA_RESULT FSAProcessCmd_rewindDir(FSAClient* client, FSAShimBuffer* shimBuffer) + { + FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)shimBuffer->request.cmdRewindDir.dirHandle); + if (!fscFile) + { + cemuLog_logDebug(LogType::Force, "RewindDir: Invalid handle (0x{:08x})", (sint32)shimBuffer->request.cmdRewindDir.dirHandle); + return FSA_RESULT::INVALID_DIR_HANDLE; + } + if (!fscFile->fscRewindDir()) + return FSA_RESULT::FATAL_ERROR; + + return FSA_RESULT::OK; + } + + FSA_RESULT FSAProcessCmd_flushFile(FSAClient* client, FSAShimBuffer* shimBuffer) + { + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_appendFile(FSAClient* client, FSAShimBuffer* shimBuffer) { - uint32 fileHandle = shimBuffer->request.cmdAppendFile.fileHandle; - FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(shimBuffer->request.cmdAppendFile.fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "FSAProcessCmd_appendFile(): size 0x{:08x} count 0x{:08x} (todo)\n", shimBuffer->request.cmdAppendFile.size, shimBuffer->request.cmdAppendFile.count); #endif - return (FSA_RESULT)(shimBuffer->request.cmdAppendFile.size * shimBuffer->request.cmdAppendFile.count); + return (FSA_RESULT)(shimBuffer->request.cmdAppendFile.count.value()); } FSA_RESULT FSAProcessCmd_truncateFile(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -582,7 +628,7 @@ namespace iosu if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; fsc_setFileLength(fscFile, fsc_getFileSeek(fscFile)); - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_isEof(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -594,8 +640,8 @@ namespace iosu uint32 filePos = fsc_getFileSeek(fscFile); uint32 fileSize = fsc_getFileSize(fscFile); if (filePos >= fileSize) - return FSA_RESULT::END_FILE; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::END_OF_FILE; + return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_getCwd(FSAClient* client, FSAShimBuffer* shimBuffer) @@ -764,17 +810,22 @@ namespace iosu fsaResult = FSAProcessCmd_flushQuota(client, shimBuffer); break; } + case FSA_CMD_OPERATION_TYPE::REWINDDIR: + { + fsaResult = FSAProcessCmd_rewindDir(client, shimBuffer); + break; + } + case FSA_CMD_OPERATION_TYPE::FLUSHFILE: + { + fsaResult = FSAProcessCmd_flushFile(client, shimBuffer); + break; + } case FSA_CMD_OPERATION_TYPE::READ: case FSA_CMD_OPERATION_TYPE::WRITE: { // These commands are IOCTLVs not IOCTL cemu_assert_error(); } - default: - { - cemu_assert_unimplemented(); - break; - } } IOS_ResourceReply(cmd, (IOS_ERROR)fsaResult); } diff --git a/src/Cafe/IOSU/fsa/iosu_fsa.h b/src/Cafe/IOSU/fsa/iosu_fsa.h index 8f8d2323..8181a4a8 100644 --- a/src/Cafe/IOSU/fsa/iosu_fsa.h +++ b/src/Cafe/IOSU/fsa/iosu_fsa.h @@ -111,10 +111,25 @@ namespace iosu { uint32be fileHandle; } cmdIsEof; + struct + { + uint32be dirHandle; + } cmdRewindDir; + struct + { + uint32be fileHandle; + } cmdFlushFile; + struct + { + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + uint32be mode1; + uint32be mode2; + } cmdChangeMode; }; }; static_assert(sizeof(FSARequest) == 0x520); +#pragma pack(1) struct FSAResponse { uint32be ukn0; @@ -158,11 +173,16 @@ namespace iosu { FSStat_t stat; } queryStat; + struct + { + FSADeviceInfo_t info; + } queryDeviceInfo; }; } cmdQueryInfo; }; }; - // static_assert(sizeof(FSAResponse) == 0x293); + static_assert(sizeof(FSAResponse) == 0x293); +#pragma pack() struct FSAShimBuffer { @@ -189,7 +209,7 @@ namespace iosu uint32 ukn0930; uint32 ukn0934; }; - // static_assert(sizeof(FSAShimBuffer) == 0x938); // exact size of this is not known + static_assert(sizeof(FSAShimBuffer) == 0x938); // exact size of this is not known void Initialize(); void Shutdown(); diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index d6e592ed..9d24a095 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -237,7 +237,7 @@ namespace iosu nnResult ServiceCall(uint32 serviceId, void* request, void* response) override { - cemuLog_log(LogType::Force, "Unsupported service call to /dec/act"); + cemuLog_log(LogType::Force, "Unsupported service call to /dev/act"); cemu_assert_unimplemented(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0); } diff --git a/src/Cafe/IOSU/legacy/iosu_mcp.cpp b/src/Cafe/IOSU/legacy/iosu_mcp.cpp index 0b99496a..3108e1f2 100644 --- a/src/Cafe/IOSU/legacy/iosu_mcp.cpp +++ b/src/Cafe/IOSU/legacy/iosu_mcp.cpp @@ -53,8 +53,10 @@ namespace iosu std::string titlePath = CafeSystem::GetMlcStoragePath(titleId); strcpy(titleOut.appPath, titlePath.c_str()); + strcpy((char*)titleOut.deviceName, "mlc"); + titleOut.osVersion = 0; // todo - titleOut.sdkVersion = 0; + titleOut.sdkVersion = it->GetAppSDKVersion(); } numTitlesCopied++; @@ -73,7 +75,8 @@ namespace iosu sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount) { std::unique_lock _lock(sTitleInfoMutex); - *titleCount = mcpBuildTitleList(titleList, *titleCount, [](const TitleInfo& titleInfo) -> bool { return true; }); + uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo); + *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [](const TitleInfo& titleInfo) -> bool { return true; }); return 0; } @@ -86,7 +89,7 @@ namespace iosu sint32 mcpGetTitleListByAppType(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint32 appType) { std::unique_lock _lock(sTitleInfoMutex); - uint32 maxEntryCount = (uint32)*titleCount; + uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo); *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [appType](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppType() == appType; }); return 0; } @@ -94,7 +97,7 @@ namespace iosu sint32 mcpGetTitleListByTitleId(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint64 titleId) { std::unique_lock _lock(sTitleInfoMutex); - uint32 maxEntryCount = (uint32)*titleCount; + uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo); *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [titleId](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppTitleId() == titleId; }); return 0; } @@ -143,11 +146,11 @@ namespace iosu return 0; } + // deprecated void iosuMcp_init() { if (iosuMcp.isInitialized) return; - // start the act thread std::thread t(iosuMcp_thread); t.detach(); iosuMcp.isInitialized = true; diff --git a/src/Cafe/IOSU/legacy/iosu_mcp.h b/src/Cafe/IOSU/legacy/iosu_mcp.h index bca301a4..64e75fdc 100644 --- a/src/Cafe/IOSU/legacy/iosu_mcp.h +++ b/src/Cafe/IOSU/legacy/iosu_mcp.h @@ -13,7 +13,8 @@ struct MCPTitleInfo // everything below is uncertain /* +0x4A */ uint64be osVersion; // app.xml /* +0x52 */ uint32be sdkVersion; // app.xml - /* +0x56 */ uint8 ukn[0x61 - 0x56]; + /* +0x56 */ uint8 deviceName[10]; + /* +0x60 */ uint8 uknPadding; // possibly the index of the device? //move this and the stuff below }; diff --git a/src/Cafe/IOSU/legacy/iosu_nim.cpp b/src/Cafe/IOSU/legacy/iosu_nim.cpp index f90ec70b..e7cf97ef 100644 --- a/src/Cafe/IOSU/legacy/iosu_nim.cpp +++ b/src/Cafe/IOSU/legacy/iosu_nim.cpp @@ -8,11 +8,9 @@ #include "openssl/x509.h" #include "openssl/ssl.h" #include "util/helpers/helpers.h" - -#include - #include "Cemu/napi/napi.h" #include "Cemu/ncrypto/ncrypto.h" +#include "Cafe/CafeSystem.h" namespace iosu { @@ -47,6 +45,13 @@ namespace iosu bool backgroundThreadStarted; } g_nim = {}; + bool nim_CheckDownloadsDisabled() + { + // currently for the Wii U menu we disable NIM to speed up boot times + uint64 tid = CafeSystem::GetForegroundTitleId(); + return tid == 0x0005001010040000 || tid == 0x0005001010040100 || tid == 0x0005001010040200; + } + bool nim_getLatestVersion() { g_nim.latestVersion = -1; @@ -101,6 +106,13 @@ namespace iosu void nim_buildDownloadList() { + if(nim_CheckDownloadsDisabled()) + { + cemuLog_logDebug(LogType::Force, "nim_buildDownloadList: Downloads are disabled for this title"); + g_nim.packages.clear(); + return; + } + sint32 titleCount = mcpGetTitleCount(); MCPTitleInfo* titleList = (MCPTitleInfo*)malloc(titleCount * sizeof(MCPTitleInfo)); memset(titleList, 0, titleCount * sizeof(MCPTitleInfo)); @@ -141,6 +153,8 @@ namespace iosu void nim_getPackagesInfo(uint64* titleIdList, sint32 count, titlePackageInfo_t* packageInfoList) { memset(packageInfoList, 0, sizeof(titlePackageInfo_t)*count); + if(nim_CheckDownloadsDisabled()) + return; for (sint32 i = 0; i < count; i++) { uint64 titleId = _swapEndianU64(titleIdList[i]); diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp index b3c0be06..d32acb2a 100644 --- a/src/Cafe/OS/RPL/rpl.cpp +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -1428,6 +1428,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() @@ -1435,8 +1436,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 @@ -1447,8 +1447,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 f4797c97..5d3046e3 100644 --- a/src/Cafe/OS/RPL/rpl_symbol_storage.cpp +++ b/src/Cafe/OS/RPL/rpl_symbol_storage.cpp @@ -83,6 +83,20 @@ RPLStoredSymbol* rplSymbolStorage_getByAddress(MPTR address) return rplSymbolStorage.map_symbolByAddress[address]; } +RPLStoredSymbol* rplSymbolStorage_getByClosestAddress(MPTR address) +{ + // highly inefficient but doesn't matter for now + std::unique_lock lck(rplSymbolStorage.m_symbolStorageMutex); + for(uint32 i=0; i<4096; i++) + { + RPLStoredSymbol* symbol = rplSymbolStorage.map_symbolByAddress[address]; + if(symbol) + return symbol; + address -= 4; + } + return nullptr; +} + void rplSymbolStorage_remove(RPLStoredSymbol* storedSymbol) { std::unique_lock lck(rplSymbolStorage.m_symbolStorageMutex); @@ -145,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/RPL/rpl_symbol_storage.h b/src/Cafe/OS/RPL/rpl_symbol_storage.h index 0f179f59..d940186a 100644 --- a/src/Cafe/OS/RPL/rpl_symbol_storage.h +++ b/src/Cafe/OS/RPL/rpl_symbol_storage.h @@ -12,6 +12,7 @@ RPLStoredSymbol* rplSymbolStorage_store(const char* libName, const char* symbolN void rplSymbolStorage_remove(RPLStoredSymbol* storedSymbol); void rplSymbolStorage_removeRange(MPTR address, sint32 length); RPLStoredSymbol* rplSymbolStorage_getByAddress(MPTR address); +RPLStoredSymbol* rplSymbolStorage_getByClosestAddress(MPTR address); void rplSymbolStorage_createJumpProxySymbol(MPTR jumpAddress, MPTR destAddress); std::unordered_map& rplSymbolStorage_lockSymbolMap(); 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 f0c0f952..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) -{ - debug_printf("OSPanic!\n"); - debug_printf("File: %s:%d\n", memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4]); - debug_printf("Msg: %s\n", 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; @@ -271,7 +258,8 @@ namespace coreinit void coreinit_exit(uint32 r) { - cemuLog_log(LogType::Force, "coreinit.exit({})", r); + cemuLog_log(LogType::Force, "The title terminated the process by calling coreinit.exit({})", (sint32)r); + DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); cemu_assert_debug(false); // never return while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -295,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); @@ -312,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 50fc66df..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 { @@ -211,6 +217,7 @@ namespace coreinit void __OSInitiateAlarm(OSAlarm_t* alarm, uint64 startTime, uint64 period, MPTR handlerFunc, bool isPeriodic) { + cemu_assert_debug(MMU_IsInPPCMemorySpace(alarm)); cemu_assert_debug(__OSHasSchedulerLock()); uint64 nextTime = startTime; @@ -281,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_Atomic.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp index 6ac781c0..accce552 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp @@ -33,10 +33,11 @@ namespace coreinit uint32 OSAddAtomic(std::atomic* mem, uint32 adder) { + // used by SDL Wii U port uint32be knownValue; while (true) { - uint32be knownValue = mem->load(); + knownValue = mem->load(); uint32be newValue = knownValue + adder; if (mem->compare_exchange_strong(knownValue, newValue)) break; @@ -68,7 +69,7 @@ namespace coreinit uint64be knownValue; while (true) { - uint64be knownValue = mem->load(); + knownValue = mem->load(); uint64be newValue = knownValue + adder; if (mem->compare_exchange_strong(knownValue, newValue)) break; @@ -81,7 +82,7 @@ namespace coreinit uint64be knownValue; while (true) { - uint64be knownValue = mem->load(); + knownValue = mem->load(); uint64be newValue = knownValue & val; if (mem->compare_exchange_strong(knownValue, newValue)) break; @@ -94,7 +95,7 @@ namespace coreinit uint64be knownValue; while (true) { - uint64be knownValue = mem->load(); + knownValue = mem->load(); uint64be newValue = knownValue | val; if (mem->compare_exchange_strong(knownValue, newValue)) break; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 6a864cf2..26636eae 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -10,6 +10,7 @@ #include "Cafe/IOSU/iosu_ipc_common.h" #include "coreinit_IPC.h" #include "Cafe/Filesystem/fsc.h" +#include "coreinit_IPCBuf.h" #define FS_CB_PLACEHOLDER_FINISHCMD (MPTR)(0xF122330E) @@ -73,22 +74,16 @@ namespace coreinit FS_RESULT FSGetMountSourceNext(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, MOUNT_TYPE mountSourceType, FS_MOUNT_SOURCE* mountSourceInfo, FS_ERROR_MASK errMask) { - // hacky - static FS_MOUNT_SOURCE* s_last_source = nullptr; - if (s_last_source != mountSourceInfo) + if (mountSourceType == MOUNT_TYPE::SD) { - s_last_source = mountSourceInfo; - fsCmdBlock->data.mount_it = 0; + // This function is supposed to be called after an initial FSGetMountSource call => always returns FS_RESULT::END_ITERATION because we only have one SD Card + // It *might* causes issues if this function is called for getting the first MountSource (instead of "FSGetMountSource") + cemu_assert_suspicious(); + return FS_RESULT::END_ITERATION; } - - fsCmdBlock->data.mount_it++; - - // SD - if (mountSourceType == MOUNT_TYPE::SD && fsCmdBlock->data.mount_it == 1) + else { - mountSourceInfo->sourceType = 0; - strcpy(mountSourceInfo->path, "/sd"); - return FS_RESULT::SUCCESS; + cemu_assert_unimplemented(); } return FS_RESULT::END_ITERATION; @@ -96,7 +91,20 @@ namespace coreinit FS_RESULT FSGetMountSource(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, MOUNT_TYPE mountSourceType, FS_MOUNT_SOURCE* mountSourceInfo, FS_ERROR_MASK errMask) { - return FSGetMountSourceNext(fsClient, fsCmdBlock, mountSourceType, mountSourceInfo, errMask); + // This implementation is simplified A LOT compared to what the Wii U is actually doing. On Cemu we expect to only have one mountable source (SD Card) anyway, + // so we can just hard code it. Other mount types are not (yet) supported. + if (mountSourceType == MOUNT_TYPE::SD) + { + mountSourceInfo->sourceType = 0; + strcpy(mountSourceInfo->path, "/sd"); + return FS_RESULT::SUCCESS; + } + else + { + cemu_assert_unimplemented(); + } + + return FS_RESULT::END_ITERATION; } bool _sdCard01Mounted = false; @@ -457,6 +465,26 @@ namespace coreinit } } + FSA_RESULT __FSADecodeIOSErrorToFSA(IOS_ERROR result) + { + return (FSA_RESULT)result; + } + + FSA_RESULT __FSAIPCSubmitCommand(iosu::fsa::FSAShimBuffer* shimBuffer) + { + if (shimBuffer->ipcReqType == 0) + { + IOS_ERROR result = IOS_Ioctl(shimBuffer->fsaDevHandle, shimBuffer->operationType, &shimBuffer->request, sizeof(shimBuffer->request), &shimBuffer->response, sizeof(shimBuffer->response)); + return __FSADecodeIOSErrorToFSA(result); + } + else if (shimBuffer->ipcReqType == 1) + { + IOS_ERROR result = IOS_Ioctlv(shimBuffer->fsaDevHandle, shimBuffer->operationType, shimBuffer->ioctlvVecIn, shimBuffer->ioctlvVecOut, shimBuffer->ioctlvVec); + return __FSADecodeIOSErrorToFSA(result); + } + return FSA_RESULT::FATAL_ERROR; + } + void __FSUpdateQueue(FSCmdQueue* cmdQueue) { FSLockMutex(); @@ -586,12 +614,12 @@ namespace coreinit } switch (err) { - case FSA_RESULT::SUCCESS: + case FSA_RESULT::OK: { return FS_RESULT::SUCCESS; } - case FSA_RESULT::END_DIR: - case FSA_RESULT::END_FILE: + case FSA_RESULT::END_OF_DIRECTORY: + case FSA_RESULT::END_OF_FILE: { return FS_RESULT::END_ITERATION; } @@ -627,6 +655,9 @@ namespace coreinit case FSA_RESULT::INVALID_PATH: case FSA_RESULT::INVALID_BUFFER: case FSA_RESULT::INVALID_ALIGNMENT: + case FSA_RESULT::NOT_INIT: + case FSA_RESULT::MAX_CLIENTS: + case FSA_RESULT::OUT_OF_RESOURCES: case FSA_RESULT::FATAL_ERROR: { return FS_RESULT::FATAL_ERROR; @@ -863,7 +894,7 @@ namespace coreinit fsaShimBuffer->response.cmdOpenFile.fileHandleOutput = 0xFFFFFFFF; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* outFileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -901,7 +932,7 @@ namespace coreinit fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = &outFileHandle->fileHandle; FSA_RESULT prepareResult = __FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, createMode, openFlag, preallocSize); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -919,14 +950,15 @@ namespace coreinit FSA_RESULT __FSPrepareCmd_CloseFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle) { if (fsaShimBuffer == nullptr) - { return FSA_RESULT::INVALID_BUFFER; - } + fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::CLOSEFILE; + fsaShimBuffer->request.cmdCloseFile.fileHandle = fileHandle; - return FSA_RESULT::SUCCESS; + + return FSA_RESULT::OK; } sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -934,7 +966,7 @@ namespace coreinit _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_CloseFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -949,6 +981,40 @@ namespace coreinit return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } + FSA_RESULT __FSPrepareCmd_FlushFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle) + { + if (fsaShimBuffer == nullptr) + return FSA_RESULT::INVALID_BUFFER; + + fsaShimBuffer->fsaDevHandle = fsaHandle; + fsaShimBuffer->ipcReqType = 0; + fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::FLUSHFILE; + + fsaShimBuffer->request.cmdFlushFile.fileHandle = fileHandle; + + return FSA_RESULT::OK; + } + + sint32 FSFlushFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + + FSA_RESULT prepareResult = __FSPrepareCmd_FlushFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); + if (prepareResult != FSA_RESULT::OK) + return (FSStatus)_FSAStatusToFSStatus(prepareResult); + + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSFlushFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) + { + StackAllocator asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSFlushFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); + } + FSA_RESULT __FSPrepareCmd_ReadFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, void* dest, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag) { if (fsaShimBuffer == NULL || dest == NULL) @@ -980,7 +1046,7 @@ namespace coreinit fsaShimBuffer->request.cmdReadFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdReadFile.flag = flag; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } SysAllocator _tempFSSpace; @@ -1009,7 +1075,7 @@ namespace coreinit flag &= ~FSA_CMD_FLAG_SET_POS; FSA_RESULT prepareResult = __FSPrepareCmd_ReadFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dest, size, count, filePos, fileHandle, flag); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1076,7 +1142,7 @@ namespace coreinit fsaShimBuffer->request.cmdWriteFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdWriteFile.flag = flag; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 __FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool useFilePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1103,7 +1169,7 @@ namespace coreinit flag &= ~FSA_CMD_FLAG_SET_POS; FSA_RESULT prepareResult = __FSPrepareCmd_WriteFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dest, size, count, filePos, fileHandle, flag); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1146,14 +1212,14 @@ namespace coreinit fsaShimBuffer->request.cmdSetPosFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdSetPosFile.filePos = filePos; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::SETPOS; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_SetPosFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle, filePos); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; @@ -1176,7 +1242,7 @@ namespace coreinit fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->request.cmdGetPosFile.fileHandle = fileHandle; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::GETPOS; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1185,7 +1251,7 @@ namespace coreinit _FSCmdIntro(); fsCmdBlockBody->returnValues.cmdGetPosFile.filePosPtr = returnedFilePos; FSA_RESULT prepareResult = __FSPrepareCmd_GetPosFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; @@ -1224,7 +1290,7 @@ namespace coreinit fsaShimBuffer->response.cmdOpenDir.dirHandleOutput = -1; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1233,7 +1299,7 @@ namespace coreinit cemu_assert(dirHandleOut && path); fsCmdBlockBody->returnValues.cmdOpenDir.handlePtr = dirHandleOut.GetMPTR(); FSA_RESULT prepareResult = __FSPrepareCmd_OpenDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; @@ -1255,14 +1321,14 @@ namespace coreinit fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::READDIR; fsaShimBuffer->request.cmdReadDir.dirHandle = dirHandle; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_ReadDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); fsCmdBlockBody->returnValues.cmdReadDir.dirEntryPtr = dirEntryOut; __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1285,14 +1351,14 @@ namespace coreinit fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::CLOSEDIR; fsaShimBuffer->request.cmdCloseDir.dirHandle = dirHandle; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_CloseDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1307,7 +1373,40 @@ namespace coreinit return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - FSA_RESULT __FSPrepareCmd_AppendFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle, uint32 size, uint32 count, uint32 uknParam) + FSA_RESULT __FSPrepareCmd_RewindDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, FSDirHandle2 dirHandle) + { + if (fsaShimBuffer == nullptr) + return FSA_RESULT::INVALID_BUFFER; + + fsaShimBuffer->fsaDevHandle = fsaHandle; + fsaShimBuffer->ipcReqType = 0; + fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::REWINDDIR; + + fsaShimBuffer->request.cmdRewindDir.dirHandle = dirHandle; + + return FSA_RESULT::OK; + } + + sint32 FSRewindDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + FSA_RESULT prepareResult = __FSPrepareCmd_RewindDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); + if (prepareResult != FSA_RESULT::OK) + return (FSStatus)_FSAStatusToFSStatus(prepareResult); + + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSRewindDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) + { + StackAllocator asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSRewindDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + FSA_RESULT __FSPrepareCmd_AppendFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 size, uint32 count, uint32 fileHandle, uint32 uknParam) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; @@ -1316,29 +1415,29 @@ namespace coreinit fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::APPENDFILE; fsaShimBuffer->request.cmdAppendFile.fileHandle = fileHandle; - fsaShimBuffer->request.cmdAppendFile.count = size; - fsaShimBuffer->request.cmdAppendFile.size = count; + fsaShimBuffer->request.cmdAppendFile.count = count; + fsaShimBuffer->request.cmdAppendFile.size = size; fsaShimBuffer->request.cmdAppendFile.uknParam = uknParam; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } - sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { _FSCmdIntro(); - FSA_RESULT prepareResult = __FSPrepareCmd_AppendFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle, size, count, 0); - if (prepareResult != FSA_RESULT::SUCCESS) + FSA_RESULT prepareResult = __FSPrepareCmd_AppendFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, size, count, fileHandle, 0); + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask) + sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask) { StackAllocator asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, fileHandle, size, count, errorMask, asyncParams.GetPointer()); + sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, size, count, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1352,14 +1451,14 @@ namespace coreinit fsaShimBuffer->request.cmdTruncateFile.fileHandle = fileHandle; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_TruncateFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1410,7 +1509,7 @@ namespace coreinit } fsaShimBuffer->request.cmdRename.dstPath[stringLen] = '\0'; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1423,7 +1522,7 @@ namespace coreinit return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_Rename(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, srcPath, dstPath); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1461,7 +1560,7 @@ namespace coreinit } fsaShimBuffer->request.cmdRemove.path[pathLen] = '\0'; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1474,7 +1573,7 @@ namespace coreinit return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_Remove(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, filePath); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1489,7 +1588,7 @@ namespace coreinit return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - FSA_RESULT __FSPrepareCmd_MakeDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, const uint8* path, uint32 uknVal660) + FSA_RESULT __FSPrepareCmd_MakeDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, const char* path, uint32 uknVal660) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; @@ -1513,10 +1612,10 @@ namespace coreinit fsaShimBuffer->request.cmdMakeDir.path[pathLen] = '\0'; fsaShimBuffer->request.cmdMakeDir.uknParam = uknVal660; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } - sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { // used by titles: XCX (via SAVEMakeDirAsync) _FSCmdIntro(); @@ -1526,14 +1625,14 @@ namespace coreinit return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_MakeDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirPath, 0x660); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* path, uint32 errorMask) + sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask) { StackAllocator asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); @@ -1563,7 +1662,7 @@ namespace coreinit fsaShimBuffer->request.cmdChangeDir.path[pathLen] = '\0'; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1575,7 +1674,7 @@ namespace coreinit return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_ChangeDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, (uint8*)path); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1599,7 +1698,7 @@ namespace coreinit fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::GETCWD; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1610,7 +1709,7 @@ namespace coreinit fsCmdBlockBody->returnValues.cmdGetCwd.transferSize = dirPathMaxLen; FSA_RESULT prepareResult = __FSPrepareCmd_GetCwd(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1647,7 +1746,7 @@ namespace coreinit fsaShimBuffer->request.cmdFlushQuota.path[i] = path[i]; fsaShimBuffer->request.cmdFlushQuota.path[pathLen] = '\0'; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1655,7 +1754,7 @@ namespace coreinit _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_FlushQuota(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1697,7 +1796,7 @@ namespace coreinit fsaShimBuffer->request.cmdQueryInfo.query[stringLen] = '\0'; fsaShimBuffer->request.cmdQueryInfo.queryType = queryType; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1707,7 +1806,7 @@ namespace coreinit fsCmdBlockBody->returnValues.cmdQueryInfo.queryResultPtr = queryResult; FSA_RESULT prepareResult = __FSPrepareCmd_QueryInfo(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, queryString, queryType); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1729,16 +1828,18 @@ namespace coreinit return ret; } - FSA_RESULT __FSPrepareCmd_GetStatFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, FSFileHandle2 fileHandle, FSStat_t* statOut) + FSA_RESULT __FSPrepareCmd_GetStatFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, FSFileHandle2 fileHandle) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; - fsaShimBuffer->request.cmdGetStatFile.fileHandle = fileHandle; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::GETSTATFILE; - return FSA_RESULT::SUCCESS; + + fsaShimBuffer->request.cmdGetStatFile.fileHandle = fileHandle; + + return FSA_RESULT::OK; } sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1747,8 +1848,8 @@ namespace coreinit cemu_assert(statOut); // statOut must not be null fsCmdBlockBody->returnValues.cmdStatFile.resultPtr = statOut; - FSA_RESULT prepareResult = __FSPrepareCmd_GetStatFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle, statOut); - if (prepareResult != FSA_RESULT::SUCCESS) + FSA_RESULT prepareResult = __FSPrepareCmd_GetStatFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1789,7 +1890,7 @@ namespace coreinit fsaShimBuffer->request.cmdIsEof.fileHandle = fileHandle; - return FSA_RESULT::SUCCESS; + return FSA_RESULT::OK; } sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) @@ -1798,7 +1899,7 @@ namespace coreinit _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_IsEof(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); - if (prepareResult != FSA_RESULT::SUCCESS) + if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); @@ -1862,6 +1963,681 @@ namespace coreinit return 0; // no error } + std::vector s_fsa_activeClients; + std::mutex s_fsa_activeClientsMutex; + + FSAClientHandle FSAAddClientEx(void* data) + { + if (data != NULL) + { + // TODO + cemu_assert_unimplemented(); + } + + IOSDevHandle handle = IOS_Open("/dev/fsa", 0); + if (handle < IOS_ERROR::IOS_ERROR_OK) + { + return (FSAClientHandle)FSA_RESULT::PERMISSION_ERROR; + } + + s_fsa_activeClientsMutex.lock(); + s_fsa_activeClients.push_back((FSAClientHandle)handle); + s_fsa_activeClientsMutex.unlock(); + + return (FSAClientHandle)handle; + } + + FSAClientHandle FSAAddClient(void* data) + { + return FSAAddClientEx(data); + } + + FSA_RESULT FSADelClient(FSAClientHandle clientHandle) + { + if (clientHandle == 0) + { + return FSA_RESULT::INVALID_CLIENT_HANDLE; + } + s_fsa_activeClientsMutex.lock(); + + auto it = std::find(s_fsa_activeClients.begin(), s_fsa_activeClients.end(), clientHandle); + if (it != s_fsa_activeClients.end()) + { + IOS_Close(clientHandle); + s_fsa_activeClients.erase(it); + } + + s_fsa_activeClientsMutex.unlock(); + + return FSA_RESULT::OK; + } + + SysAllocator s_fsaIpcPool; + SysAllocator s_fsaIpcPoolBuffer; + SysAllocator s_fsaIpcPoolBufferNumItems; + + std::mutex sFSAIPCBufferLock; + bool s_fsaInitDone = false; + + void FSAInit() + { + if (!s_fsaInitDone) + { + s_fsaIpcPool = IPCBufPoolCreate(s_fsaIpcPoolBuffer.GetPtr(), s_fsaIpcPoolBuffer.GetByteSize(), sizeof(iosu::fsa::FSAShimBuffer), &s_fsaIpcPoolBufferNumItems, 0); + s_fsaInitDone = true; + } + } + + bool FSAShimCheckClientHandle(FSAClientHandle clientHandle) + { + std::scoped_lock lock(s_fsa_activeClientsMutex); + if (std::find(s_fsa_activeClients.begin(), s_fsa_activeClients.end(), clientHandle) != s_fsa_activeClients.end()) + { + return true; + } + return false; + } + + FSA_RESULT FSAShimAllocateBuffer(MEMPTR> outBuffer) + { + if (!s_fsaInitDone) + return FSA_RESULT::NOT_INIT; + + sFSAIPCBufferLock.lock(); + auto ptr = IPCBufPoolAllocate(s_fsaIpcPool, sizeof(iosu::fsa::FSAShimBuffer)); + sFSAIPCBufferLock.unlock(); + + if (!ptr) + return FSA_RESULT::OUT_OF_RESOURCES; + + std::memset(ptr, 0, sizeof(iosu::fsa::FSAShimBuffer)); + outBuffer[0] = reinterpret_cast(ptr); + return FSA_RESULT::OK; + } + + FSA_RESULT FSAShimFreeBuffer(iosu::fsa::FSAShimBuffer* buffer) + { + sFSAIPCBufferLock.lock(); + IPCBufPoolFree(s_fsaIpcPool, (uint8_t*)buffer); + sFSAIPCBufferLock.unlock(); + return FSA_RESULT::OK; + } + + FSA_RESULT FSACloseFile(FSAClientHandle client, uint32 fileHandle) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_CloseFile(shimBuffer->GetPtr(), client, fileHandle); + if (result == FSA_RESULT::OK) + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAFlushFile(FSAClientHandle client, uint32_t fileHandle) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_FlushFile(shimBuffer->GetPtr(), client, fileHandle); + if (result == FSA_RESULT::OK) + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAMakeDir(FSAClientHandle client, const char* path, uint32 uknVal660) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_MakeDir(shimBuffer->GetPtr(), client, path, uknVal660); + if (result == FSA_RESULT::OK) + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSARename(FSAClientHandle client, char* oldPath, char* newPath) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_Rename(shimBuffer->GetPtr(), client, oldPath, newPath); + if (result == FSA_RESULT::OK) + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAChangeDir(FSAClientHandle client, char* path) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_ChangeDir(shimBuffer->GetPtr(), client, (uint8_t*)path); + if (result == FSA_RESULT::OK) + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAReadDir(FSAClientHandle client, FSDirHandle2 dirHandle, MEMPTR directoryEntry) + { + if (directoryEntry.IsNull()) + return FSA_RESULT::INVALID_BUFFER; + + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_ReadDir(shimBuffer->GetPtr(), client, dirHandle); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + if (result == FSA_RESULT::OK) + { + *directoryEntry = shimBuffer->GetPtr()->response.cmdReadDir.dirEntry; + } + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAOpenDir(FSAClientHandle client, char* path, MEMPTR dirHandle) + { + if (dirHandle.IsNull()) + return FSA_RESULT::INVALID_BUFFER; + + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_OpenDir(shimBuffer->GetPtr(), client, path); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + if (result == FSA_RESULT::OK) + { + *dirHandle = shimBuffer->GetPtr()->response.cmdOpenDir.dirHandleOutput; + } + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSACloseDir(FSAClientHandle client, FSDirHandle2 dirHandle) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_CloseDir(shimBuffer->GetPtr(), client, dirHandle); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSARewindDir(FSAClientHandle client, FSDirHandle2 dirHandle) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_RewindDir(shimBuffer->GetPtr(), client, dirHandle); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAOpenFileEx(FSAClientHandle client, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32_t preallocSize, MEMPTR outFileHandle) + { + if (outFileHandle.IsNull()) + return FSA_RESULT::INVALID_BUFFER; + + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_OpenFile(shimBuffer->GetPtr(), client, path, mode, createMode, openFlag, preallocSize); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + if (result == FSA_RESULT::OK) + { + *outFileHandle = shimBuffer->GetPtr()->response.cmdOpenFile.fileHandleOutput; + } + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAGetStatFile(FSAClientHandle client, FSFileHandle2 fileHandle, MEMPTR outStat) + { + if (outStat.IsNull()) + return FSA_RESULT::INVALID_BUFFER; + + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_GetStatFile(shimBuffer->GetPtr(), client, fileHandle); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + if (result == FSA_RESULT::OK) + { + *outStat = shimBuffer->GetPtr()->response.cmdStatFile.statOut; + } + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSASetPosFile(FSAClientHandle client, FSFileHandle2 fileHandle, uint32_t pos) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_SetPosFile(shimBuffer->GetPtr(), client, fileHandle, pos); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSATruncateFile(FSAClientHandle client, FSFileHandle2 fileHandle) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_TruncateFile(shimBuffer->GetPtr(), client, fileHandle); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSARemove(FSAClientHandle client, char* path) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_Remove(shimBuffer->GetPtr(), client, (uint8_t*)path); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT __FSPrepareCmd_ChangeMode(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint8_t* path, uint32 permission, uint32 permissionMask) + { + if (fsaShimBuffer == nullptr) + return FSA_RESULT::INVALID_BUFFER; + if (path == nullptr) + return FSA_RESULT::INVALID_PATH; + + fsaShimBuffer->fsaDevHandle = fsaHandle; + fsaShimBuffer->ipcReqType = 0; + fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::REWINDDIR; + + size_t pathLen = strlen((char*)path); + + for (sint32 i = 0; i < pathLen; i++) + fsaShimBuffer->request.cmdChangeMode.path[i] = path[i]; + for (size_t i = pathLen; i < FSA_CMD_PATH_MAX_LENGTH; i++) + fsaShimBuffer->request.cmdChangeMode.path[i] = '\0'; + + fsaShimBuffer->request.cmdChangeMode.mode1 = permission; + fsaShimBuffer->request.cmdChangeMode.mode2 = permissionMask; + + return FSA_RESULT::OK; + } + + FSA_RESULT FSAChangeMode(FSAClientHandle client, const char* path, uint32 permission) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_ChangeMode(shimBuffer->GetPtr(), client, (uint8_t*)path, permission, 0x666); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAReadFile(FSAClientHandle client, void* buffer, uint32_t size, uint32_t count, FSFileHandle2 handle, uint32_t flags) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_ReadFile(shimBuffer->GetPtr(), client, buffer, size, count, 0, handle, flags & ~0x2); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAWriteFile(FSAClientHandle client, void* buffer, uint32_t size, uint32_t count, FSFileHandle2 handle, uint32_t flags) + { + if (!FSAShimCheckClientHandle(client)) + return FSA_RESULT::INVALID_CLIENT_HANDLE; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_WriteFile(shimBuffer->GetPtr(), client, buffer, size, count, 0, handle, flags & ~0x2); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAGetInfoByQuery(FSAClientHandle client, char* path, uint32_t queryType, MEMPTR outData) + { + if (outData.IsNull()) + return FSA_RESULT::INVALID_BUFFER; + + StackAllocator, 1> shimBuffer; + FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); + if (result != FSA_RESULT::OK) + return result; + + result = __FSPrepareCmd_QueryInfo(shimBuffer->GetPtr(), client, (uint8_t*)path, queryType); + if (result == FSA_RESULT::OK) + { + result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); + if (result == FSA_RESULT::OK) + { + if (queryType == FSA_QUERY_TYPE_FREESPACE) + { + *MEMPTR(outData.GetMPTR()) = shimBuffer->GetPtr()->response.cmdQueryInfo.queryFreeSpace.freespace; + } + else if (queryType == FSA_QUERY_TYPE_DEVICE_INFO) + { + *MEMPTR(outData.GetMPTR()) = shimBuffer->GetPtr()->response.cmdQueryInfo.queryDeviceInfo.info; + } + else if (queryType == FSA_QUERY_TYPE_STAT) + { + *MEMPTR(outData.GetMPTR()) = shimBuffer->GetPtr()->response.cmdQueryInfo.queryStat.stat; + } + else + { + // TODO: implement other query types + cemu_assert_unimplemented(); + result = FSA_RESULT::FATAL_ERROR; + } + } + } + + FSAShimFreeBuffer(shimBuffer->GetPtr()); + return result; + } + + FSA_RESULT FSAGetStat(FSAClientHandle client, char* path, FSStat_t* outStat) + { + return FSAGetInfoByQuery(client, path, FSA_QUERY_TYPE_STAT, outStat); + } + + FSA_RESULT FSAGetFreeSpaceSize(FSAClientHandle client, char* path, uint64* outSize) + { + return FSAGetInfoByQuery(client, path, FSA_QUERY_TYPE_DEVICE_INFO, outSize); + } + + FSA_RESULT FSAGetDeviceInfo(FSAClientHandle client, char* path, void* outSize) + { + return FSAGetInfoByQuery(client, path, FSA_QUERY_TYPE_FREESPACE, outSize); + } + + SysAllocator s_fsaStr_OK("FSA_STATUS_OK"); + SysAllocator s_fsaStr_NOT_INIT("FSA_STATUS_NOT_INIT"); + SysAllocator s_fsaStr_END_OF_DIRECTORY("FSA_STATUS_END_OF_DIRECTORY"); + SysAllocator s_fsaStr_END_OF_FILE("FSA_STATUS_END_OF_FILE"); + SysAllocator s_fsaStr_MAX_CLIENTS("FSA_STATUS_MAX_CLIENTS"); + SysAllocator s_fsaStr_MAX_FILES("FSA_STATUS_MAX_FILES"); + SysAllocator s_fsaStr_MAX_DIRS("FSA_STATUS_MAX_DIRS"); + SysAllocator s_fsaStr_ALREADY_EXISTS("FSA_STATUS_ALREADY_EXISTS"); + SysAllocator s_fsaStr_NOT_FOUND("FSA_STATUS_NOT_FOUND"); + SysAllocator s_fsaStr_PERMISSION_ERROR("FSA_STATUS_PERMISSION_ERROR"); + SysAllocator s_fsaStr_INVALID_PARAM("FSA_STATUS_INVALID_PARAM"); + SysAllocator s_fsaStr_INVALID_PATH("FSA_STATUS_INVALID_PATH"); + SysAllocator s_fsaStr_INVALID_BUFFER("FSA_STATUS_INVALID_BUFFER"); + SysAllocator s_fsaStr_INVALID_ALIGNMENT("FSA_STATUS_INVALID_ALIGNMENT"); + SysAllocator s_fsaStr_INVALID_CLIENT_HANDLE("FSA_STATUS_INVALID_CLIENT_HANDLE"); + SysAllocator s_fsaStr_INVALID_FILE_HANDLE("FSA_STATUS_INVALID_FILE_HANDLE"); + SysAllocator s_fsaStr_INVALID_DIR_HANDLE("FSA_STATUS_INVALID_DIR_HANDLE"); + SysAllocator s_fsaStr_NOT_FILE("FSA_STATUS_NOT_FILE"); + SysAllocator s_fsaStr_NOT_DIR("FSA_STATUS_NOT_DIR"); + SysAllocator s_fsaStr_OUT_OF_RESOURCES("FSA_STATUS_OUT_OF_RESOURCES"); + SysAllocator s_fsaStr_UNKNOWN("FSA_STATUS_???"); + + const char* FSAGetStatusStr(FSA_RESULT status) + { + switch (status) + { + case FSA_RESULT::OK: + { + return s_fsaStr_OK.GetPtr(); + } + case FSA_RESULT::NOT_INIT: + { + return s_fsaStr_NOT_INIT.GetPtr(); + } + case FSA_RESULT::END_OF_DIRECTORY: + { + return s_fsaStr_END_OF_DIRECTORY.GetPtr(); + } + case FSA_RESULT::END_OF_FILE: + { + return s_fsaStr_END_OF_FILE.GetPtr(); + } + case FSA_RESULT::MAX_CLIENTS: + { + return s_fsaStr_MAX_CLIENTS.GetPtr(); + } + case FSA_RESULT::MAX_FILES: + { + return s_fsaStr_MAX_FILES.GetPtr(); + } + case FSA_RESULT::MAX_DIRS: + { + return s_fsaStr_MAX_DIRS.GetPtr(); + } + case FSA_RESULT::ALREADY_EXISTS: + { + return s_fsaStr_ALREADY_EXISTS.GetPtr(); + } + case FSA_RESULT::NOT_FOUND: + { + return s_fsaStr_NOT_FOUND.GetPtr(); + } + case FSA_RESULT::PERMISSION_ERROR: + { + return s_fsaStr_PERMISSION_ERROR.GetPtr(); + } + case FSA_RESULT::INVALID_PARAM: + { + return s_fsaStr_INVALID_PARAM.GetPtr(); + } + case FSA_RESULT::INVALID_PATH: + { + return s_fsaStr_INVALID_PATH.GetPtr(); + } + case FSA_RESULT::INVALID_BUFFER: + { + return s_fsaStr_INVALID_BUFFER.GetPtr(); + } + case FSA_RESULT::INVALID_ALIGNMENT: + { + return s_fsaStr_INVALID_ALIGNMENT.GetPtr(); + } + case FSA_RESULT::INVALID_CLIENT_HANDLE: + { + return s_fsaStr_INVALID_CLIENT_HANDLE.GetPtr(); + } + case FSA_RESULT::INVALID_FILE_HANDLE: + { + return s_fsaStr_INVALID_FILE_HANDLE.GetPtr(); + } + case FSA_RESULT::INVALID_DIR_HANDLE: + { + return s_fsaStr_INVALID_DIR_HANDLE.GetPtr(); + } + case FSA_RESULT::NOT_FILE: + { + return s_fsaStr_NOT_FILE.GetPtr(); + } + case FSA_RESULT::NOT_DIR: + { + return s_fsaStr_NOT_DIR.GetPtr(); + } + case FSA_RESULT::OUT_OF_RESOURCES: + { + return s_fsaStr_OUT_OF_RESOURCES.GetPtr(); + } + case FSA_RESULT::FATAL_ERROR: + { + return s_fsaStr_UNKNOWN.GetPtr(); + } + } + cemu_assert_unimplemented(); + return s_fsaStr_UNKNOWN.GetPtr(); + } + + FSA_RESULT FSAMount(FSAClientHandle client, const char* source, const char* target, uint32 flags, void* arg_buf, uint32_t arg_len) + { + if ("/dev/sdcard01" == std::string_view(source) && "/vol/external01" == std::string_view(target) && flags == 0 && arg_buf == nullptr && arg_len == 0) + { + mountSDCard(); + return FSA_RESULT::OK; + } + else + { + cemu_assert_unimplemented(); + } + + return FSA_RESULT::FATAL_ERROR; + } + + FSA_RESULT FSAUnmount(FSAClientHandle client, + const char* mountedTarget, + uint32 flags) + { + return FSA_RESULT::OK; + } + void InitializeFS() { cafeExportRegister("coreinit", FSInit, LogType::CoreinitFile); @@ -1957,6 +2733,36 @@ namespace coreinit cafeExportRegister("coreinit", FSGetErrorCodeForViewer, LogType::Placeholder); cafeExportRegister("coreinit", FSGetLastErrorCodeForViewer, LogType::Placeholder); + cafeExportRegister("coreinit", FSAMakeDir, LogType::Placeholder); + cafeExportRegister("coreinit", FSAInit, LogType::Placeholder); + cafeExportRegister("coreinit", FSAAddClient, LogType::Placeholder); + cafeExportRegister("coreinit", FSADelClient, LogType::Placeholder); + cafeExportRegister("coreinit", FSARewindDir, LogType::Placeholder); + cafeExportRegister("coreinit", FSAGetDeviceInfo, LogType::Placeholder); + cafeExportRegister("coreinit", FSARename, LogType::Placeholder); + + cafeExportRegister("coreinit", FSAChangeDir, LogType::Placeholder); + + cafeExportRegister("coreinit", FSAMount, LogType::Placeholder); + cafeExportRegister("coreinit", FSAUnmount, LogType::Placeholder); + + cafeExportRegister("coreinit", FSAChangeMode, LogType::Placeholder); + cafeExportRegister("coreinit", FSAReadDir, LogType::Placeholder); + cafeExportRegister("coreinit", FSAOpenDir, LogType::Placeholder); + cafeExportRegister("coreinit", FSACloseDir, LogType::Placeholder); + cafeExportRegister("coreinit", FSACloseFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSAFlushFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSAOpenFileEx, LogType::Placeholder); + cafeExportRegister("coreinit", FSAGetStatFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSAGetFreeSpaceSize, LogType::Placeholder); + cafeExportRegister("coreinit", FSASetPosFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSATruncateFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSARemove, LogType::Placeholder); + cafeExportRegister("coreinit", FSAReadFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSAWriteFile, LogType::Placeholder); + cafeExportRegister("coreinit", FSAGetStat, LogType::Placeholder); + cafeExportRegister("coreinit", FSAGetStatusStr, LogType::Placeholder); + g_fsRegisteredClientBodies = nullptr; } } // namespace coreinit diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.h b/src/Cafe/OS/libs/coreinit/coreinit_FS.h index e6b7a29c..0355c9aa 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.h @@ -12,6 +12,8 @@ typedef struct typedef MEMPTR> FSDirHandlePtr; +typedef uint32 FSAClientHandle; + typedef struct { MEMPTR userCallback; @@ -284,8 +286,8 @@ namespace coreinit sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask); sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask); - sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); - sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* path, uint32 errorMask); + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask); sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp index 33f3e7c2..9345d58f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp @@ -1,40 +1,9 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "coreinit_IPCBuf.h" namespace coreinit { - - struct FIFOEntry_t - { - MEMPTR p; - }; - - struct IPCFifo_t - { - uint32be writeIndex; - uint32be readIndex; - uint32be availableEntries; // number of available entries - uint32be entryCount; - MEMPTR entryArray; - }; - - struct IPCBufPool_t - { - /* +0x00 */ uint32be magic; - /* +0x04 */ MEMPTR fullBufferPtr; - /* +0x08 */ uint32be fullBufferSize; - /* +0x0C */ uint32be uknFromParamR7; // boolean? - /* +0x10 */ uint32be ukn10; // set to zero on init - /* +0x14 */ uint32be entrySize1; - /* +0x18 */ uint32be entrySize2; // set to same value as entrySize1 - /* +0x1C */ uint32be entryCount; // actual number of used entries - /* +0x20 */ MEMPTR entryStartPtr; - /* +0x24 */ uint32be entryCountMul4; - /* +0x28 */ IPCFifo_t fifo; - /* +0x3C */ coreinit::OSMutex mutex; - // full size is 0x68 - }; - void FIFOInit(IPCFifo_t* fifo, uint32 entryCount, void* entryArray) { fifo->entryCount = entryCount; @@ -90,8 +59,6 @@ namespace coreinit return (uint8*)v; } - static_assert(sizeof(IPCBufPool_t) == 0x68); - IPCBufPool_t* IPCBufPoolCreate(uint8* bufferArea, uint32 bufferSize, uint32 entrySize, uint32be* entryCountOutput, uint32 uknR7) { memset(bufferArea, 0, bufferSize); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h index 004fb178..bfb29397 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h @@ -2,5 +2,43 @@ namespace coreinit { + struct FIFOEntry_t + { + MEMPTR p; + }; + + struct IPCFifo_t + { + uint32be writeIndex; + uint32be readIndex; + uint32be availableEntries; // number of available entries + uint32be entryCount; + MEMPTR entryArray; + }; + + struct IPCBufPool_t + { + /* +0x00 */ uint32be magic; + /* +0x04 */ MEMPTR fullBufferPtr; + /* +0x08 */ uint32be fullBufferSize; + /* +0x0C */ uint32be uknFromParamR7; // boolean? + /* +0x10 */ uint32be ukn10; // set to zero on init + /* +0x14 */ uint32be entrySize1; + /* +0x18 */ uint32be entrySize2; // set to same value as entrySize1 + /* +0x1C */ uint32be entryCount; // actual number of used entries + /* +0x20 */ MEMPTR entryStartPtr; + /* +0x24 */ uint32be entryCountMul4; + /* +0x28 */ IPCFifo_t fifo; + /* +0x3C */ coreinit::OSMutex mutex; + /* +0x68 */ uint32 ukn68; + // full size is 0x6C + }; + + static_assert(sizeof(IPCBufPool_t) == 0x6C); + + uint8* IPCBufPoolAllocate(IPCBufPool_t* ipcBufPool, uint32 size); + IPCBufPool_t* IPCBufPoolCreate(uint8* bufferArea, uint32 bufferSize, uint32 entrySize, uint32be* entryCountOutput, uint32 uknR7); + sint32 IPCBufPoolFree(IPCBufPool_t* ipcBufPool, uint8* entry); + void InitializeIPCBuf(); -} +} // namespace coreinit diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp index dcc0d6df..51a3f542 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp @@ -64,6 +64,24 @@ sint32 _GetArgLength(const char* arg) return c; } +static std::string GetLaunchArgs() +{ + std::string argStr = CafeSystem::GetForegroundTitleArgStr(); + if(std::vector overrideArgs; CafeSystem::GetOverrideArgStr(overrideArgs)) + { + // args are overriden by launch directive (OSLaunchTitleByPath) + // keep the rpx path but use the arguments from the override + if (size_t pos = argStr.find(' '); pos != std::string::npos) + argStr.resize(pos); + for(size_t i=0; iargStorage, std::string(rpxFileName).c_str()); - std::string _argStr = CafeSystem::GetForegroundTitleArgStr(); + std::string _argStr = GetLaunchArgs(); + CafeSystem::UnsetOverrideArgs(); // make sure next launch doesn't accidentally use the same arguments const char* argString = _argStr.c_str(); // attach parameters from arg string if (argString && argString[0] != '\0') diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp index 41eaa2fa..a784e593 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -103,13 +103,12 @@ void coreinitExport_MCP_TitleListByAppType(PPCInterpreter_t* hCPU) void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU) { - cemuLog_logDebug(LogType::Force, "MCP_TitleList(...) unimplemented"); ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32BEPtr(countOutput, 1); ppcDefineParamStructPtr(titleList, MCPTitleInfo, 2); ppcDefineParamU32(titleListBufferSize, 3); - // todo -> Other parameters + // todo -> Other parameters? mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST; @@ -120,6 +119,8 @@ void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU) *countOutput = mcpRequest->titleListRequest.titleCount; + cemuLog_logDebug(LogType::Force, "MCP_TitleList(...) returned {} titles", (uint32)mcpRequest->titleListRequest.titleCount); + osLib_returnFromFunction(hCPU, mcpRequest->returnCode); } @@ -186,7 +187,6 @@ void coreinitExport_MCP_GetTitleInfoByTitleAndDevice(PPCInterpreter_t* hCPU) } osLib_returnFromFunction(hCPU, mcpRequest->returnCode); - } namespace coreinit @@ -200,7 +200,7 @@ namespace coreinit systemVersion->n0 = 0x5; systemVersion->n1 = 0x5; - systemVersion->n2 = 0x2; + systemVersion->n2 = 0x5; // todo: Load this from \sys\title\00050010\10041200\content\version.bin osLib_returnFromFunction(hCPU, 0); @@ -236,57 +236,56 @@ namespace coreinit } #pragma pack(1) - typedef struct + struct MCPDevice_t { - /* +0x000 */ char storageName[0x90]; // the name in the storage path + /* +0x000 */ char storageName[0x8]; // the name in the storage path (mlc, slc, usb?) // volumeId at +8 + /* +0x008 */ char volumeId[16]; // + /* +0x018 */ char ukn[0x90 - 0x18]; /* +0x090 */ char storagePath[0x280 - 1]; // /vol/storage_%s%02x - /* +0x30F */ uint32be storageSubindexOrMask; // the id in the storage path, but this might also be a MASK of indices (e.g. 1 -> Only device 1, 7 -> Device 1,2,3) men.rpx expects 0xF (or 0x7?) to be set for MLC, SLC and USB for MLC_FullDeviceList + /* +0x30F */ uint32be flags; // men.rpx checks for 0x2 and 0x8 uint8 ukn313[4]; uint8 ukn317[4]; - }MCPDevice_t; + }; #pragma pack() - static_assert(sizeof(MCPDevice_t) == 0x31B, "MCPDevice_t has invalid size"); - static_assert(offsetof(MCPDevice_t, storagePath) == 0x090, "MCPDevice_t.storagePath has invalid offset"); - static_assert(offsetof(MCPDevice_t, storageSubindexOrMask) == 0x30F, "MCPDevice_t.storageSubindex has invalid offset"); - static_assert(offsetof(MCPDevice_t, ukn313) == 0x313, "MCPDevice_t.ukn313 has invalid offset"); - static_assert(offsetof(MCPDevice_t, ukn317) == 0x317, "MCPDevice_t.ukn317 has invalid offset"); + static_assert(sizeof(MCPDevice_t) == 0x31B); + + static_assert(sizeof(MCPDevice_t) == 0x31B); + static_assert(offsetof(MCPDevice_t, storagePath) == 0x90); + static_assert(offsetof(MCPDevice_t, flags) == 0x30F); + static_assert(offsetof(MCPDevice_t, ukn313) == 0x313); + static_assert(offsetof(MCPDevice_t, ukn317) == 0x317); void MCP_DeviceListEx(uint32 mcpHandle, uint32be* deviceCount, MCPDevice_t* deviceList, uint32 deviceListSize, bool returnFullList) { sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t); - if (maxDeviceCount < 3*3) - assert_dbg(); + cemu_assert(maxDeviceCount >= 2); - // if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot - - memset(deviceList, 0, sizeof(MCPDevice_t) * 1); + memset(deviceList, 0, deviceListSize); sint32 index = 0; - for (sint32 f = 0; f < 1; f++) - { - // 0 - strcpy(deviceList[index].storageName, "mlc"); - deviceList[index].storageSubindexOrMask = 0xF; // bitmask? - sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask); - index++; - // 1 - strcpy(deviceList[index].storageName, "slc"); - deviceList[index].storageSubindexOrMask = 0xF; // bitmask? - sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask); - index++; - // 2 - strcpy(deviceList[index].storageName, "usb"); - deviceList[index].storageSubindexOrMask = 0xF; - sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask); - index++; - } + uint32 flags = 2 | 8; + // flag 2 is necessary for Wii U menu and Friend List to load + // if we dont set flag 0x8 then Wii U menu will show a disk loading icon and screen + // slc + strcpy(deviceList[index].storageName, "slc"); + strcpy(deviceList[index].volumeId, "VOLID_SLC"); + deviceList[index].flags = flags; + strcpy(deviceList[index].storagePath, "/vol/system_slc"); // unsure + index++; + // mlc + strcpy(deviceList[index].storageName, "mlc"); + strcpy(deviceList[index].volumeId, "VOLID_MLC"); + deviceList[index].flags = flags; + sprintf(deviceList[index].storagePath, "/vol/storage_mlc01"); + index++; + + // we currently dont emulate USB storage *deviceCount = index; } - void export_MCP_DeviceList(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); @@ -306,12 +305,12 @@ namespace coreinit memset(deviceList, 0, sizeof(MCPDevice_t) * 1); // 0 strcpy(deviceList[0].storageName, "mlc"); - deviceList[0].storageSubindexOrMask = (0x01); // bitmask? - sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].storageSubindexOrMask); + deviceList[0].flags = (0x01); // bitmask? + sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].flags); // 1 strcpy(deviceList[1].storageName, "slc"); - deviceList[1].storageSubindexOrMask = (0x01); // bitmask? - sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].storageSubindexOrMask); + deviceList[1].flags = (0x01); // bitmask? + sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].flags); // 2 //strcpy(deviceList[2].storageName, "usb"); @@ -360,6 +359,8 @@ namespace coreinit // this callback is to let the app know when the title list changed? + //PPCCoreCallback(callbackMPTR); // -> If we trigger the callback then the menu will repeat with a call to MCP_GetTitleList(), MCP_DeviceList() and MCP_TitleListUpdateGetNext + osLib_returnFromFunction(hCPU, 0); } @@ -387,6 +388,34 @@ namespace coreinit osLib_returnFromFunction(hCPU, 0); } + uint32 MCP_UpdateClearContextAsync(uint32 mcpHandle, betype* callbackPtr) + { + cemuLog_logDebug(LogType::Force, "MCP_UpdateClearContextAsync() - stubbed"); + uint32 clearContextResult = 0; + PPCCoreCallback(*callbackPtr, clearContextResult); + return 0; + } + + uint32 MCP_InstallUtilGetTitleEnability(uint32 mcpHandle, uint32be* enabilityOutput, MCPTitleInfo* title) + { + *enabilityOutput = 1; + return 0; + } + + uint32 MCP_GetEcoSettings(uint32 mcpHandle, uint32be* flagCaffeineEnable, uint32be* uknFlag2, uint32be* uknFlag3) + { + *flagCaffeineEnable = 1; // returning 1 here will stop the Wii U Menu from showing the Quick Start setup dialogue + *uknFlag2 = 0; + *uknFlag3 = 0; + return 0; + } + + uint32 MCP_RightCheckLaunchable(uint32 mcpHandle, uint64 titleId, uint32be* launchableOut) + { + *launchableOut = 1; + return 0; + } + void InitializeMCP() { osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open); @@ -408,6 +437,12 @@ namespace coreinit osLib_addFunction("coreinit", "MCP_UpdateCheckContext", export_MCP_UpdateCheckContext); osLib_addFunction("coreinit", "MCP_TitleListUpdateGetNext", export_MCP_TitleListUpdateGetNext); osLib_addFunction("coreinit", "MCP_GetOverlayAppInfo", export_MCP_GetOverlayAppInfo); + cafeExportRegister("coreinit", MCP_UpdateClearContextAsync, LogType::Placeholder); + + cafeExportRegister("coreinit", MCP_InstallUtilGetTitleEnability, LogType::Placeholder); + cafeExportRegister("coreinit", MCP_RightCheckLaunchable, LogType::Placeholder); + + cafeExportRegister("coreinit", MCP_GetEcoSettings, LogType::Placeholder); } } @@ -552,6 +587,27 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } + /* caffeine settings (Quick Start) */ + else if (_strcmpi(ucParam->settingName, "caffeine.enable") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 1); + } + else if (_strcmpi(ucParam->settingName, "caffeine.ad_enable") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "caffeine.push_enable") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "caffeine.drcled_enable") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } else { cemuLog_logDebug(LogType::Force, "Unsupported SCI value: {} Size {:08x}", ucParam->settingName, ucParam->ukn4_size); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp index 6586416f..dc82f772 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp @@ -50,7 +50,7 @@ namespace coreinit MEMList g_list3; std::array gHeapFillValues{ 0xC3C3C3C3, 0xF3F3F3F3, 0xD3D3D3D3 }; - OSSpinLock gHeapGlobalLock; + SysAllocator gHeapGlobalLock; MEMHeapBase* gDefaultHeap; bool MEMHeapTable_Add(MEMHeapBase* heap) @@ -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 6b3dedc1..59bd034e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -20,8 +20,6 @@ SlimRWLock srwlock_activeThreadList; MPTR activeThread[256]; sint32 activeThreadCount = 0; -MPTR exitThreadPtr = 0; - void nnNfp_update(); namespace coreinit @@ -198,8 +196,6 @@ namespace coreinit return __currentCoreThread[currentInstance->spr.UPIR]; } - MPTR funcPtr_threadEntry = 0; - void threadEntry(PPCInterpreter_t* hCPU) { OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(hCPU); @@ -223,12 +219,6 @@ namespace coreinit void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU); - void initFunctionPointers() - { - exitThreadPtr = PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr); - funcPtr_threadEntry = PPCInterpreter_makeCallableExportDepr(threadEntry); - } - void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType) { cemu_assert_debug(thread != nullptr); // make thread struct mandatory. Caller can always use SysAllocator @@ -236,7 +226,7 @@ namespace coreinit bool isThreadStillActive = __OSIsThreadActive(thread); if (isThreadStillActive) { - // workaround for games that restart threads to quickly + // workaround for games that restart threads before they correctly entered stopped/moribund state // seen in Fast Racing Neo at boot (0x020617BC OSCreateThread) cemuLog_log(LogType::Force, "Game attempting to re-initialize existing thread"); while ((thread->state == OSThread_t::THREAD_STATE::STATE_READY || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) && thread->suspendCounter == 0) @@ -257,15 +247,9 @@ 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->context.magic0 = OS_CONTEXT_MAGIC_0; - thread->context.magic1 = OS_CONTEXT_MAGIC_1; - thread->magic = 'tHrD'; + thread->SetMagic(); thread->type = threadType; thread->state = (entryPoint != MPTR_NULL) ? OSThread_t::THREAD_STATE::STATE_READY : OSThread_t::THREAD_STATE::STATE_NONE; thread->entrypoint = _swapEndianU32(entryPoint); @@ -279,8 +263,8 @@ namespace coreinit // init misc stuff thread->attr = affinityMask; thread->context.setAffinity(affinityMask); - thread->context.srr0 = funcPtr_threadEntry; - thread->context.lr = _swapEndianU32(exitThreadPtr); + thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry); + thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); thread->id = 0x8000; // Warriors Orochi 3 softlocks if this is zero due to confusing threads (_OSActivateThread should set this?) // init ugqr thread->context.gqr[0] = 0x00000000; @@ -362,8 +346,8 @@ namespace coreinit // todo - this should fully reinitialize the thread? thread->entrypoint = _swapEndianU32(funcAddress); - thread->context.srr0 = coreinit::funcPtr_threadEntry; - thread->context.lr = _swapEndianU32(exitThreadPtr); + thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry); + thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); thread->context.gpr[3] = _swapEndianU32(numParam); thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); thread->suspendCounter = 0; // verify @@ -563,7 +547,10 @@ namespace coreinit // adds the thread to each core's run queue if in runable state void __OSAddReadyThreadToRunQueue(OSThread_t* thread) { + cemu_assert_debug(MMU_IsInPPCMemorySpace(thread)); + cemu_assert_debug(thread->IsValidMagic()); cemu_assert_debug(__OSHasSchedulerLock()); + if (thread->state != OSThread_t::THREAD_STATE::STATE_READY) return; if (thread->suspendCounter != 0) @@ -703,10 +690,18 @@ namespace coreinit } else if (prevAffinityMask != affinityMask) { - __OSRemoveThreadFromRunQueues(thread); - thread->attr = (thread->attr & ~7) | (affinityMask & 7); - thread->context.setAffinity(affinityMask); - __OSAddReadyThreadToRunQueue(thread); + if(thread->state != OSThread_t::THREAD_STATE::STATE_NONE) + { + __OSRemoveThreadFromRunQueues(thread); + thread->attr = (thread->attr & ~7) | (affinityMask & 7); + thread->context.setAffinity(affinityMask); + __OSAddReadyThreadToRunQueue(thread); + } + else + { + thread->attr = (thread->attr & ~7) | (affinityMask & 7); + thread->context.setAffinity(affinityMask); + } } __OSUnlockScheduler(); return true; @@ -803,17 +798,21 @@ namespace coreinit return suspendCounter > 0; } + bool OSIsThreadRunningNoLock(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + return thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING; + } + bool OSIsThreadRunning(OSThread_t* thread) { bool isRunning = false; __OSLockScheduler(); - if (thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) - isRunning = true; + isRunning = OSIsThreadRunningNoLock(thread); __OSUnlockScheduler(); return isRunning; } - void OSCancelThread(OSThread_t* thread) { __OSLockScheduler(); @@ -1005,6 +1004,18 @@ namespace coreinit return selectedThread; } + void __OSDeleteAllActivePPCThreads() + { + __OSLockScheduler(); + while(activeThreadCount > 0) + { + MEMPTR t{activeThread[0]}; + t->state = OSThread_t::THREAD_STATE::STATE_NONE; + __OSDeactivateThread(t.GetPtr()); + } + __OSUnlockScheduler(); + } + void __OSCheckSystemEvents() { // AX update @@ -1189,7 +1200,7 @@ namespace coreinit g_schedulerThreadHandles.emplace_back(it.native_handle()); } - // shuts down all scheduler host threads and deletes all fibers and their state + // shuts down all scheduler host threads and deletes all fibers and ppc threads void OSSchedulerEnd() { std::unique_lock _lock(sSchedulerStateMtx); @@ -1371,9 +1382,10 @@ namespace coreinit for (sint32 i = 0; i < PPC_CORE_COUNT; i++) __currentCoreThread[i] = nullptr; - __OSInitDefaultThreads(); + __OSInitDefaultThreads(); __OSInitTerminatorThreads(); - } + + } } void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index f1c39bd4..e2f5bef2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -404,6 +404,18 @@ struct OSThread_t return 0; } + void SetMagic() + { + context.magic0 = OS_CONTEXT_MAGIC_0; + context.magic1 = OS_CONTEXT_MAGIC_1; + magic = 'tHrD'; + } + + bool IsValidMagic() const + { + return magic == 'tHrD' && context.magic0 == OS_CONTEXT_MAGIC_0 && context.magic1 == OS_CONTEXT_MAGIC_1; + } + /* +0x000 */ OSContext_t context; /* +0x320 */ uint32be magic; // 'tHrD' /* +0x324 */ betype state; @@ -526,6 +538,7 @@ namespace coreinit bool OSIsThreadTerminated(OSThread_t* thread); bool OSIsThreadSuspended(OSThread_t* thread); + bool OSIsThreadRunningNoLock(OSThread_t* thread); bool OSIsThreadRunning(OSThread_t* thread); // OSThreadQueue @@ -591,6 +604,9 @@ namespace coreinit void __OSAddReadyThreadToRunQueue(OSThread_t* thread); bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread); void __OSQueueThreadDeallocation(OSThread_t* thread); + + bool __OSIsThreadActive(OSThread_t* thread); + void __OSDeleteAllActivePPCThreads(); } #pragma pack() diff --git a/src/Cafe/OS/libs/erreula/erreula.cpp b/src/Cafe/OS/libs/erreula/erreula.cpp index 751294c0..1d88b976 100644 --- a/src/Cafe/OS/libs/erreula/erreula.cpp +++ b/src/Cafe/OS/libs/erreula/erreula.cpp @@ -80,7 +80,7 @@ namespace erreula struct ErrEula_t { - coreinit::OSMutex mutex; + SysAllocator mutex; uint32 regionType; uint32 langType; MEMPTR fsClient; 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/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 09984dad..9afb9f85 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -504,7 +504,7 @@ void export_curl_multi_fdset(PPCInterpreter_t* hCPU) // fd write set for (uint32 i = 0; i < h_writeFd.fd_count; i++) { - cemu_assert_debug(false); + hostFdSet(h_writeFd.fd_array[i], writeFd.GetPtr()); } // fd exception set for (uint32 i = 0; i < h_exceptionFd.fd_count; i++) @@ -906,7 +906,8 @@ int sockopt_callback(void* clientp, curl_socket_t curlfd, curlsocktype purpose) } size_t read_callback(char* buffer, size_t size, size_t nitems, void* instream) -{ +{ + nitems = std::min(nitems, 0x4000); CURL_t* curl = (CURL_t*)instream; cemuLog_logDebug(LogType::Force, "read_callback(0x{}, 0x{:x}, 0x{:x}, 0x{:08x}) [func: 0x{:x}]", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), curl->fread_func_set.GetMPTR()); @@ -1385,4 +1386,4 @@ void load() osLib_addFunction("nlibcurl", "curl_easy_cleanup", export_curl_easy_cleanup); osLib_addFunction("nlibcurl", "curl_easy_pause", export_curl_easy_pause); } -} +} \ No newline at end of file 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_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 5dad1b0b..fb1d4d14 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -95,6 +95,51 @@ namespace act return result; } + uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot) + { + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_TRANSFERABLEID; + actRequest->accountSlot = slot; + actRequest->unique = unique; + + uint32 result = _doCemuActRequest(actRequest); + + *transferableId = _swapEndianU64(actRequest->resultU64.u64); + + return result; + } + + uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) + { + memset(token, 0, sizeof(independentServiceToken_t)); + actPrepareRequest(); + actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; + actRequest->titleId = CafeSystem::GetForegroundTitleId(); + actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); + actRequest->expiresIn = cacheDurationInSeconds; + strcpy(actRequest->clientId, clientId); + + uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + + memcpy(token, actRequest->resultBinary.binBuffer, sizeof(independentServiceToken_t)); + return getNNReturnCode(resultCode, actRequest); + } + + sint64 GetUtcOffset() + { + return ((ppcCyclesSince2000 / ESPRESSO_CORE_CLOCK) - (ppcCyclesSince2000_UTC / ESPRESSO_CORE_CLOCK)) * 1'000'000; + } + + sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo) + { + + if (!pOutOffset) + return 0xc0712c80; + + *pOutOffset = GetUtcOffset(); + return 0; + } + sint32 g_initializeCount = 0; // inc in Initialize and dec in Finalize uint32 Initialize() { @@ -170,21 +215,6 @@ uint32 GetPrincipalIdEx(uint32be* principalId, uint8 slot) return result; } - -uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot) -{ - actPrepareRequest2(); - actRequest->requestCode = IOSU_ARC_TRANSFERABLEID; - actRequest->accountSlot = slot; - actRequest->unique = unique; - - uint32 result = _doCemuActRequest(actRequest); - - *transferableId = _swapEndianU64(actRequest->resultU64.u64); - - return result; -} - uint32 GetCountryEx(char* country, uint8 slot) { actPrepareRequest2(); @@ -278,7 +308,7 @@ void nnActExport_GetTransferableIdEx(PPCInterpreter_t* hCPU) cemuLog_logDebug(LogType::Force, "nn_act.GetTransferableIdEx(0x{:08x}, 0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5] & 0xFF); - uint32 r = GetTransferableIdEx(transferableId, unique, slot); + uint32 r = nn::act::GetTransferableIdEx(transferableId, unique, slot); osLib_returnFromFunction(hCPU, 0); // ResultSuccess } @@ -589,33 +619,11 @@ void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, getNNReturnCode(resultCode, actRequest)); } -struct independentServiceToken_t -{ - /* +0x000 */ char token[0x201]; -}; -static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size - -uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) -{ - memset(token, 0, sizeof(independentServiceToken_t)); - actPrepareRequest(); - actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; - actRequest->titleId = CafeSystem::GetForegroundTitleId(); - actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); - actRequest->expiresIn = cacheDurationInSeconds; - strcpy(actRequest->clientId, clientId); - - uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); - - memcpy(token, actRequest->resultBinary.binBuffer, sizeof(independentServiceToken_t)); - return getNNReturnCode(resultCode, actRequest); -} - void nnActExport_AcquireIndependentServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(token, independentServiceToken_t, 0); ppcDefineParamMEMPTR(serviceToken, const char, 1); - uint32 result = AcquireIndependentServiceToken(token.GetPtr(), serviceToken.GetPtr(), 0); + uint32 result = nn::act::AcquireIndependentServiceToken(token.GetPtr(), serviceToken.GetPtr(), 0); cemuLog_logDebug(LogType::Force, "nn_act.AcquireIndependentServiceToken(0x{}, {}) -> {:x}", (void*)token.GetPtr(), serviceToken.GetPtr(), result); cemuLog_logDebug(LogType::Force, "Token: {}", serviceToken.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -626,7 +634,7 @@ void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) ppcDefineParamStructPtr(token, independentServiceToken_t, 0); ppcDefineParamMEMPTR(clientId, const char, 1); ppcDefineParamU32(cacheDurationInSeconds, 2); - uint32 result = AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); + uint32 result = nn::act::AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireIndependentServiceToken2"); osLib_returnFromFunction(hCPU, result); } @@ -634,7 +642,7 @@ void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) void nnActExport_AcquireEcServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(pEcServiceToken, independentServiceToken_t, 0); - uint32 result = AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); + uint32 result = nn::act::AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireEcServiceToken"); osLib_returnFromFunction(hCPU, result); } @@ -656,30 +664,6 @@ void nnActExport_AcquirePrincipalIdByAccountId(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result); } -void nnActExport_GetUtcOffsetEx(PPCInterpreter_t* hCPU) -{ - // GetUtcOffsetEx__Q2_2nn3actFPLUc - ppcDefineParamU32BEPtr(utcOffsetOut, 0); - ppcDefineParamU32(uknParam, 1); - - cemuLog_logDebug(LogType::Force, "Called nn_act.GetUtcOffsetEx"); - - *utcOffsetOut = 0; - - cemuLog_logDebug(LogType::Force, "GetUtcOffsetEx stub"); - - osLib_returnFromFunction(hCPU, 0); -} - -void nnActExport_GetUtcOffset(PPCInterpreter_t* hCPU) -{ - uint64 utcOffset = 0; - - uint64 utcDifferenceInSeconds = (ppcCyclesSince2000_UTC / ESPRESSO_CORE_CLOCK) - (ppcCyclesSince2000 / ESPRESSO_CORE_CLOCK); - - osLib_returnFromFunction64(hCPU, utcDifferenceInSeconds * 1000000ULL); -} - // register account functions void nnAct_load() { @@ -744,8 +728,8 @@ void nnAct_load() // placeholders / incomplete implementations osLib_addFunction("nn_act", "HasNfsAccount__Q2_2nn3actFv", nnActExport_HasNfsAccount); osLib_addFunction("nn_act", "GetHostServerSettings__Q2_2nn3actFPcT1Uc", nnActExport_GetHostServerSettings); - osLib_addFunction("nn_act", "GetUtcOffset__Q2_2nn3actFv", nnActExport_GetUtcOffset); - osLib_addFunction("nn_act", "GetUtcOffsetEx__Q2_2nn3actFPLUc", nnActExport_GetUtcOffsetEx); + cafeExportRegisterFunc(nn::act::GetUtcOffset, "nn_act", "GetUtcOffset__Q2_2nn3actFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::act::GetUtcOffsetEx, "nn_act", "GetUtcOffsetEx__Q2_2nn3actFPLUc", LogType::Placeholder); } diff --git a/src/Cafe/OS/libs/nn_act/nn_act.h b/src/Cafe/OS/libs/nn_act/nn_act.h index 06df4af1..6b0a1236 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.h +++ b/src/Cafe/OS/libs/nn_act/nn_act.h @@ -1,5 +1,13 @@ #pragma once +#include "Cafe/IOSU/legacy/iosu_act.h" + +struct independentServiceToken_t +{ + /* +0x000 */ char token[0x201]; +}; +static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size + namespace nn { namespace act @@ -9,6 +17,12 @@ namespace act uint32 GetPersistentIdEx(uint8 slot); uint32 GetUuidEx(uint8* uuid, uint8 slot, sint32 name = -2); uint32 GetSimpleAddressIdEx(uint32be* simpleAddressId, uint8 slot); + uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot); + + sint64 GetUtcOffset(); + sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo); + + uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds); static uint32 getCountryCodeFromSimpleAddress(uint32 simpleAddressId) { diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index c2a35341..c2d65a5f 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -23,7 +23,7 @@ memset(bossRequest, 0, sizeof(iosuBossCemuRequest_t)); \ memset(bossBufferVector, 0, sizeof(ioBufferVector_t)); \ bossBufferVector->buffer = (uint8*)bossRequest; - coreinit::OSMutex g_mutex; + SysAllocator g_mutex; sint32 g_initCounter = 0; bool g_isInitialized = false; @@ -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 038b555b..50036249 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -1,7 +1,14 @@ -#include "Cafe/OS/common/OSCommon.h" -#include "Cafe/OS/libs/nn_common.h" #include "nn_olv.h" +#include "nn_olv_InitializeTypes.h" +#include "nn_olv_UploadCommunityTypes.h" +#include "nn_olv_DownloadCommunityTypes.h" +#include "nn_olv_UploadFavoriteTypes.h" +#include "nn_olv_PostTypes.h" + +#include "Cafe/OS/libs/proc_ui/proc_ui.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" + namespace nn { namespace olv @@ -124,14 +131,6 @@ namespace nn osLib_returnFromFunction(hCPU, 0); } - void export_DownloadCommunityDataList(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(communityListSizeOut, uint32be, 1); - - *communityListSizeOut = 0; - osLib_returnFromFunction(hCPU, 0); - } - void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); @@ -203,27 +202,117 @@ namespace nn osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(&portalAppParam->serviceToken)); } - uint32 UploadPostDataByPostApp(void *postParam) + static SysAllocator s_OlvReleaseBgThread; + SysAllocator s_OlvReleaseBgThreadStack; + SysAllocator s_OlvReleaseBgThreadName; + + void StubPostAppReleaseBackground(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::Force, "UploadPostDataByPostApp() called. Returning error"); - return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, 0); // undefined error + coreinit::OSSleepTicks(ESPRESSO_TIMER_CLOCK * 2); // Sleep 2s + ProcUI_SendForegroundMessage(); } + sint32 StubPostApp(void* pAnyPostParam) + { + coreinit::OSCreateThreadType(s_OlvReleaseBgThread.GetPtr(), RPLLoader_MakePPCCallable(StubPostAppReleaseBackground), 0, nullptr, s_OlvReleaseBgThreadStack.GetPtr() + s_OlvReleaseBgThreadStack.GetByteSize(), (sint32)s_OlvReleaseBgThreadStack.GetByteSize(), 0, (1 << 1) | (1 << 3), OSThread_t::THREAD_TYPE::TYPE_APP); + coreinit::OSResumeThread(s_OlvReleaseBgThread.GetPtr()); + strcpy(s_OlvReleaseBgThreadName.GetPtr(), "StubPostApp!"); + coreinit::OSSetThreadName(s_OlvReleaseBgThread.GetPtr(),s_OlvReleaseBgThreadName.GetPtr()); + return OLV_RESULT_SUCCESS; + } + + sint32 StubPostAppResult() + { + return OLV_RESULT_STATUS(301); // Cancelled post app + } + + // Somehow required, MK8 doesn't even seem to care about the error codes lol + char* UploadedPostData_GetPostId(char* pPostData) + { + pPostData[4] = '\0'; + return &pPostData[4]; + } + + // https://github.com/kinnay/NintendoClients/wiki/Wii-U-Error-Codes#act-error-codes + constexpr uint32 GetErrorCodeImpl(uint32 in) + { + uint32_t errorCode = in; + uint32_t errorVersion = (errorCode >> 27) & 3; + uint32_t errorModuleMask = (errorVersion != 3) ? 0x1FF00000 : 0x7F00000; + bool isCodeFailure = errorCode & 0x80000000; + + if (((errorCode & errorModuleMask) >> 20) == NN_RESULT_MODULE_NN_ACT) + { + // BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE or BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE_TEMPORARILY + if (errorCode == OLV_ACT_RESULT_STATUS(2805) || errorCode == OLV_ACT_RESULT_STATUS(2825)) + { + uint32 tmpCode = OLV_RESULT_STATUS(1008); + return GetErrorCodeImpl(tmpCode); + } + // BANNED_DEVICE_IN_INDEPENDENT_SERVICE or BANNED_DEVICE_IN_INDEPENDENT_SERVICE_TEMPORARILY + else if (errorCode == OLV_ACT_RESULT_STATUS(2815) || errorCode == OLV_ACT_RESULT_STATUS(2835)) + { + uint32 tmpCode = OLV_RESULT_STATUS(1009); + return GetErrorCodeImpl(tmpCode); + } + else + { + // Check ACT error code + return 1159999; + } + } + else + { + if (((errorCode & errorModuleMask) >> 20) == NN_RESULT_MODULE_NN_OLV && isCodeFailure) + { + uint32_t errorValueMask = (errorVersion != 3) ? 0xFFFFF : 0x3FF; + return ((errorCode & errorValueMask) >> 7) + 1150000; + } + else + { + return 1159999; + } + } + } + + uint32 GetErrorCode(uint32be* pResult) + { + return GetErrorCodeImpl(pResult->value()); + } + + static_assert(GetErrorCodeImpl(0xa119c600) == 1155004); + void load() { - 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", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", export_DownloadCommunityDataList); + 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(UploadPostDataByPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Placeholder); + cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Force); + cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadCommentDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv31UploadCommentDataByPostAppParam", LogType::Force); + cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadDirectMessageDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv37UploadDirectMessageDataByPostAppParam", LogType::Force); + + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultByPostApp__Q2_2nn3olvFv", LogType::Force); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedPostDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv16UploadedPostData", LogType::Force); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedDirectMessageDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv25UploadedDirectMessageData", LogType::Force); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedCommentDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv19UploadedCommentData", LogType::Force); + + cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::Force); } } -} +} \ 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 e8278136..c608e391 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h @@ -1,9 +1,23 @@ #pragma once +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include "Cafe/CafeSystem.h" +#include "Cemu/napi/napi.h" + +#include "nn_olv_Common.h" + namespace nn { namespace olv { + + extern ParamPackStorage g_ParamPack; + extern DiscoveryResultStorage g_DiscoveryResults; + + sint32 GetOlvAccessKey(uint32_t* pOutKey); + void load(); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp new file mode 100644 index 00000000..839aa412 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp @@ -0,0 +1,285 @@ +#include "nn_olv_Common.h" +#include + +namespace nn +{ + namespace olv + { + + sint32 olv_copy_wstr(char16_t* dest, const char16_t* src, uint32_t maxSize, uint32_t destSize) + { + size_t len = maxSize + 1; + if (olv_wstrnlen(src, len) > maxSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + memset(dest, 0, 2 * destSize); + olv_wstrncpy(dest, src, len); + return OLV_RESULT_SUCCESS; + } + + size_t olv_wstrnlen(const char16_t* str, size_t max_len) + { + size_t len = 0; + while (len < max_len && str[len] != u'\0') + len++; + + return len; + } + + char16_t* olv_wstrncpy(char16_t* dest, const char16_t* src, size_t n) + { + char16_t* ret = dest; + while (n > 0 && *src != u'\0') + { + *dest++ = *src++; + n--; + } + while (n > 0) + { + *dest++ = u'\0'; + n--; + } + return ret; + } + + bool CheckTGA(const uint8* pTgaFile, uint32 pTgaFileLen, TGACheckType checkType) + { + const TGAHeader* header = (const TGAHeader*)pTgaFile; + try + { + if (checkType == TGACheckType::CHECK_PAINTING) + { + if ( + header->idLength || + header->colorMapType || + header->imageType != 2 || // Uncompressed true color + header->first_entry_idx || + header->colormap_length || + header->bpp || + header->x_origin || + header->y_origin || + header->width != 320 || + header->height != 120 || + header->pixel_depth_bpp != 32 || + header->image_desc_bits != 8 + ) + { + throw std::runtime_error("TGACheckType::CHECK_PAINTING - Invalid TGA file!"); + } + } + else if (checkType == TGACheckType::CHECK_COMMUNITY_ICON) + { + if (header->width != 128 || header->height != 128 || header->pixel_depth_bpp != 32) + throw std::runtime_error("TGACheckType::CHECK_COMMUNITY_ICON - Invalid TGA file -> width, height or bpp is wrong"); + } + else if (checkType == TGACheckType::CHECK_100x100_200x200) + { + if (header->pixel_depth_bpp != 32) + throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> bpp is wrong"); + + if (header->width == 100) + { + if (header->height != 100) + throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> Not 100x100"); + } + else if (header->width != 200 || header->height != 200) + throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> Not 100x100 or 200x200"); + } + } + catch (const std::runtime_error& error) + { + // TGA Check Error! illegal format + cemuLog_log(LogType::Force, error.what()); + return false; + } + return true; + } + + sint32 DecodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType) + { + uint32 decompressedSize = outSize; + if (DecompressTGA(pOutBuffer, &decompressedSize, pInBuffer, inSize)) + { + if (CheckTGA(pOutBuffer, decompressedSize, checkType)) + return decompressedSize; + + return -2; + } + else + { + cemuLog_log(LogType::Force, "OLIVE uncompress error.\n"); + return -1; + } + } + + sint32 EncodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType) + { + if (inSize == outSize) + { + if (!CheckTGA(pInBuffer, inSize, checkType)) + return -1; + + uint32 compressedSize = outSize; + if (CompressTGA(pOutBuffer, &compressedSize, pInBuffer, inSize)) + return compressedSize; + else + { + cemuLog_log(LogType::Force, "OLIVE compress error.\n"); + return -1; + } + } + else + { + cemuLog_log(LogType::Force, "compress buffer size check error. uSrcBufSize({}) != uDstBufSize({})\n", inSize, outSize); + return -1; + } + } + + bool DecompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize) + { + if (pOutBuffer == nullptr || pOutSize == nullptr || pInBuffer == nullptr || inSize == 0) + return false; + + uLongf bufferSize = *pOutSize; + int result = uncompress(pOutBuffer, &bufferSize, pInBuffer, inSize); + + if (result == Z_OK) + { + *pOutSize = static_cast(bufferSize); + return true; + } + else + { + const char* error_msg = (result == Z_MEM_ERROR) ? "Insufficient memory" : "Unknown decompression error"; + cemuLog_log(LogType::Force, "OLIVE ZLIB - ERROR: {}\n", error_msg); + return false; + } + } + + bool CompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize) + { + if (pOutBuffer == nullptr || pOutSize == nullptr || pInBuffer == nullptr || inSize == 0) + return false; + + uLongf bufferSize = *pOutSize; + int result = compress(pOutBuffer, &bufferSize, pInBuffer, inSize); + + if (result == Z_OK) + { + *pOutSize = static_cast(bufferSize); + return true; + } + else + { + const char* error_msg = (result == Z_MEM_ERROR) ? "Insufficient memory" : "Unknown compression error"; + cemuLog_log(LogType::Force, "OLIVE ZLIB - ERROR: {}\n", error_msg); + return false; + } + } + + constexpr uint32 CreateCommunityCodeById(uint32 communityId) + { + uint32 res = communityId ^ (communityId << 18) ^ (communityId << 24) ^ (communityId << 30); + return res ^ (16 * (res & 0xF0F0F0F)) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 17) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 23) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 29) ^ 0x20121002; + } + + constexpr uint32 CreateCommunityIdByCode(uint32 code) + { + uint32 res = code ^ 0x20121002 ^ ((code ^ 0x20121002u) >> 17) ^ ((code ^ 0x20121002u) >> 23) ^ ((code ^ 0x20121002u) >> 29); + return res ^ (16 * (res & 0xF0F0F0F)) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 18) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 24) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 30); + } + + + constexpr uint32 GetCommunityCodeTopByte(uint32 communityId) + { + uint8 code_byte3 = (uint8_t)(communityId >> 0x18); + uint8 code_byte2 = (uint8_t)(communityId >> 0x10); + uint8 code_byte1 = (uint8_t)(communityId >> 8); + uint8 code_byte0 = (uint8_t)(communityId >> 0); + return code_byte3 ^ code_byte2 ^ code_byte1 ^ code_byte0 ^ 0xff; + } + + constexpr uint64 GetRealCommunityCode(uint32_t communityId) + { + uint64 topByte = GetCommunityCodeTopByte(communityId); + if ((0xe7 < topByte) && ((0xe8 < topByte || (0xd4a50fff < communityId)))) + return ((topByte << 32) | communityId) & 0x7fffffffff; + + return ((topByte << 32) | communityId); + } + + void WriteCommunityCode(char* pOutCode, uint32 communityId) + { + uint32 code = CreateCommunityCodeById(communityId); + uint64 communityCode = GetRealCommunityCode(code); + sprintf(pOutCode, "%012llu", communityCode); + } + + bool EnsureCommunityCode(char* pCode) + { + uint64 code; + if (sscanf(pCode, "%012llu", &code) > 0) + { + uint32 lowerCode = code; + uint64 newCode = GetRealCommunityCode(code); + return code == newCode; + } + return false; + } + + bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId) + { + bool result = false; + if (communityId != -1) + { + if (communityId) + { + WriteCommunityCode(pOutCode, communityId); + *outLen = strnlen(pOutCode, 12); + if (EnsureCommunityCode(pOutCode)) + result = 1; + } + } + return result; + } + + static_assert(GetRealCommunityCode(CreateCommunityCodeById(140500)) == 717651734336, "Wrong community code generation code, result must match."); + + uint32 ExtractCommunityIdFromCode(char* pCode) + { + uint32 id = 0; + uint64 code; + if (sscanf(pCode, "%012llu", &code) > 0) + { + uint32 lower_code = code; + id = CreateCommunityIdByCode(lower_code); + } + return id; + } + + bool GetCommunityIdFromCode(uint32* pOutId, const char* pCode) + { + if (!EnsureCommunityCode((char*)pCode)) + return false; + + *pOutId = ExtractCommunityIdFromCode((char*)pCode); + return true; + } + + sint32 olv_curlformcode_to_error(CURLFORMcode code) + { + switch (code) + { + case CURL_FORMADD_OK: + return OLV_RESULT_SUCCESS; + + case CURL_FORMADD_MEMORY: + return OLV_RESULT_FATAL(25); + + case CURL_FORMADD_OPTION_TWICE: + default: + return OLV_RESULT_LVL6(50); + } + } + } +} \ 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 new file mode 100644 index 00000000..718c10c3 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h @@ -0,0 +1,179 @@ +#pragma once + +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cemu/napi/napi_helper.h" +#include "util/helpers/StringHelpers.h" +#include "pugixml.hpp" + +// https://github.com/kinnay/NintendoClients/wiki/Wii-U-Error-Codes#act-error-codes +#define OLV_ACT_RESULT_STATUS(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) + +#define OLV_RESULT_STATUS(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) +#define OLV_RESULT_LVL6(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) +#define OLV_RESULT_FATAL(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) +#define OLV_RESULT_SUCCESS (BUILD_NN_RESULT(0, NN_RESULT_MODULE_NN_OLV, 1 << 7)) + +#define OLV_RESULT_INVALID_PARAMETER (OLV_RESULT_LVL6(201)) +#define OLV_RESULT_INVALID_DATA (OLV_RESULT_LVL6(202)) +#define OLV_RESULT_NOT_ENOUGH_SIZE (OLV_RESULT_LVL6(203)) +#define OLV_RESULT_INVALID_PTR (OLV_RESULT_LVL6(204)) +#define OLV_RESULT_NOT_INITIALIZED (OLV_RESULT_LVL6(205)) +#define OLV_RESULT_ALREADY_INITIALIZED (OLV_RESULT_LVL6(206)) +#define OLV_RESULT_OFFLINE_MODE_REQUEST (OLV_RESULT_LVL6(207)) +#define OLV_RESULT_MISSING_DATA (OLV_RESULT_LVL6(208)) +#define OLV_RESULT_INVALID_SIZE (OLV_RESULT_LVL6(209)) + +#define OLV_RESULT_BAD_VERSION (OLV_RESULT_STATUS(2001)) +#define OLV_RESULT_FAILED_REQUEST (OLV_RESULT_STATUS(2003)) +#define OLV_RESULT_INVALID_XML (OLV_RESULT_STATUS(2004)) +#define OLV_RESULT_INVALID_TEXT_FIELD (OLV_RESULT_STATUS(2006)) +#define OLV_RESULT_INVALID_INTEGER_FIELD (OLV_RESULT_STATUS(2007)) + +#define OLV_CLIENT_ID "87cd32617f1985439ea608c2746e4610" + +#define OLV_VERSION_MAJOR 5 +#define OLV_VERSION_MINOR 0 +#define OLV_VERSION_PATCH 3 + +namespace nn +{ + namespace olv + { + struct ParamPackStorage + { + uint64_t titleId; + uint32_t accessKey; + uint32_t platformId; + uint8_t regionId, languageId, countryId, areaId; + uint8_t networkRestriction, friendRestriction; + uint32_t ratingRestriction; + uint8_t ratingOrganization; + uint64_t transferableId; + char tzName[72]; + uint64_t utcOffset; + char encodedParamPack[512]; + }; + + struct DiscoveryResultStorage + { + sint32 has_error; + char serviceToken[512]; + char userAgent[64]; + char apiEndpoint[256]; + char portalEndpoint[256]; + }; + + extern ParamPackStorage g_ParamPack; + extern DiscoveryResultStorage g_DiscoveryResults; + extern uint32_t g_ReportTypes; + extern bool g_IsInitialized; + extern bool g_IsOnlineMode; + + static void InitializeOliveRequest(CurlRequestHelper& req) + { + req.addHeaderField("X-Nintendo-ServiceToken", g_DiscoveryResults.serviceToken); + req.addHeaderField("X-Nintendo-ParamPack", g_ParamPack.encodedParamPack); + curl_easy_setopt(req.getCURL(), CURLOPT_USERAGENT, g_DiscoveryResults.userAgent); + } + + static void appendQueryToURL(char* url, const char* query) + { + size_t urlLength = strlen(url); + size_t queryLength = strlen(query); + + char* delimiter = strchr(url, '?'); + if (delimiter) + snprintf(url + urlLength, queryLength + 2, "&%s", query); + else + snprintf(url + urlLength, queryLength + 2, "?%s", query); + } + + static sint32 CheckOliveResponse(pugi::xml_document& doc) + { + + /* + + 1 + 1 + 400 + 4 + SERVICE_CLOSED + + */ + + pugi::xml_node resultNode = doc.child("result"); + if (!resultNode) + { + cemuLog_log(LogType::Force, "Discovery response doesn't contain ..."); + return OLV_RESULT_INVALID_XML; + } + + std::string_view has_error = resultNode.child_value("has_error"); + std::string_view version = resultNode.child_value("version"); + std::string_view code = resultNode.child_value("code"); + std::string_view error_code = resultNode.child_value("error_code"); + + if (has_error.compare("1") == 0) + { + int codeVal = StringHelpers::ToInt(error_code, -1); + if (codeVal < 0) + { + codeVal = StringHelpers::ToInt(code, -1); + return OLV_RESULT_STATUS(codeVal + 4000); + } + return OLV_RESULT_STATUS(codeVal + 5000); + + } + + if (version.compare("1") != 0) + return OLV_RESULT_BAD_VERSION; // Version mismatch + + return OLV_RESULT_SUCCESS; + } + + sint32 olv_copy_wstr(char16_t* dest, const char16_t* src, uint32_t maxSize, uint32_t destSize); + size_t olv_wstrnlen(const char16_t* str, size_t max_len); + char16_t* olv_wstrncpy(char16_t* dest, const char16_t* src, size_t n); + +#pragma pack(push, 1) + struct TGAHeader + { + uint8 idLength; + uint8 colorMapType; + uint8 imageType; + uint16 first_entry_idx; + uint16 colormap_length; + uint8 bpp; + uint16 x_origin; + uint16 y_origin; + uint16 width; + uint16 height; + uint8 pixel_depth_bpp; + uint8 image_desc_bits; + }; +#pragma pack(pop) + static_assert(sizeof(nn::olv::TGAHeader) == 0x12, "sizeof(nn::olv::TGAHeader != 0x12"); + + enum TGACheckType : uint32 + { + CHECK_PAINTING = 0, + CHECK_COMMUNITY_ICON = 1, + CHECK_100x100_200x200 = 2 + }; + + + bool CheckTGA(const uint8* pTgaFile, uint32 pTgaFileLen, TGACheckType checkType); + sint32 DecodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType); + sint32 EncodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkTyp); + + bool CompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize); + bool DecompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize); + + + bool GetCommunityIdFromCode(uint32* pOutId, const char* pCode); + bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId); + + sint32 olv_curlformcode_to_error(CURLFORMcode code); + } +} diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp new file mode 100644 index 00000000..8df14ce0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp @@ -0,0 +1,231 @@ +#include "nn_olv_DownloadCommunityTypes.h" + +namespace nn +{ + namespace olv + { + + sint32 DownloadCommunityDataList_AsyncRequestImpl( + CurlRequestHelper& req, const char* reqUrl, + DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam); + + + sint32 DownloadCommunityDataList_AsyncRequest( + CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, + DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam + ) + { + sint32 res = DownloadCommunityDataList_AsyncRequestImpl(req, reqUrl, pOutList, pOutNum, numMaxList, pParam); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 DownloadCommunityDataList(DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam) + { + if (!g_IsInitialized) + return OLV_RESULT_NOT_INITIALIZED; + + if (!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pOutList || !pOutNum || !pParam) + return OLV_RESULT_INVALID_PTR; + + if (!numMaxList) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + for (int i = 0; i < numMaxList; i++) + DownloadedCommunityData::Clean(&pOutList[i]); + + char reqUrl[2048]; + sint32 res = pParam->GetRawDataUrl(reqUrl, sizeof(reqUrl)); + if (res < 0) + return res; + + CurlRequestHelper req; + req.initate(reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, DownloadCommunityDataList_AsyncRequest, + std::ref(req), reqUrl, requestDoneEvent.GetPointer(), pOutList, pOutNum, numMaxList, pParam); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 DownloadCommunityDataList_AsyncRequestImpl( + CurlRequestHelper& req, const char* reqUrl, + DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam + ) + { + + bool reqResult = req.submitRequest(); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + + + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, 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; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + std::string request_name = doc.select_single_node("//request_name").node().child_value(); + if (request_name.size() == 0) + { + cemuLog_log(LogType::Force, "Community download response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + if ((request_name.compare("communities") != 0) && (request_name.compare("specified_communities") != 0)) + { + cemuLog_log(LogType::Force, "Community download response isn't \"communities\" or \"specified_communities\""); + return OLV_RESULT_INVALID_XML; + } + + pugi::xml_node communities = doc.select_single_node("//communities").node(); + if (!communities) + { + cemuLog_log(LogType::Force, "Community download response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + int idx = 0; + for (pugi::xml_node communityNode : communities.children("community")) + { + if (idx >= numMaxList) + break; + + DownloadedCommunityData* pOutData = &pOutList[idx]; + + std::string_view app_data = communityNode.child_value("app_data"); + std::string_view community_id = communityNode.child_value("community_id"); + std::string_view name = communityNode.child_value("name"); + std::string_view description = communityNode.child_value("description"); + std::string_view pid = communityNode.child_value("pid"); + std::string_view icon = communityNode.child_value("icon"); + std::string_view mii = communityNode.child_value("mii"); + std::string_view screen_name = communityNode.child_value("screen_name"); + + if (app_data.size() != 0) + { + auto app_data_bin = NCrypto::base64Decode(app_data); + if (app_data_bin.size() != 0) + { + memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_APP_DATA; + pOutData->appDataLen = app_data_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); + if (community_id_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->communityId = community_id_val; + + if (name.size() != 0) + { + auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + if (name_utf16.size() != 0) + { + for (int i = 0; i < name_utf16.size(); i++) + pOutData->titleText[i] = name_utf16.at(i).bevalue(); + + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_TITLE_TEXT; + pOutData->titleTextMaxLen = name_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (description.size() != 0) + { + auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + if (description_utf16.size() != 0) + { + for (int i = 0; i < description_utf16.size(); i++) + pOutData->description[i] = description_utf16.at(i).bevalue(); + + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_DESC_TEXT; + pOutData->descriptionMaxLen = description_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 pid_val = StringHelpers::ToInt64(pid, -1); + if (pid_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->pid = pid_val; + + if (icon.size() != 0) + { + auto icon_bin = NCrypto::base64Decode(icon); + if (icon_bin.size() != 0) + { + memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_ICON_DATA; + pOutData->iconDataSize = icon_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (mii.size() != 0) + { + auto mii_bin = NCrypto::base64Decode(mii); + if (mii_bin.size() != 0) + { + memcpy(pOutData->miiFFLStoreData, mii_bin.data(), std::min(size_t(96), mii_bin.size())); + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_MII_DATA; + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (screen_name.size() != 0) + { + auto screen_name_utf16 = StringHelpers::FromUtf8(screen_name).substr(0, 32); + if (screen_name_utf16.size() != 0) + { + for (int i = 0; i < screen_name_utf16.size(); i++) + pOutData->miiDisplayName[i] = screen_name_utf16.at(i).bevalue(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + idx++; + + } + + *pOutNum = _swapEndianU32(idx); + sint32 res = OLV_RESULT_SUCCESS; + if (idx > 0) + res = 0; // nn_olv doesn't do it like that, but it's the same effect. I have no clue why it returns 0 when you have 1+ communities downloaded + + return res; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h new file mode 100644 index 00000000..3f5df35c --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h @@ -0,0 +1,530 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + + class DownloadedCommunityData + { + public: + static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); + static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); + static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); + static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); + static const inline uint32 FLAG_HAS_MII_DATA = (1 << 4); + + DownloadedCommunityData() + { + this->titleTextMaxLen = 0; + this->appDataLen = 0; + this->descriptionMaxLen = 0; + this->pid = 0; + this->communityId = 0; + this->flags = 0; + this->iconDataSize = 0; + this->miiDisplayName[0] = 0; + } + static DownloadedCommunityData* __ctor(DownloadedCommunityData* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) DownloadedCommunityData(); + } + + static DownloadedCommunityData* Clean(DownloadedCommunityData* data) + { + data->flags = 0; + data->communityId = 0; + data->pid = 0; + data->iconData[0] = 0; + data->titleTextMaxLen = 0; + data->appData[0] = 0; + data->appDataLen = 0; + data->description[0] = 0; + data->descriptionMaxLen = 0; + data->iconDataSize = 0; + data->titleText[0] = 0; + data->miiDisplayName[0] = 0; + return data; + } + + bool TestFlags(uint32 flags) const + { + return (this->flags & flags) != 0; + } + static bool __TestFlags(DownloadedCommunityData* _this, uint32 flags) + { + return _this->TestFlags(flags); + } + + uint32 GetCommunityId() const + { + return this->communityId; + } + static uint32 __GetCommunityId(DownloadedCommunityData* _this) + { + return _this->GetCommunityId(); + } + + sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize <= 12) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + uint32 len = 0; + if (FormatCommunityCode(pBuffer, &len, this->communityId)) + return OLV_RESULT_SUCCESS; + + return OLV_RESULT_INVALID_PARAMETER; + } + static sint32 __GetCommunityCode(DownloadedCommunityData* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetCommunityCode(pBuffer, bufferSize); + } + + uint32 GetOwnerPid() const + { + return this->pid; + } + static uint32 __GetOwnerPid(DownloadedCommunityData* _this) + { + return _this->GetOwnerPid(); + } + + sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + uint32 readSize = this->titleTextMaxLen; + if (numChars < readSize) + readSize = numChars; + + olv_wstrncpy(pBuffer, this->titleText, readSize); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetTitleText(DownloadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetTitleText(pBuffer, numChars); + } + + sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + olv_wstrncpy(pBuffer, this->description, numChars); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetDescriptionText(DownloadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetDescriptionText(pBuffer, numChars); + } + + sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + uint32 appDataSize = bufferSize; + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize) + { + if (!this->TestFlags(FLAG_HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (this->appDataLen < appDataSize) + appDataSize = this->appDataLen; + + memcpy(pBuffer, this->appData, appDataSize); + if (pOutSize) + *pOutSize = appDataSize; + + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetAppData(DownloadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetAppData(pBuffer, pOutSize, bufferSize); + } + + uint32 GetAppDataSize() const + { + if (this->TestFlags(FLAG_HAS_APP_DATA)) + return this->appDataLen; + + return 0; + } + static uint32 __GetAppDataSize(DownloadedCommunityData* _this) + { + return _this->GetAppDataSize(); + } + + sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize < sizeof(this->iconData)) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + if (!this->TestFlags(FLAG_HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + + sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); + if (decodeRes >= 0) + { + if (pOutSize) + *pOutSize = (uint32)decodeRes; + + return OLV_RESULT_SUCCESS; + } + + if (pOutSize) + *pOutSize = 0; + + if (decodeRes == -1) + cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); + else if (decodeRes == -2) + cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); + + return OLV_RESULT_INVALID_TEXT_FIELD; + } + static sint32 __GetIconData(DownloadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetIconData(pBuffer, pOutSize, bufferSize); + } + + sint32 GetOwnerMiiData(/* FFLStoreData* */void* pBuffer) const + { + if (!this->TestFlags(FLAG_HAS_MII_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + memcpy(pBuffer, this->miiFFLStoreData, sizeof(this->miiFFLStoreData)); + return OLV_RESULT_SUCCESS; + } + static sint32 __GetOwnerMiiData(DownloadedCommunityData* _this, /* FFLStoreData* */void* pBuffer) + { + return _this->GetOwnerMiiData(pBuffer); + } + + const char16_t* GetOwnerMiiNickname() const + { + if (this->miiDisplayName[0]) + return this->miiDisplayName; + + return nullptr; + } + static const char16_t* __GetOwnerMiiNickname(DownloadedCommunityData* _this) + { + return _this->GetOwnerMiiNickname(); + } + + public: + uint32be flags; + uint32be communityId; + uint32be pid; + char16_t titleText[128]; + uint32be titleTextMaxLen; + char16_t description[256]; + uint32be descriptionMaxLen; + uint8 appData[1024]; + uint32be appDataLen; + uint8 iconData[65580]; + uint32be iconDataSize; + uint8 miiFFLStoreData[96]; + char16_t miiDisplayName[32]; + uint8 unk[6168]; + }; + static_assert(sizeof(nn::olv::DownloadedCommunityData) == 0x12000, "sizeof(nn::olv::DownloadedCommunityData) != 0x12000"); + + class DownloadCommunityDataListParam + { + public: + static const inline uint32 FLAG_FILTER_FAVORITES = (1 << 0); + static const inline uint32 FLAG_FILTER_OFFICIALS = (1 << 1); + static const inline uint32 FLAG_FILTER_OWNED = (1 << 2); + static const inline uint32 FLAG_QUERY_MII_DATA = (1 << 3); + static const inline uint32 FLAG_QUERY_ICON_DATA = (1 << 4); + + DownloadCommunityDataListParam() + { + this->flags = 0; + this->communityDownloadLimit = 0; + this->communityId = 0; + + for (int i = 0; i < 20; i++) + this->additionalCommunityIdList[i] = -2; + } + static DownloadCommunityDataListParam* __ctor(DownloadCommunityDataListParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) DownloadCommunityDataListParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(DownloadCommunityDataListParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetCommunityId(uint32 communityId) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityId = communityId; + if (communityId) + { + if (!this->communityDownloadLimit) + this->communityDownloadLimit = 1; + } + + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(DownloadCommunityDataListParam* _this, uint32 communityId) + { + return _this->SetCommunityId(communityId); + } + + sint32 SetCommunityId(uint32 communityId, uint8 idx) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + if (idx >= 20) + return OLV_RESULT_INVALID_PARAMETER; + + this->additionalCommunityIdList[idx] = communityId; + int validIdsCount = 0; + for (int i = 0; i < 20; i++ ) + { + if (this->additionalCommunityIdList[i] != -2) + ++validIdsCount; + } + + if (validIdsCount > this->communityDownloadLimit) + this->communityDownloadLimit = validIdsCount; + + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(DownloadCommunityDataListParam* _this, uint32 communityId, uint8 idx) + { + return _this->SetCommunityId(communityId, idx); + } + + sint32 SetCommunityDataMaxNum(uint32 num) + { + if (!num) + return OLV_RESULT_INVALID_PARAMETER; + + int validIdsCount = 0; + for (int i = 0; i < 20; ++i) + { + if (this->additionalCommunityIdList[i] != -2) + ++validIdsCount; + } + + if (validIdsCount > num) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityDownloadLimit = num; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityDataMaxNum(DownloadCommunityDataListParam* _this, uint32 num) + { + return _this->SetCommunityDataMaxNum(num); + } + + sint32 GetRawDataUrl(char* pBuffer, uint32 bufferSize) const + { + if (!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (!bufferSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + char tmpFormatBuffer[64]; + char urlBuffer[1024]; + memset(urlBuffer, 0, sizeof(urlBuffer)); + + uint32 communityId; + int validIdsCount = 0; + for (int i = 0; i < 20; ++i) + { + if (this->additionalCommunityIdList[i] != -2) + { + communityId = this->additionalCommunityIdList[i]; + ++validIdsCount; + } + } + + if (validIdsCount) + { + if (this->communityId && this->communityId != -2) + return OLV_RESULT_INVALID_PARAMETER; + + uint32 unkFlag = this->flags & 0xFFFFFFE7; + if (unkFlag) + return OLV_RESULT_INVALID_PARAMETER; + + // It's how it's done in the real nn_olv, what even the fuck is this, never seen used yet. + snprintf(urlBuffer, sizeof(urlBuffer), "%s/v1/communities/%u.search", g_DiscoveryResults.apiEndpoint, communityId); + + for (int i = 0; i < 20; ++i) + { + if (this->additionalCommunityIdList[i] != -2) + { + snprintf(tmpFormatBuffer, 64, "community_id=%u", this->additionalCommunityIdList[i].value()); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + ++unkFlag; + } + } + } + else + snprintf(urlBuffer, sizeof(urlBuffer), "%s/v1/communities", g_DiscoveryResults.apiEndpoint); + + if (this->communityId) + { + snprintf(tmpFormatBuffer, 64, "community_id=%u", this->communityId.value()); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + else + { + uint32 filterBy_favorite = (this->flags & FLAG_FILTER_FAVORITES) != 0; + uint32 filterBy_official = (this->flags & FLAG_FILTER_OFFICIALS) != 0; + uint32 filterBy_selfmade = (this->flags & FLAG_FILTER_OWNED) != 0; + + if ((filterBy_favorite + filterBy_official + filterBy_selfmade) != 1) + return OLV_RESULT_INVALID_PARAMETER; + + snprintf(tmpFormatBuffer, 64, "limit=%u", this->communityDownloadLimit.value()); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + + if (filterBy_favorite) + { + strncpy(tmpFormatBuffer, "type=favorite", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + else if (filterBy_official) + { + strncpy(tmpFormatBuffer, "type=official", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + else + { + strncpy(tmpFormatBuffer, "type=my", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + } + + if (this->flags & FLAG_QUERY_MII_DATA) + { + strncpy(tmpFormatBuffer, "with_mii=1", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + + if (this->flags & FLAG_QUERY_ICON_DATA) + { + strncpy(tmpFormatBuffer, "with_icon=1", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + + int res = snprintf(pBuffer, bufferSize, "%s", urlBuffer); + if (res < 0) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + return OLV_RESULT_SUCCESS; + } + static sint32 __GetRawDataUrl(DownloadCommunityDataListParam* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetRawDataUrl(pBuffer, bufferSize); + } + + public: + uint32be flags; + uint32be communityId; + uint32be communityDownloadLimit; + uint32be additionalCommunityIdList[20]; // Additional community ID filter list + uint8 unk[4004]; // Looks unused lol, probably reserved data + }; + static_assert(sizeof(nn::olv::DownloadCommunityDataListParam) == 0x1000, "sizeof(nn::olv::DownloadCommunityDataListParam) != 0x1000"); + + sint32 DownloadCommunityDataList(DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam); + + static void loadOliveDownloadCommunityTypes() + { + cafeExportRegisterFunc(DownloadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv23DownloadedCommunityDataFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiData, "nn_olv", "GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiNickname, "nn_olv", "GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + + cafeExportRegisterFunc(DownloadCommunityDataListParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv30DownloadCommunityDataListParamFv", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetCommunityDataMaxNum, "nn_olv", "SetCommunityDataMaxNum__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv30DownloadCommunityDataListParamCFPcUi", LogType::None); + + cafeExportRegisterFunc((sint32 (*)(DownloadCommunityDataListParam*, uint32))DownloadCommunityDataListParam::__SetCommunityId, + "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + cafeExportRegisterFunc((sint32(*)(DownloadCommunityDataListParam*, uint32, uint8))DownloadCommunityDataListParam::__SetCommunityId, + "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUiUc", LogType::None); + + cafeExportRegisterFunc(DownloadCommunityDataList, "nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", LogType::None); + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp new file mode 100644 index 00000000..62a2654d --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp @@ -0,0 +1,294 @@ +#pragma once + +#include "nn_olv_InitializeTypes.h" +#include "CafeSystem.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include + +namespace nn +{ + namespace olv + { + + uint32_t g_ReportTypes = 0; + bool g_IsOnlineMode = false; + bool g_IsInitialized = false; + ParamPackStorage g_ParamPack; + DiscoveryResultStorage g_DiscoveryResults; + + sint32 GetOlvAccessKey(uint32_t* pOutKey) + { + *pOutKey = 0; + uint32_t accessKey = CafeSystem::GetForegroundTitleOlvAccesskey(); + if (accessKey == -1) + return OLV_RESULT_STATUS(1102); + + *pOutKey = accessKey; + return OLV_RESULT_SUCCESS; + } + + sint32 CreateParamPack(uint64_t titleId, uint32_t accessKey) + { + g_ParamPack.languageId = uint8(GetConfig().console_language.GetValue()); + + uint32be simpleAddress = 0; + nn::act::GetSimpleAddressIdEx(&simpleAddress, nn::act::ACT_SLOT_CURRENT); + uint32 countryCode = nn::act::getCountryCodeFromSimpleAddress(simpleAddress); + + g_ParamPack.countryId = countryCode; + g_ParamPack.titleId = titleId; + g_ParamPack.platformId = 1; + + g_ParamPack.areaId = (simpleAddress >> 8) & 0xff; + + MCPHANDLE handle = MCP_Open(); + SysProdSettings sysProdSettings; + MCP_GetSysProdSettings(handle, &sysProdSettings); + MCP_Close(handle); + + g_ParamPack.regionId = sysProdSettings.platformRegion; + g_ParamPack.accessKey = accessKey; + + g_ParamPack.networkRestriction = 0; + g_ParamPack.friendRestriction = 0; + g_ParamPack.ratingRestriction = 18; + g_ParamPack.ratingOrganization = 4; // PEGI ? + + uint64 transferrableId; + nn::act::GetTransferableIdEx(&transferrableId, (titleId >> 8) & 0xFFFFF, nn::act::ACT_SLOT_CURRENT); + g_ParamPack.transferableId = transferrableId; + + strcpy(g_ParamPack.tzName, "CEMU/Olive"); // Should be nn::act::GetTimeZoneId + g_ParamPack.utcOffset = (uint64_t)nn::act::GetUtcOffset() / 1'000'000; + + char paramPackStr[1024]; + snprintf( + paramPackStr, + sizeof(paramPackStr), + "\\%s\\%llu\\%s\\%u\\%s\\%u\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%u\\%s\\%d\\%s\\%llu\\" + "%s\\%s\\%s\\%lld\\", + "title_id", + g_ParamPack.titleId, + "access_key", + g_ParamPack.accessKey, + "platform_id", + g_ParamPack.platformId, + "region_id", + g_ParamPack.regionId, + "language_id", + g_ParamPack.languageId, + "country_id", + g_ParamPack.countryId, + "area_id", + g_ParamPack.areaId, + "network_restriction", + g_ParamPack.networkRestriction, + "friend_restriction", + g_ParamPack.friendRestriction, + "rating_restriction", + g_ParamPack.ratingRestriction, + "rating_organization", + g_ParamPack.ratingOrganization, + "transferable_id", + g_ParamPack.transferableId, + "tz_name", + g_ParamPack.tzName, + "utc_offset", + g_ParamPack.utcOffset); + std::string encodedParamPack = NCrypto::base64Encode(paramPackStr, strnlen(paramPackStr, 1024)); + memset(&g_ParamPack.encodedParamPack, 0, sizeof(g_ParamPack.encodedParamPack)); + memcpy(&g_ParamPack.encodedParamPack, encodedParamPack.data(), encodedParamPack.size()); + + return OLV_RESULT_SUCCESS; + } + + sint32 MakeDiscoveryRequest_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl) + { + bool reqResult = req.submitRequest(); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, 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 discovery service response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + + /* + + 0 + 1 + + api.olv.pretendo.cc + api.olv.pretendo.cc + portal.olv.pretendo.cc + ctr.olv.pretendo.cc + + + */ + + pugi::xml_node resultNode = doc.child("result"); + if (!resultNode) + { + cemuLog_log(LogType::Force, "Discovery response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + pugi::xml_node endpointNode = resultNode.child("endpoint"); + if (!endpointNode) + { + cemuLog_log(LogType::Force, "Discovery response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + // Yes it only uses and + std::string_view host = endpointNode.child_value("host"); + std::string_view portal_host = endpointNode.child_value("portal_host"); + + snprintf(g_DiscoveryResults.apiEndpoint, sizeof(g_DiscoveryResults.apiEndpoint), "https://%s", host.data()); + snprintf(g_DiscoveryResults.portalEndpoint, sizeof(g_DiscoveryResults.portalEndpoint), "https://%s", portal_host.data()); + + return OLV_RESULT_SUCCESS; + } + + sint32 MakeDiscoveryRequest_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent) + { + sint32 res = MakeDiscoveryRequest_AsyncRequestImpl(req, reqUrl); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 MakeDiscoveryRequest() + { + // ============================================================================= + // Discovery request | https://discovery.olv.nintendo.net/v1/endpoint + // ============================================================================= + + CurlRequestHelper req; + std::string requestUrl; + switch (ActiveSettings::GetNetworkService()) + { + case NetworkService::Pretendo: + requestUrl = PretendoURLs::OLVURL; + break; + case NetworkService::Custom: + requestUrl = GetNetworkConfig().urls.OLV.GetValue(); + break; + case NetworkService::Nintendo: + default: + requestUrl = NintendoURLs::OLVURL; + break; + } + + req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, MakeDiscoveryRequest_AsyncRequest, std::ref(req), requestUrl.c_str(), requestDoneEvent.GetPointer()); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 Initialize(nn::olv::InitializeParam* pParam) + { + if (g_IsInitialized) + return OLV_RESULT_ALREADY_INITIALIZED; + + if (!pParam->m_Work) + { + g_IsInitialized = false; + return OLV_RESULT_INVALID_PTR; + } + + if (pParam->m_WorkSize < 0x10000) + { + g_IsInitialized = false; + return OLV_RESULT_INVALID_SIZE; + } + + uint32_t accessKey; + int32_t olvAccessKeyStatus = GetOlvAccessKey(&accessKey); + if (olvAccessKeyStatus < 0) + { + g_IsInitialized = false; + return olvAccessKeyStatus; + } + + uint64_t tid = CafeSystem::GetForegroundTitleId(); + int32_t createParamPackResult = CreateParamPack(tid, accessKey); + if (createParamPackResult < 0) + { + g_IsInitialized = false; + return createParamPackResult; + } + + g_IsInitialized = true; + + if ((pParam->m_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0) + { + + g_IsOnlineMode = true; + + independentServiceToken_t token; + sint32 res = (sint32)nn::act::AcquireIndependentServiceToken(&token, OLV_CLIENT_ID, 0); + if (res < 0) + { + g_IsInitialized = false; + return res; + } + + // Assuming we're always a production WiiU (non-dev) + uint32 uniqueId = (CafeSystem::GetForegroundTitleId() >> 8) & 0xFFFFF; + + char versionBuffer[32]; + snprintf(versionBuffer, sizeof(versionBuffer), "%d.%d.%d", OLV_VERSION_MAJOR, OLV_VERSION_MINOR, OLV_VERSION_PATCH); + snprintf(g_DiscoveryResults.userAgent, sizeof(g_DiscoveryResults.userAgent), "%s/%s-%s/%d", "WiiU", "POLV", versionBuffer, uniqueId); + + memcpy(g_DiscoveryResults.serviceToken, token.token, sizeof(g_DiscoveryResults.serviceToken)); + + sint32 discoveryRes = MakeDiscoveryRequest(); + if (discoveryRes < 0) + g_IsInitialized = false; + + return discoveryRes; + } + + return OLV_RESULT_SUCCESS; + } + + namespace Report + { + uint32 GetReportTypes() + { + return g_ReportTypes; + } + + void SetReportTypes(uint32 reportTypes) + { + g_ReportTypes = reportTypes | 0x1000; + } + } + + bool IsInitialized() + { + return g_IsInitialized; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h new file mode 100644 index 00000000..603b167c --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h @@ -0,0 +1,128 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" +#include "Cafe/OS/libs/coreinit/coreinit_MCP.h" + + +namespace nn +{ + namespace olv + { + + class InitializeParam + { + public: + static const inline uint32 FLAG_OFFLINE_MODE = (1 << 0); + + InitializeParam() + { + this->m_Flags = 0; + this->m_ReportTypes = 7039; + this->m_SysArgsSize = 0; + this->m_Work = MEMPTR(nullptr); + this->m_SysArgs = MEMPTR(nullptr); + this->m_WorkSize = 0; + } + static InitializeParam* __ctor(InitializeParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) InitializeParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->m_Flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(InitializeParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetWork(MEMPTR pWorkData, uint32 workDataSize) + { + if (!pWorkData) + return OLV_RESULT_INVALID_PTR; + if (workDataSize < 0x10000) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + this->m_Work = pWorkData; + this->m_WorkSize = workDataSize; + return OLV_RESULT_SUCCESS; + } + static uint32 __SetWork(InitializeParam* _this, MEMPTR pWorkData, uint32 workDataSize) + { + return _this->SetWork(pWorkData, workDataSize); + } + + sint32 SetReportTypes(uint32 reportTypes) + { + this->m_ReportTypes = reportTypes; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetReportTypes(InitializeParam* _this, uint32 reportTypes) + { + return _this->SetReportTypes(reportTypes); + } + + sint32 SetSysArgs(MEMPTR pSysArgs, uint32 sysArgsSize) + { + if (!pSysArgs) + return OLV_RESULT_INVALID_PTR; + + if (!sysArgsSize) + return OLV_RESULT_INVALID_PARAMETER; + + this->m_SysArgs = pSysArgs; + this->m_SysArgsSize = sysArgsSize; + + return OLV_RESULT_SUCCESS; + } + static sint32 __SetSysArgs(InitializeParam* _this, MEMPTR pSysArgs, uint32 sysArgsSize) + { + return _this->SetSysArgs(pSysArgs, sysArgsSize); + } + + uint32be m_Flags; + uint32be m_ReportTypes; + MEMPTR m_Work; + uint32be m_WorkSize; + MEMPTR m_SysArgs; + uint32be m_SysArgsSize; + char unk[0x28]; + }; + static_assert(sizeof(nn::olv::InitializeParam) == 0x40, "sizeof(nn::olv::InitializeParam) != 0x40"); + + + namespace Report + { + uint32 GetReportTypes(); + void SetReportTypes(uint32 reportTypes); + } + + bool IsInitialized(); + sint32 Initialize(nn::olv::InitializeParam* pParam); + + static void loadOliveInitializeTypes() + { + cafeExportRegisterFunc(Initialize, "nn_olv", "Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam", LogType::None); + cafeExportRegisterFunc(IsInitialized, "nn_olv", "IsInitialized__Q2_2nn3olvFv", LogType::None); + cafeExportRegisterFunc(Report::GetReportTypes, "nn_olv", "GetReportTypes__Q3_2nn3olv6ReportFv", LogType::None); + cafeExportRegisterFunc(Report::SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv6ReportFUi", LogType::None); + + cafeExportRegisterFunc(InitializeParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv15InitializeParamFv", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv15InitializeParamFUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetWork, "nn_olv", "SetWork__Q3_2nn3olv15InitializeParamFPUcUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv15InitializeParamFUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetSysArgs, "nn_olv", "SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", LogType::None); + } + } +} \ 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..5056f2fe --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp @@ -0,0 +1,311 @@ +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" +#include "nn_olv_PostTypes.h" +#include "Cemu/ncrypto/ncrypto.h" // for base64 decoder +#include "util/helpers/helpers.h" +#include +#include + +namespace nn +{ + namespace olv + { + + template + uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) + { + if(unescape) + { + // todo + } + std::wstring ws = boost::nowide::widen(sv); + size_t copyLen = std::min(TLength-1, ws.size()); + for(size_t i=0; i 0) + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT); + } + if(tokenNode = xmlNode.child("feeling_id"); tokenNode) + { + obj.feeling = ConvertString(tokenNode.child_value()); + if(obj.feeling < 0 || obj.feeling >= 5) + { + cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: feeling_id out of range"); + return false; + } + } + if(tokenNode = xmlNode.child("id"); tokenNode) + { + std::string_view id_sv = tokenNode.child_value(); + if(id_sv.size() > 22) + { + cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: id too long"); + return false; + } + memcpy(obj.postId, id_sv.data(), id_sv.size()); + obj.postId[id_sv.size()] = '\0'; + } + if(tokenNode = xmlNode.child("is_autopost"); tokenNode) + { + uint8 isAutopost = ConvertString(tokenNode.child_value()); + if(isAutopost == 1) + obj.SetFlag(DownloadedDataBase::FLAGS::IS_AUTOPOST); + else if(isAutopost == 0) + obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST); + else + { + cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: is_autopost has invalid value"); + return false; + } + } + if(tokenNode = xmlNode.child("empathy_added"); tokenNode) + { + if(ConvertString(tokenNode.child_value()) > 0) + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_EMPATHY_ADDED); + } + if(tokenNode = xmlNode.child("is_spoiler"); tokenNode) + { + if(ConvertString(tokenNode.child_value()) > 0) + obj.SetFlag(DownloadedDataBase::FLAGS::IS_SPOILER); + } + if(tokenNode = xmlNode.child("mii"); tokenNode) + { + std::vector miiData = NCrypto::base64Decode(tokenNode.child_value()); + if(miiData.size() != 96) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicData mii data is not valid (incorrect size)"); + return false; + } + memcpy(obj.miiData, miiData.data(), miiData.size()); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_MII_DATA); + } + if(tokenNode = xmlNode.child("pid"); tokenNode) + { + obj.userPid = ConvertString(tokenNode.child_value()); + } + if(tokenNode = xmlNode.child("screen_name"); tokenNode) + { + SetStringUC2(obj.miiNickname, tokenNode.child_value(), true); + } + if(tokenNode = xmlNode.child("region_id"); tokenNode) + { + obj.regionId = ConvertString(tokenNode.child_value()); + } + if(tokenNode = xmlNode.child("platform_id"); tokenNode) + { + obj.platformId = ConvertString(tokenNode.child_value()); + } + if(tokenNode = xmlNode.child("language_id"); tokenNode) + { + obj.languageId = ConvertString(tokenNode.child_value()); + } + if(tokenNode = xmlNode.child("country_id"); tokenNode) + { + obj.countryId = ConvertString(tokenNode.child_value()); + } + return true; + } + + bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode) + { + pugi::xml_node tokenNode; + if(tokenNode = xmlNode.child("community_id"); tokenNode) + obj.communityId = ConvertString(tokenNode.child_value()); + if(tokenNode = xmlNode.child("empathy_count"); tokenNode) + obj.empathyCount = ConvertString(tokenNode.child_value()); + if(tokenNode = xmlNode.child("reply_count"); tokenNode) + obj.commentCount = ConvertString(tokenNode.child_value()); + return ParseXml_DownloadedDataBase(obj.downloadedDataBase, xmlNode); + } + + bool ParseXML_DownloadedSystemPostData(hidden::DownloadedSystemPostData& obj, pugi::xml_node& xmlNode) + { + pugi::xml_node tokenNode; + if(tokenNode = xmlNode.child("title_id"); tokenNode) + obj.titleId = ConvertString(tokenNode.child_value()); + return ParseXML_DownloadedPostData(obj.downloadedPostData, xmlNode); + } + + bool ParseXML_DownloadedTopicData(DownloadedTopicData& obj, pugi::xml_node& xmlNode) + { + pugi::xml_node tokenNode; + if(tokenNode = xmlNode.child("community_id"); tokenNode) + obj.communityId = ConvertString(tokenNode.child_value()); + return true; + } + + bool Parse_DownloadedSystemTopicData(hidden::DownloadedSystemTopicData& obj, pugi::xml_node& xmlNode) + { + if(!ParseXML_DownloadedTopicData(obj.downloadedTopicData, xmlNode)) + return false; + pugi::xml_node tokenNode; + if(tokenNode = xmlNode.child("name"); tokenNode) + { + SetStringUC2(obj.titleText, tokenNode.child_value(), true); + obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::HAS_TITLE); + } + if(tokenNode = xmlNode.child("is_recommended"); tokenNode) + { + uint32 isRecommended = ConvertString(tokenNode.child_value()); + if(isRecommended != 0) + obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::IS_RECOMMENDED); + } + if(tokenNode = xmlNode.child("title_id"); tokenNode) + { + obj.titleId = ConvertString(tokenNode.child_value()); + } + if(tokenNode = xmlNode.child("title_ids"); tokenNode) + { + cemu_assert_unimplemented(); + } + if(tokenNode = xmlNode.child("icon"); tokenNode) + { + std::vector iconData = NCrypto::base64Decode(tokenNode.child_value()); + if(iconData.size() > sizeof(obj.iconData)) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicData icon data is not valid"); + return false; + } + obj.iconDataSize = iconData.size(); + memcpy(obj.iconData, iconData.data(), iconData.size()); + obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::HAS_ICON_DATA); + } + return true; + } + + uint32 GetSystemTopicDataListFromRawData(hidden::DownloadedSystemTopicDataList* downloadedSystemTopicDataList, hidden::DownloadedSystemPostData* downloadedSystemPostData, uint32be* postCountOut, uint32 postCountMax, void* xmlData, uint32 xmlDataSize) + { + // copy xmlData into a temporary buffer since load_buffer_inplace will modify it + std::vector buffer; + buffer.resize(xmlDataSize); + memcpy(buffer.data(), xmlData, xmlDataSize); + pugi::xml_document doc; + if (!doc.load_buffer_inplace(buffer.data(), xmlDataSize, pugi::parse_default, pugi::xml_encoding::encoding_utf8)) + return -1; + + memset(downloadedSystemTopicDataList, 0, sizeof(hidden::DownloadedSystemTopicDataList)); + downloadedSystemTopicDataList->topicDataNum = 0; + + cemu_assert_debug(doc.child("result").child("topics")); + + size_t postCount = 0; + + // parse topics + for (pugi::xml_node topicsChildNode : doc.child("result").child("topics").children()) + { + const char* name = topicsChildNode.name(); + cemuLog_logDebug(LogType::Force, "topicsChildNode.name() = {}", name); + if (strcmp(topicsChildNode.name(), "topic")) + continue; + // parse topic + if(downloadedSystemTopicDataList->topicDataNum > 10) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicDataList exceeded maximum topic count (10)"); + return false; + } + auto& topicEntry = downloadedSystemTopicDataList->topicData[downloadedSystemTopicDataList->topicDataNum]; + memset(&topicEntry, 0, sizeof(hidden::DownloadedSystemTopicDataList::DownloadedSystemTopicWrapped)); + Parse_DownloadedSystemTopicData(topicEntry.downloadedSystemTopicData, topicsChildNode); + downloadedSystemTopicDataList->topicDataNum = downloadedSystemTopicDataList->topicDataNum + 1; + + topicEntry.postDataNum = 0; + // parse all posts within the current topic + for (pugi::xml_node personNode : topicsChildNode.child("people").children("person")) + { + for (pugi::xml_node postNode : personNode.child("posts").children("post")) + { + if(postCount >= postCountMax) + { + cemuLog_log(LogType::Force, "[Olive-XML] GetSystemTopicDataListFromRawData exceeded maximum post count"); + return false; + } + auto& postEntry = downloadedSystemPostData[postCount]; + memset(&postEntry, 0, sizeof(hidden::DownloadedSystemPostData)); + bool r = ParseXML_DownloadedSystemPostData(postEntry, postNode); + if(!r) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemPostData parsing failed"); + return false; + } + postCount++; + // add post to topic + if(topicEntry.postDataNum >= hidden::DownloadedSystemTopicDataList::MAX_POSTS_PER_TOPIC) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicDataList has too many posts for a single topic (up to {})", hidden::DownloadedSystemTopicDataList::MAX_POSTS_PER_TOPIC); + return false; + } + topicEntry.postDataList[topicEntry.postDataNum] = &postEntry; + topicEntry.postDataNum = topicEntry.postDataNum + 1; + } + } + } + *postCountOut = postCount; + return 0; + } + + void loadOlivePostAndTopicTypes() + { + cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::None); + + // DownloadedDataBase getters + cafeExportRegisterFunc(DownloadedDataBase::TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetUserPid, "nn_olv", "GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetPostDate, "nn_olv", "GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetFeeling, "nn_olv", "GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetRegionId, "nn_olv", "GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetPlatformId, "nn_olv", "GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetLanguageId, "nn_olv", "GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetCountryId, "nn_olv", "GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetExternalUrl, "nn_olv", "GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetMiiData1, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetMiiNickname, "nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetBodyText, "nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetBodyMemo, "nn_olv", "GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetTopicTag, "nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + + // DownloadedPostData getters + cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedPostData::GetEmpathyCount, "nn_olv", "GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedPostData::GetCommentCount, "nn_olv", "GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedPostData::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); + + // DownloadedSystemPostData getters + cafeExportRegisterFunc(hidden::DownloadedSystemPostData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv", LogType::None); + + // DownloadedTopicData getters + cafeExportRegisterFunc(DownloadedTopicData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv", LogType::None); + + // DownloadedSystemTopicData getters + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::TestFlags, "nn_olv", "TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIdNum, "nn_olv", "GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleText, "nn_olv", "GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIconData, "nn_olv", "GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi", LogType::None); + + // DownloadedSystemTopicDataList getters + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicDataNum, "nn_olv", "GetDownloadedSystemTopicDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFv", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostDataNum, "nn_olv", "GetDownloadedSystemPostDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::None); + + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h new file mode 100644 index 00000000..3ca4f87e --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h @@ -0,0 +1,430 @@ +#pragma once +#include + +namespace nn +{ + namespace olv + { + struct DownloadedDataBase + { + enum class FLAGS : uint32 + { + HAS_BODY_TEXT = 0x01, + HAS_BODY_MEMO = 0x02, + HAS_EXTERNAL_IMAGE = 0x04, + HAS_EXTERNAL_BINARY_DATA = 0x08, + HAS_MII_DATA = 0x10, + HAS_EXTERNAL_URL = 0x20, + HAS_APP_DATA = 0x40, + HAS_EMPATHY_ADDED = 0x80, + IS_AUTOPOST = 0x100, + IS_SPOILER = 0x200, + IS_NOT_AUTOPOST = 0x400, // autopost flag was explicitly set to false + }; + + void Reset() + { + memset(this, 0, sizeof(DownloadedDataBase)); + } + + void SetFlag(FLAGS flag) + { + flags = (FLAGS)((uint32)flags.value() | (uint32)flag); + } + + betype flags; + uint32be userPid; + uint8be postId[32]; // string, up to 22 characters but the buffer is 32 bytes + uint64be postDate; + sint8be feeling; + uint8be _padding0031[3]; + uint32be regionId; + uint8be platformId; + uint8be languageId; + uint8be countryId; + uint8be _padding003B; + uint16be bodyText[256]; + uint32be bodyTextLength; + uint8be compressedMemoBody[40960]; + uint32be compressedMemoBodySize; + uint16be topicTag[152]; + uint8be appData[1024]; + uint32be appDataLength; + uint8be externalBinaryUrl[256]; + uint32be externalBinaryDataSize; + uint8be externalImageDataUrl[256]; + uint32be externalImageDataSize; + uint8be externalURL[256]; + uint8be miiData[96]; + uint16be miiNickname[16]; + uint32be _paddingAB00[1344]; + uint32be uknC000_someVTableMaybe; + uint32be uknC004; + + // getters + // TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi + static bool TestFlags(DownloadedDataBase* _this, DownloadedDataBase::FLAGS flag) + { + return HAS_FLAG((uint32)_this->flags.value(), (uint32)flag); + } + + // GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetUserPid(DownloadedDataBase* _this) + { + return _this->userPid; + } + + // GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv + static uint64 GetPostDate(DownloadedDataBase* _this) + { + return _this->postDate; + } + + // GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetFeeling(DownloadedDataBase* _this) + { + if(_this->feeling >= 6) + return 0; + return _this->feeling; + } + + // GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetRegionId(DownloadedDataBase* _this) + { + return _this->regionId; + } + + // GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetPlatformId(DownloadedDataBase* _this) + { + return _this->platformId; + } + + // GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetLanguageId(DownloadedDataBase* _this) + { + return _this->languageId; + } + + // GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetCountryId(DownloadedDataBase* _this) + { + return _this->countryId; + } + + // GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv + static uint8be* GetExternalUrl(DownloadedDataBase* _this) + { + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_URL)) + return nullptr; + return _this->externalURL; + } + + // GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData + static nnResult GetMiiData1(DownloadedDataBase* _this, void* miiDataOut) + { + if (!TestFlags(_this, FLAGS::HAS_MII_DATA)) + return OLV_RESULT_MISSING_DATA; + if (!miiDataOut) + return OLV_RESULT_INVALID_PTR; + memcpy(miiDataOut, _this->miiData, 96); + return OLV_RESULT_SUCCESS; + } + + // GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv + static uint8be* GetMiiData2(DownloadedDataBase* _this) + { + if (!TestFlags(_this, FLAGS::HAS_MII_DATA)) + return nullptr; + return _this->miiData; + } + + // GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv + static uint16be* GetMiiNickname(DownloadedDataBase* _this) + { + if (_this->miiNickname[0] == 0) + return nullptr; + return _this->miiNickname; + } + + // GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi + static nnResult GetBodyText(DownloadedDataBase* _this, uint16be* bodyTextOut, uint32 maxLength) + { + if (!bodyTextOut) + return OLV_RESULT_INVALID_PTR; + if (maxLength == 0) + return OLV_RESULT_NOT_ENOUGH_SIZE; + uint32 outputLength = std::min(_this->bodyTextLength, maxLength); + olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, _this->bodyTextLength); + return OLV_RESULT_SUCCESS; + } + + // GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi + static nnResult GetBodyMemo(DownloadedDataBase* _this, uint8be* bodyMemoOut, uint32* bodyMemoSizeOut, uint32 maxSize) + { + if (!bodyMemoOut) + return OLV_RESULT_INVALID_PTR; + if (maxSize < 0x2582C) + return OLV_RESULT_NOT_ENOUGH_SIZE; + if (!TestFlags(_this, FLAGS::HAS_BODY_MEMO)) + return OLV_RESULT_MISSING_DATA; + // uncompress TGA + uLongf decompressedSize = maxSize; + if (uncompress((uint8*)bodyMemoOut, &decompressedSize, (uint8*)_this->compressedMemoBody, _this->compressedMemoBodySize) != Z_OK) + { + cemuLog_log(LogType::Force, "DownloadedSystemTopicData::GetTitleIconData: uncompress failed"); + return OLV_RESULT_INVALID_TEXT_FIELD; // status + } + if(bodyMemoSizeOut) + *bodyMemoSizeOut = decompressedSize; + // todo - verify TGA header + return OLV_RESULT_SUCCESS; + } + + // GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv + static uint16be* GetTopicTag(DownloadedDataBase* _this) + { + return _this->topicTag; + } + + // GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi + static nnResult GetAppData(DownloadedDataBase* _this, uint8be* appDataOut, uint32* appDataSizeOut, uint32 maxSize) + { + if (!appDataOut) + return OLV_RESULT_INVALID_PTR; + if (!TestFlags(_this, FLAGS::HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + uint32 outputSize = std::min(maxSize, _this->appDataLength); + memcpy(appDataOut, _this->appData, outputSize); + if(appDataSizeOut) + *appDataSizeOut = outputSize; + return OLV_RESULT_SUCCESS; + } + + // GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetAppDataSize(DownloadedDataBase* _this) + { + return _this->appDataLength; + } + + // GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv + static uint8be* GetPostId(DownloadedDataBase* _this) + { + return _this->postId; + } + + // todo: + // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi + // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + // DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi + // GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + }; + + static_assert(sizeof(DownloadedDataBase) == 0xC008); + + struct DownloadedPostData + { + DownloadedDataBase downloadedDataBase; + uint32be communityId; + uint32be empathyCount; + uint32be commentCount; + uint32be paddingC014[125]; // probably unused? + + // getters + // GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv + static uint32 GetCommunityId(DownloadedPostData* _this) + { + return _this->communityId; + } + + // GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv + static uint32 GetEmpathyCount(DownloadedPostData* _this) + { + return _this->empathyCount; + } + + // GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv + static uint32 GetCommentCount(DownloadedPostData* _this) + { + return _this->commentCount; + } + + // GetPostId__Q3_2nn3olv18DownloadedPostDataCFv + static uint8be* GetPostId(DownloadedPostData* _this) + { + return _this->downloadedDataBase.postId; + } + + }; + + static_assert(sizeof(DownloadedPostData) == 0xC208); + + struct DownloadedTopicData + { + enum class FLAGS + { + IS_RECOMMENDED = 0x01, + HAS_TITLE = 0x02, + HAS_ICON_DATA = 0x04, + }; + betype flags; + uint32be communityId; + int ukn[1022]; + + void SetFlag(FLAGS flag) + { + flags = (FLAGS)((uint32)flags.value() | (uint32)flag); + } + + // GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv + static uint32 GetCommunityId(DownloadedTopicData* _this) + { + return _this->communityId; + } + }; + + static_assert(sizeof(DownloadedTopicData) == 0x1000); + + namespace hidden + { + struct DownloadedSystemPostData + { + DownloadedPostData downloadedPostData; + uint64be titleId; + uint32be uknC210[124]; + uint32be uknC400; + uint32be uknC404; + + // getters + // GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv + static uint64 GetTitleId(DownloadedSystemPostData* _this) + { + return _this->titleId; + } + }; + + static_assert(sizeof(DownloadedSystemPostData) == 0xC408); + + struct DownloadedSystemTopicData + { + DownloadedTopicData downloadedTopicData; + uint64be titleId; + uint16be titleText[128]; + uint8be ukn1108[256]; + uint8be iconData[0x1002C]; + uint32be iconDataSize; + uint64be titleIds[32]; + uint32be titleIdsCount; + uint32be ukn1133C[1841]; + + // implement getters as static methods for compatibility with CafeExportRegisterFunc() + // TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi + static bool TestFlags(DownloadedSystemTopicData* _this, DownloadedTopicData::FLAGS flag) + { + return HAS_FLAG((uint32)_this->downloadedTopicData.flags.value(), (uint32)flag); + } + + // GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv + static uint64 GetTitleId(DownloadedSystemTopicData* _this) + { + return _this->titleId; + } + + // GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv + static uint32 GetTitleIdNum(DownloadedSystemTopicData* _this) + { + return _this->titleIdsCount; + } + + // GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi + static nnResult GetTitleText(DownloadedSystemTopicData* _this, uint16be* titleTextOut, uint32 maxLength) + { + if (!TestFlags(_this, DownloadedTopicData::FLAGS::HAS_TITLE)) + return OLV_RESULT_MISSING_DATA; + if (!titleTextOut) + return OLV_RESULT_INVALID_PTR; + memset(titleTextOut, 0, maxLength * sizeof(uint16be)); + if (maxLength > 128) + maxLength = 128; + olv_wstrncpy((char16_t*)titleTextOut, (char16_t*)_this->titleText, maxLength); + return OLV_RESULT_SUCCESS; + } + + // GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi + static nnResult GetTitleIconData(DownloadedSystemTopicData* _this, void* iconDataOut, uint32be* iconSizeOut, uint32 iconDataMaxSize) + { + if (!TestFlags(_this, DownloadedTopicData::FLAGS::HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + if (!iconDataOut) + return OLV_RESULT_INVALID_PTR; + if (iconDataMaxSize < 0x1002C) + return OLV_RESULT_NOT_ENOUGH_SIZE; + uLongf decompressedSize = iconDataMaxSize; + if (uncompress((uint8*)iconDataOut, &decompressedSize, (uint8*)_this->iconData, _this->iconDataSize) != Z_OK) + { + cemuLog_log(LogType::Force, "DownloadedSystemTopicData::GetTitleIconData: uncompress failed"); + return OLV_RESULT_INVALID_TEXT_FIELD; // status + } + *iconSizeOut = decompressedSize; + // todo - check for TGA + return OLV_RESULT_SUCCESS; + } + }; + + static_assert(sizeof(DownloadedSystemTopicData) == 0x13000); + + struct DownloadedSystemTopicDataList + { + static constexpr size_t MAX_TOPIC_COUNT = 10; + static constexpr size_t MAX_POSTS_PER_TOPIC = 300; + + // 0x134B8 sized wrapper of DownloadedSystemTopicData + struct DownloadedSystemTopicWrapped + { + DownloadedSystemTopicData downloadedSystemTopicData; + uint32be postDataNum; + MEMPTR postDataList[MAX_POSTS_PER_TOPIC]; + uint32 uknPadding; + }; + static_assert(offsetof(DownloadedSystemTopicWrapped, postDataNum) == 0x13000); + static_assert(sizeof(DownloadedSystemTopicWrapped) == 0x134B8); + + static uint32 GetDownloadedSystemTopicDataNum(DownloadedSystemTopicDataList* _this) + { + return _this->topicDataNum; + }; + + static uint32 GetDownloadedSystemPostDataNum(DownloadedSystemTopicDataList* _this, uint32 topicIndex) + { + if(topicIndex >= MAX_TOPIC_COUNT) + return 0; + return _this->topicData[topicIndex].postDataNum; + }; + + static DownloadedSystemTopicData* GetDownloadedSystemTopicData(DownloadedSystemTopicDataList* _this, uint32 topicIndex) + { + if(topicIndex >= MAX_TOPIC_COUNT) + return nullptr; + return &_this->topicData[topicIndex].downloadedSystemTopicData; + }; + + static DownloadedSystemPostData* GetDownloadedSystemPostData(DownloadedSystemTopicDataList* _this, sint32 topicIndex, sint32 postIndex) + { + if (topicIndex >= MAX_TOPIC_COUNT || postIndex >= MAX_POSTS_PER_TOPIC) + return nullptr; + return _this->topicData[topicIndex].postDataList[postIndex]; + } + + // member variables + uint32be topicDataNum; + uint32be ukn4; + DownloadedSystemTopicWrapped topicData[MAX_TOPIC_COUNT]; + uint32be uknC0F38[50]; + }; + + static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000); + } + + void loadOlivePostAndTopicTypes(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp new file mode 100644 index 00000000..b76e6d63 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp @@ -0,0 +1,304 @@ +#include "nn_olv_UploadCommunityTypes.h" +#include + +namespace nn +{ + namespace olv + { + + sint32 UploadCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam); + + sint32 UploadCommunityData_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, + UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam + ) + { + sint32 res = UploadCommunityData_AsyncRequestImpl(req, reqUrl, pOutData, pParam); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 UploadCommunityData(UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam) + { + if (!nn::olv::g_IsInitialized) + return OLV_RESULT_NOT_INITIALIZED; + + if (!nn::olv::g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pParam) + return OLV_RESULT_INVALID_PTR; + + if (pOutData) + UploadedCommunityData::Clean(pOutData); + + char requestUrl[512]; + if (pParam->flags & UploadCommunityDataParam::FLAG_DELETION) + { + if (!pParam->communityId) + return OLV_RESULT_INVALID_PARAMETER; + + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.delete", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + } + else + { + if (pParam->communityId) + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + else + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities", g_DiscoveryResults.apiEndpoint); + } + + + CurlRequestHelper req; + req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, UploadCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 UploadCommunityData(UploadCommunityDataParam const* pParam) + { + return UploadCommunityData(nullptr, pParam); + } + + sint32 UploadCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam) + { + sint32 res = OLV_RESULT_SUCCESS; + + std::string base64icon; + std::string form_name; + std::string form_desc; + std::string form_searchKey[5]; + std::string encodedAppData; + uint8* encodedIcon = nullptr; + + struct curl_httppost* post = nullptr; + struct curl_httppost* last = nullptr; + + try + { + if (!pParam->iconData.IsNull()) + { + encodedIcon = new uint8[pParam->iconDataLen]; + if (encodedIcon) + { + sint32 iconEncodeRes = EncodeTGA(pParam->iconData.GetPtr(), pParam->iconDataLen, encodedIcon, pParam->iconDataLen, TGACheckType::CHECK_COMMUNITY_ICON); + if (iconEncodeRes <= 0) + { + delete[] encodedIcon; + return OLV_RESULT_NOT_ENOUGH_SIZE; // ? + } + + base64icon = NCrypto::base64Encode(encodedIcon, iconEncodeRes); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "icon", + CURLFORM_PTRCONTENTS, base64icon.data(), + CURLFORM_CONTENTSLENGTH, base64icon.size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - icon"); + } + } + + if (pParam->titleText[0]) + { + form_name = StringHelpers::ToUtf8((const uint16be*)pParam->titleText, 127); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "name", + CURLFORM_PTRCONTENTS, form_name.data(), + CURLFORM_CONTENTSLENGTH, form_name.size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - name"); + } + + if (pParam->description[0]) + { + form_desc = StringHelpers::ToUtf8((const uint16be*)pParam->description, 255); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "description", + CURLFORM_PTRCONTENTS, form_desc.data(), + CURLFORM_CONTENTSLENGTH, form_desc.size(), + CURLFORM_END) + ); + + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - description"); + } + + for (int i = 0; i < 5; i++) + { + if (pParam->searchKeys[i][0]) + { + form_searchKey[i] = StringHelpers::ToUtf8((const uint16be*)pParam->searchKeys[i], 151); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "search_key", + CURLFORM_PTRCONTENTS, form_searchKey[i].data(), + CURLFORM_CONTENTSLENGTH, form_searchKey[i].size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - search_key"); + } + } + + if (!pParam->appData.IsNull()) + { + encodedAppData = NCrypto::base64Encode(pParam->appData.GetPtr(), pParam->appDataLen); + if (encodedAppData.size() < pParam->appDataLen) + res = OLV_RESULT_FATAL(101); + else + { + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "app_data", + CURLFORM_PTRCONTENTS, encodedAppData.data(), + CURLFORM_CONTENTSLENGTH, encodedAppData.size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - app_data"); + } + } + } + catch (const std::runtime_error& error) + { + cemuLog_log(LogType::Force, "Error in multipart curl -> {}", error.what()); + curl_formfree(post); + + if (encodedIcon) + delete[] encodedIcon; + + return res; + } + + curl_easy_setopt(req.getCURL(), CURLOPT_HTTPPOST, post); + req.setUseMultipartFormData(true); + + bool reqResult = req.submitRequest(true); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + + if (encodedIcon) + delete[] encodedIcon; + + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, 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 upload response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + if (pOutData) + { + + std::string_view app_data = doc.select_single_node("//app_data").node().child_value(); + std::string_view community_id = doc.select_single_node("//community_id").node().child_value(); + std::string_view name = doc.select_single_node("//name").node().child_value(); + std::string_view description = doc.select_single_node("//description").node().child_value(); + std::string_view pid = doc.select_single_node("//pid").node().child_value(); + std::string_view icon = doc.select_single_node("//icon").node().child_value(); + + if (app_data.size() != 0) + { + auto app_data_bin = NCrypto::base64Decode(app_data); + if (app_data_bin.size() != 0) { + memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); + pOutData->flags |= UploadedCommunityData::FLAG_HAS_APP_DATA; + pOutData->appDataLen = app_data_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); + if (community_id_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->communityId = community_id_val; + + if (name.size() != 0) + { + auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + if (name_utf16.size() != 0) + { + for (int i = 0; i < name_utf16.size(); i++) + pOutData->titleText[i] = name_utf16.at(i); + + pOutData->flags |= UploadedCommunityData::FLAG_HAS_TITLE_TEXT; + pOutData->titleTextMaxLen = name_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (description.size() != 0) + { + auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + if (description_utf16.size() != 0) + { + for (int i = 0; i < description_utf16.size(); i++) + pOutData->description[i] = description_utf16.at(i); + + pOutData->flags |= UploadedCommunityData::FLAG_HAS_DESC_TEXT; + pOutData->descriptionMaxLen = description_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 pid_val = StringHelpers::ToInt64(pid, -1); + if (pid_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->pid = pid_val; + + if (icon.size() != 0) + { + auto icon_bin = NCrypto::base64Decode(icon); + if (icon_bin.size() != 0) + { + memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); + pOutData->flags |= UploadedCommunityData::FLAG_HAS_ICON_DATA; + pOutData->iconDataSize = icon_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + } + + return OLV_RESULT_SUCCESS; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h new file mode 100644 index 00000000..4944e314 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h @@ -0,0 +1,432 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + class UploadedCommunityData + { + public: + static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); + static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); + static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); + static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); + + UploadedCommunityData() + { + this->titleTextMaxLen = 0; + this->appDataLen = 0; + this->descriptionMaxLen = 0; + this->pid = 0; + this->communityId = 0; + this->flags = 0; + this->iconDataSize = 0; + } + static UploadedCommunityData* __ctor(UploadedCommunityData* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadedCommunityData(); + } + + static UploadedCommunityData* Clean(UploadedCommunityData* data) + { + data->appDataLen = 0; + data->pid = 0; + data->titleText[0] = 0; + data->description[0] = 0; + data->appData[0] = 0; + data->titleTextMaxLen = 0; + data->iconData[0] = 0; + data->descriptionMaxLen = 0; + data->communityId = 0; + data->flags = 0; + data->iconDataSize = 0; + return data; + } + + bool TestFlags(uint32 flags) const + { + return (this->flags & flags) != 0; + } + static bool __TestFlags(UploadedCommunityData* _this, uint32 flags) + { + return _this->TestFlags(flags); + } + + uint32 GetCommunityId() const + { + return this->communityId; + } + static uint32 __GetCommunityId(UploadedCommunityData* _this) + { + return _this->GetCommunityId(); + } + + sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize <= 12) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + uint32 len = 0; + if (FormatCommunityCode(pBuffer, &len, this->communityId)) + return OLV_RESULT_SUCCESS; + + return OLV_RESULT_INVALID_PARAMETER; + } + static sint32 __GetCommunityCode(UploadedCommunityData* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetCommunityCode(pBuffer, bufferSize); + } + + uint32 GetOwnerPid() const + { + return this->pid; + } + static uint32 __GetOwnerPid(UploadedCommunityData* _this) + { + return _this->GetOwnerPid(); + } + + sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + uint32 readSize = this->titleTextMaxLen; + if (numChars < readSize) + readSize = numChars; + + olv_wstrncpy(pBuffer, this->titleText, readSize); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetTitleText(UploadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetTitleText(pBuffer, numChars); + } + + sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + olv_wstrncpy(pBuffer, this->description, numChars); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetDescriptionText(UploadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetDescriptionText(pBuffer, numChars); + } + + sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + uint32 appDataSize = bufferSize; + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize) + { + if (!this->TestFlags(FLAG_HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (this->appDataLen < appDataSize) + appDataSize = this->appDataLen; + + memcpy(pBuffer, this->appData, appDataSize); + if (pOutSize) + *pOutSize = appDataSize; + + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetAppData(UploadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetAppData(pBuffer, pOutSize, bufferSize); + } + + uint32 GetAppDataSize() const + { + if (this->TestFlags(FLAG_HAS_APP_DATA)) + return this->appDataLen; + + return 0; + } + static uint32 __GetAppDataSize(UploadedCommunityData* _this) + { + return _this->GetAppDataSize(); + } + + sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize < sizeof(this->iconData)) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + if (!this->TestFlags(FLAG_HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + + sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); + if (decodeRes >= 0) + { + if (pOutSize) + *pOutSize = (uint32)decodeRes; + + return OLV_RESULT_SUCCESS; + } + + if (pOutSize) + *pOutSize = 0; + + if (decodeRes == -1) + cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); + else if (decodeRes == -2) + cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); + + return OLV_RESULT_INVALID_TEXT_FIELD; + } + static sint32 __GetIconData(UploadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetIconData(pBuffer, pOutSize, bufferSize); + } + + public: + uint32be flags; + uint32be communityId; + uint32be pid; + char16_t titleText[128]; + uint32be titleTextMaxLen; + char16_t description[256]; + uint32be descriptionMaxLen; + uint8 appData[1024]; + uint32be appDataLen; + uint8 iconData[65580]; + uint32be iconDataSize; + uint8 unk[6328]; + }; + static_assert(sizeof(nn::olv::UploadedCommunityData) == 0x12000, "sizeof(nn::olv::UploadedCommunityData) != 0x12000"); + + + class UploadCommunityDataParam + { + public: + static const inline uint32 FLAG_DELETION = (1 << 0); + + UploadCommunityDataParam() + { + this->appDataLen = 0; + this->communityId = 0; + this->titleId = 0; + this->iconData = MEMPTR(nullptr); + this->appData = MEMPTR(nullptr); + this->iconDataLen = 0; + this->flags = 0; + memset(this->titleText, 0, sizeof(this->titleText)); + memset(this->description, 0, sizeof(this->description)); + int v2 = 0; + do + memset(this->searchKeys[v2++], 0, sizeof(this->searchKeys[v2++])); + while (v2 < 5); + } + static UploadCommunityDataParam* __ctor(UploadCommunityDataParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadCommunityDataParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(UploadCommunityDataParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetCommunityId(uint32 communityId) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(UploadCommunityDataParam* _this, uint32 communityId) + { + return _this->SetCommunityId(communityId); + } + + sint32 SetAppData(MEMPTR pBuffer, uint32 bufferSize) + { + if (!pBuffer.IsNull()) + { + if (bufferSize - 1 >= 0x400) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + this->appData = pBuffer; + this->appDataLen = bufferSize; + } + else + { + this->appData = MEMPTR(nullptr); + this->appDataLen = 0; + } + return OLV_RESULT_SUCCESS; + } + static sint32 __SetAppData(UploadCommunityDataParam* _this, MEMPTR pBuffer, uint32 bufferSize) + { + return _this->SetAppData(pBuffer, bufferSize); + } + + sint32 SetTitleText(char16_t const* pText) + { + if (pText) + return olv_copy_wstr(this->titleText, pText, 127, 128); + + memset(this->titleText, 0, sizeof(this->titleText)); + return OLV_RESULT_SUCCESS; + } + static sint32 __SetTitleText(UploadCommunityDataParam* _this, char16_t const* pText) + { + return _this->SetTitleText(pText); + } + + sint32 SetDescriptionText(char16_t const* pText) + { + if (pText) + return olv_copy_wstr(this->description, pText, 255, 256); + + memset(this->description, 0, sizeof(this->description)); + return OLV_RESULT_SUCCESS; + } + static sint32 __SetDescriptionText(UploadCommunityDataParam* _this, char16_t const* pText) + { + return _this->SetDescriptionText(pText); + } + + sint32 SetIconData(MEMPTR pBuffer, uint32 bufferSize) + { + if (!pBuffer.IsNull()) + { + if (bufferSize) + { + if (bufferSize - 0x10012 < 0x1B) + { + if (CheckTGA(pBuffer.GetPtr(), bufferSize, TGACheckType::CHECK_COMMUNITY_ICON)) + { + this->iconData = pBuffer; + this->iconDataLen = bufferSize; + return OLV_RESULT_SUCCESS; + } + else + { + cemuLog_log(LogType::Force, "OLIVE - SetIconData: TGA Check Failed.\n"); + return OLV_RESULT_INVALID_DATA; + } + } + else + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + else + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + else + { + this->iconData = MEMPTR(nullptr); + this->iconDataLen = 0; + return OLV_RESULT_SUCCESS; + } + } + static sint32 __SetIconData(UploadCommunityDataParam* _this, MEMPTR pBuffer, uint32 bufferSize) + { + return _this->SetIconData(pBuffer, bufferSize); + } + + public: + uint32be flags; + sint32be ___padding_0c; + uint64be titleId; + uint32be communityId; + char16_t titleText[128]; + char16_t description[256]; + char16_t searchKeys[5][152]; + MEMPTR appData; + uint32be appDataLen; + MEMPTR iconData; + uint32be iconDataLen; + char unk3[1772]; + }; + static_assert(sizeof(nn::olv::UploadCommunityDataParam) == 0x1000, "sizeof(nn::olv::UploadCommunityDataParam) != 0x1000"); + + sint32 UploadCommunityData(UploadCommunityDataParam const* pParam); + sint32 UploadCommunityData(UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam); + + static void loadOliveUploadCommunityTypes() + { + cafeExportRegisterFunc(UploadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv21UploadedCommunityDataFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv21UploadedCommunityDataCFUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv21UploadedCommunityDataCFPcUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::None); + + cafeExportRegisterFunc(UploadCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv24UploadCommunityDataParamFv", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetAppData, "nn_olv", "SetAppData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetTitleText, "nn_olv", "SetTitleText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetDescriptionText, "nn_olv", "SetDescriptionText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetIconData, "nn_olv", "SetIconData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::None); + + cafeExportRegisterFunc((sint32(*)(UploadCommunityDataParam const*))UploadCommunityData, + "nn_olv", "UploadCommunityData__Q2_2nn3olvFPCQ3_2nn3olv24UploadCommunityDataParam", LogType::None); + + cafeExportRegisterFunc((sint32(*)(UploadedCommunityData *, UploadCommunityDataParam const*))UploadCommunityData, + "nn_olv", "UploadCommunityData__Q2_2nn3olvFPQ3_2nn3olv21UploadedCommunityDataPCQ3_2nn3olv24UploadCommunityDataParam", LogType::None); + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp new file mode 100644 index 00000000..7d9220fc --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp @@ -0,0 +1,169 @@ +#include "nn_olv_UploadFavoriteTypes.h" +#include + +namespace nn +{ + namespace olv + { + + sint32 UploadFavoriteToCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam + ); + + sint32 UploadFavoriteToCommunityData_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, + UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam + ) + { + sint32 res = UploadFavoriteToCommunityData_AsyncRequestImpl(req, reqUrl, pOutData, pParam); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 UploadFavoriteToCommunityData(UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam) + { + if (!nn::olv::g_IsInitialized) + return OLV_RESULT_NOT_INITIALIZED; + + if (!nn::olv::g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pParam) + return OLV_RESULT_INVALID_PTR; + + if (pOutData) + UploadedFavoriteToCommunityData::Clean(pOutData); + + char requestUrl[512]; + if (pParam->flags & UploadFavoriteToCommunityDataParam::FLAG_DELETION) + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.unfavorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + else + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.favorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + + CurlRequestHelper req; + req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, UploadFavoriteToCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 UploadFavoriteToCommunityData(const UploadFavoriteToCommunityDataParam* pParam) + { + return UploadFavoriteToCommunityData(nullptr, pParam); + } + + sint32 UploadFavoriteToCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam + ) + { + bool reqResult = req.submitRequest(true); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, 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 favorite upload response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + if (pOutData) + { + std::string_view app_data = doc.select_single_node("//app_data").node().child_value(); + std::string_view community_id = doc.select_single_node("//community_id").node().child_value(); + std::string_view name = doc.select_single_node("//name").node().child_value(); + std::string_view description = doc.select_single_node("//description").node().child_value(); + std::string_view pid = doc.select_single_node("//pid").node().child_value(); + std::string_view icon = doc.select_single_node("//icon").node().child_value(); + + if (app_data.size() != 0) + { + auto app_data_bin = NCrypto::base64Decode(app_data); + if (app_data_bin.size() != 0) + { + memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_APP_DATA; + pOutData->appDataLen = app_data_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); + if (community_id_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->communityId = community_id_val; + + if (name.size() != 0) + { + auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + if (name_utf16.size() != 0) + { + for (int i = 0; i < name_utf16.size(); i++) + pOutData->titleText[i] = name_utf16.at(i); + + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_TITLE_TEXT; + pOutData->titleTextMaxLen = name_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (description.size() != 0) + { + auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + if (description_utf16.size() != 0) + { + for (int i = 0; i < description_utf16.size(); i++) + pOutData->description[i] = description_utf16.at(i); + + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_DESC_TEXT; + pOutData->descriptionMaxLen = description_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 pid_val = StringHelpers::ToInt64(pid, -1); + if (pid_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->pid = pid_val; + + if (icon.size() != 0) + { + auto icon_bin = NCrypto::base64Decode(icon); + if (icon_bin.size() != 0) + { + memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_ICON_DATA; + pOutData->iconDataSize = icon_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + } + + return OLV_RESULT_SUCCESS; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h new file mode 100644 index 00000000..05ef1dd4 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h @@ -0,0 +1,342 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + class UploadedFavoriteToCommunityData + { + public: + static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); + static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); + static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); + static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); + + UploadedFavoriteToCommunityData() + { + this->titleTextMaxLen = 0; + this->appDataLen = 0; + this->descriptionMaxLen = 0; + this->pid = 0; + this->communityId = 0; + this->flags = 0; + this->iconDataSize = 0; + } + static UploadedFavoriteToCommunityData* __ctor(UploadedFavoriteToCommunityData* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadedFavoriteToCommunityData(); + } + + static UploadedFavoriteToCommunityData* Clean(UploadedFavoriteToCommunityData* data) + { + data->appDataLen = 0; + data->pid = 0; + data->titleText[0] = 0; + data->description[0] = 0; + data->appData[0] = 0; + data->titleTextMaxLen = 0; + data->iconData[0] = 0; + data->descriptionMaxLen = 0; + data->communityId = 0; + data->flags = 0; + data->iconDataSize = 0; + return data; + } + + bool TestFlags(uint32 flags) const + { + return (this->flags & flags) != 0; + } + static bool __TestFlags(UploadedFavoriteToCommunityData* _this, uint32 flags) + { + return _this->TestFlags(flags); + } + + uint32 GetCommunityId() const + { + return this->communityId; + } + static uint32 __GetCommunityId(UploadedFavoriteToCommunityData* _this) + { + return _this->GetCommunityId(); + } + + sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize <= 12) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + uint32 len = 0; + if (FormatCommunityCode(pBuffer, &len, this->communityId)) + return OLV_RESULT_SUCCESS; + + return OLV_RESULT_INVALID_PARAMETER; + } + static sint32 __GetCommunityCode(UploadedFavoriteToCommunityData* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetCommunityCode(pBuffer, bufferSize); + } + + uint32 GetOwnerPid() const + { + return this->pid; + } + static uint32 __GetOwnerPid(UploadedFavoriteToCommunityData* _this) + { + return _this->GetOwnerPid(); + } + + sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + uint32 readSize = this->titleTextMaxLen; + if (numChars < readSize) + readSize = numChars; + + olv_wstrncpy(pBuffer, this->titleText, readSize); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetTitleText(UploadedFavoriteToCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetTitleText(pBuffer, numChars); + } + + sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + olv_wstrncpy(pBuffer, this->description, numChars); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetDescriptionText(UploadedFavoriteToCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetDescriptionText(pBuffer, numChars); + } + + sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + uint32 appDataSize = bufferSize; + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize) + { + if (!this->TestFlags(FLAG_HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (this->appDataLen < appDataSize) + appDataSize = this->appDataLen; + + memcpy(pBuffer, this->appData, appDataSize); + if (pOutSize) + *pOutSize = appDataSize; + + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetAppData(UploadedFavoriteToCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetAppData(pBuffer, pOutSize, bufferSize); + } + + uint32 GetAppDataSize() const + { + if (this->TestFlags(FLAG_HAS_APP_DATA)) + return this->appDataLen; + + return 0; + } + static uint32 __GetAppDataSize(UploadedFavoriteToCommunityData* _this) + { + return _this->GetAppDataSize(); + } + + sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize < sizeof(this->iconData)) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + if (!this->TestFlags(FLAG_HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + + sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); + if (decodeRes >= 0) + { + if (pOutSize) + *pOutSize = (uint32)decodeRes; + + return OLV_RESULT_SUCCESS; + } + + if (pOutSize) + *pOutSize = 0; + + if (decodeRes == -1) + cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); + else if (decodeRes == -2) + cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); + + return OLV_RESULT_INVALID_TEXT_FIELD; + } + static sint32 __GetIconData(UploadedFavoriteToCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetIconData(pBuffer, pOutSize, bufferSize); + } + + public: + uint32be flags; + uint32be communityId; + uint32be pid; + char16_t titleText[128]; + uint32be titleTextMaxLen; + char16_t description[256]; + uint32be descriptionMaxLen; + uint8 appData[1024]; + uint32be appDataLen; + uint8 iconData[65580]; + uint32be iconDataSize; + uint8 unk[6328]; + }; + static_assert(sizeof(nn::olv::UploadedFavoriteToCommunityData) == 0x12000, "sizeof(nn::olv::UploadedFavoriteToCommunityData) != 0x12000"); + + class UploadFavoriteToCommunityDataParam + { + + public: + static const inline uint32 FLAG_DELETION = (1 << 0); + + UploadFavoriteToCommunityDataParam() + { + this->communityId = 0; + this->flags = 0; + } + static UploadFavoriteToCommunityDataParam* __ctor(UploadFavoriteToCommunityDataParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadFavoriteToCommunityDataParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(UploadFavoriteToCommunityDataParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetCommunityCode(const char* pBuffer) + { + if (strnlen(pBuffer, 13) != 12) + return OLV_RESULT_INVALID_TEXT_FIELD; + + uint32_t id; + if (GetCommunityIdFromCode(&id, pBuffer)) + { + this->communityId = id; + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_STATUS(1901); + } + static sint32 __SetCommunityCode(UploadFavoriteToCommunityDataParam* _this, char* pBuffer) + { + return _this->SetCommunityCode(pBuffer); + } + + sint32 SetCommunityId(uint32 communityId) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(UploadFavoriteToCommunityDataParam* _this, uint32 communityId) + { + return _this->SetCommunityId(communityId); + } + + public: + uint32be flags; + uint32be communityId; + uint8 unk[54]; // Unused + }; + static_assert(sizeof(nn::olv::UploadFavoriteToCommunityDataParam) == 64, "sizeof(nn::olv::UploadFavoriteToCommunityDataParam) != 64"); + + sint32 UploadFavoriteToCommunityData(const UploadFavoriteToCommunityDataParam* pParam); + sint32 UploadFavoriteToCommunityData(UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam); + + static void loadOliveUploadFavoriteTypes() + { + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv31UploadedFavoriteToCommunityDataFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPcUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::None); + + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFv", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityCode, "nn_olv", "SetCommunityCode__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFPCc", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::None); + + cafeExportRegisterFunc((sint32(*)(const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, + "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::None); + + cafeExportRegisterFunc((sint32(*)(UploadedFavoriteToCommunityData*, const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, + "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPQ3_2nn3olv31UploadedFavoriteToCommunityDataPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::None); + } + + } +} \ 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 6e740f12..8a372a41 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -202,6 +202,11 @@ namespace save return ConvertACPToSaveStatus(status); } + SAVEStatus SAVEUnmountSaveDir() + { + return ConvertACPToSaveStatus(acp::ACPUnmountSaveDir()); + } + SAVEStatus SAVEInit() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); @@ -270,7 +275,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSMakeDirAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -1442,6 +1447,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); @@ -1494,5 +1501,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/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index 9b957d06..f39a24ea 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -55,6 +55,7 @@ #define WU_SO_ECONNRESET 0x0008 #define WU_SO_EINVAL 0x000B #define WU_SO_EINPROGRESS 0x0016 +#define WU_SO_EAFNOSUPPORT 0x0021 #define WU_SO_ESHUTDOWN 0x000F @@ -90,15 +91,12 @@ uint32* __gh_errno_ptr() void _setSockError(sint32 errCode) { - // todo -> Call __gh_errno_ptr and then write 32bit error code - *(uint32*)__gh_errno_ptr() = _swapEndianU32(errCode); - //coreinitData->ghsErrno = _swapEndianU32(errCode); + *(uint32be*)__gh_errno_ptr() = (uint32)errCode; } sint32 _getSockError() { - return (sint32)_swapEndianU32(*(uint32*)__gh_errno_ptr()); - //return (sint32)_swapEndianU32(coreinitData->ghsErrno); + return (sint32)*(uint32be*)__gh_errno_ptr(); } // error translation modes for _translateError @@ -644,6 +642,16 @@ void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) *(uint32*)optval = _swapEndianU32(optvalLE); // used by Lost Reavers after some loading screens } + else if (optname == WU_SO_NONBLOCK) + { + if (memory_readU32(optlenMPTR) != 4) + assert_dbg(); + int optvalLE = 0; + socklen_t optlenLE = 4; + memory_writeU32(optlenMPTR, 4); + *(uint32*)optval = _swapEndianU32(vs->isNonBlocking ? 1 : 0); + r = WU_SO_SUCCESS; + } else { cemu_assert_debug(false); @@ -712,13 +720,10 @@ void nsysnetExport_inet_pton(PPCInterpreter_t* hCPU) invalidIp = true; if (d3 < 0 || d3 > 255) invalidIp = true; -#ifdef CEMU_DEBUG_ASSERT - if (invalidIp) - assert_dbg(); -#endif if (invalidIp) { cemuLog_log(LogType::Socket, "inet_pton({}, \"{}\", 0x{:08x}) -> Invalid ip", af, ip, hCPU->gpr[5]); + _setSockError(WU_SO_EAFNOSUPPORT); osLib_returnFromFunction(hCPU, 0); // 0 -> invalid address return; } @@ -729,6 +734,32 @@ void nsysnetExport_inet_pton(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 1); // 1 -> success } +namespace nsysnet +{ + const char* inet_ntop(sint32 af, const void* src, char* dst, uint32 size) + { + if( af != WU_AF_INET) + { + // set error + _setSockError(WU_SO_EAFNOSUPPORT); + return nullptr; + } + const uint8* ip = (const uint8*)src; + char buf[32]; + sprintf(buf, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + size_t bufLen = strlen(buf); + if( (bufLen+1) > size ) + { + // set error + _setSockError(WU_SO_EAFNOSUPPORT); + return nullptr; + } + strcpy(dst, buf); + cemuLog_log(LogType::Socket, "inet_ntop -> {}", buf); + return dst; + } +} + MEMPTR _ntoa_tempString = nullptr; void nsysnetExport_inet_ntoa(PPCInterpreter_t* hCPU) @@ -2122,13 +2153,22 @@ namespace nsysnet } - +namespace nsysnet +{ + void Initialize() + { + cafeExportRegister("nsysnet", inet_ntop, LogType::Socket); + } +} // register nsysnet functions void nsysnet_load() { - - osLib_addFunction("nsysnet", "socket_lib_init", nsysnetExport_socket_lib_init); + nsysnet::Initialize(); + + // the below code is the old way of registering API which is deprecated + + osLib_addFunction("nsysnet", "socket_lib_init", nsysnetExport_socket_lib_init); osLib_addFunction("nsysnet", "socket_lib_finish", nsysnetExport_socket_lib_finish); // socket API diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp index 7ab4cd7a..821cd49e 100644 --- a/src/Cafe/OS/libs/padscore/padscore.cpp +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -53,7 +53,7 @@ namespace padscore WPADState_t g_wpad_state = kWPADStateMaster; struct { - coreinit::OSAlarm_t alarm; + SysAllocator alarm; bool kpad_initialized = false; struct WPADData @@ -286,7 +286,7 @@ void padscoreExport_KPADGetUnifiedWpadStatus(PPCInterpreter_t* hCPU) ppcDefineParamPtr(status, KPADUnifiedWpadStatus_t, 1); ppcDefineParamU32(count, 2); - cemuLog_log(LogType::InputAPI, "KPADGetUnifiedWpadStatus({}, 0x{:x}, 0x{:x})", channel, fmt::ptr(status), count); + cemuLog_log(LogType::InputAPI, "KPADGetUnifiedWpadStatus({}, 0x{:08x}, 0x{:x})", channel, MEMPTR(status).GetMPTR(), count); if (channel < InputManager::kMaxWPADControllers) { diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index caba7237..7de8691a 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -17,11 +17,38 @@ uint32 ProcUIInForeground(PPCInterpreter_t* hCPU) return 1; // true means application is in foreground } -uint32 ProcUIRegisterCallback(uint32 message, MPTR callback, void* data, sint32 ukn) +struct ProcUICallback { + MPTR callback; + void* data; + sint32 priority; +}; +std::unordered_map g_Callbacks; + +uint32 ProcUIRegisterCallback(uint32 message, MPTR callback, void* data, sint32 priority) +{ + g_Callbacks.insert_or_assign(message, ProcUICallback{ .callback = callback, .data = data, .priority = priority }); return 0; } +void ProcUI_SendBackgroundMessage() +{ + if (g_Callbacks.contains(PROCUI_STATUS_BACKGROUND)) + { + ProcUICallback& callback = g_Callbacks[PROCUI_STATUS_BACKGROUND]; + PPCCoreCallback(callback.callback, callback.data); + } +} + +void ProcUI_SendForegroundMessage() +{ + if (g_Callbacks.contains(PROCUI_STATUS_FOREGROUND)) + { + ProcUICallback& callback = g_Callbacks[PROCUI_STATUS_FOREGROUND]; + PPCCoreCallback(callback.callback, callback.data); + } +} + void procui_load() { cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi); diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.h b/src/Cafe/OS/libs/proc_ui/proc_ui.h index 4463c5d3..1cd04fb1 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.h +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.h @@ -1,2 +1,5 @@ -void procui_load(); \ No newline at end of file +void procui_load(); + +void ProcUI_SendForegroundMessage(); +void ProcUI_SendBackgroundMessage(); \ 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 215b9ca7..697d7e96 100644 --- a/src/Cafe/OS/libs/snd_core/ax_internal.h +++ b/src/Cafe/OS/libs/snd_core/ax_internal.h @@ -150,23 +150,7 @@ namespace snd_core /* +0x1E4 */ uint16 a1; /* +0x1E6 */ uint16 a2; }biquad; - uint16 reserved1E8[1]; - uint16 reserved1EA; - uint16 reserved1EC; - uint16 reserved1EE; - uint32 reserved1F0[4]; - uint16 reserved200; - uint16 reserved202; - uint16 reserved204; - uint16 reserved206; - uint16 reserved208; - uint16 reserved20A; - uint16 reserved20C; - uint16 reserved20E; - uint16 reserved210; - uint16 reserved212; - uint16 reserved214; - uint16 reserved216; + uint16 reserved1E8[0x18]; uint16 reserved218[0x20]; // not related to device mix? uint16 reserved258[0x10]; // not related to device mix? // rmt src related @@ -210,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) { @@ -222,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/snd_user/snd_user.cpp b/src/Cafe/OS/libs/snd_user/snd_user.cpp index b413f576..4b884f68 100644 --- a/src/Cafe/OS/libs/snd_user/snd_user.cpp +++ b/src/Cafe/OS/libs/snd_user/snd_user.cpp @@ -247,7 +247,7 @@ namespace snd channel->drc_control[index].fader = 0; channel->drc_control[index].lfe = -960; - for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i) { channel->drc_control[index].aux[i] = -960; } @@ -272,7 +272,7 @@ namespace snd channel->rmt_control[index].fader = 0; channel->rmt_control[index].lfe = -960; - for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i) { channel->rmt_control[index].aux[i] = -960; } 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/OS/libs/vpad/vpad.cpp b/src/Cafe/OS/libs/vpad/vpad.cpp index ab7d403a..85767b56 100644 --- a/src/Cafe/OS/libs/vpad/vpad.cpp +++ b/src/Cafe/OS/libs/vpad/vpad.cpp @@ -162,7 +162,7 @@ namespace vpad struct { - coreinit::OSAlarm_t alarm; + SysAllocator alarm; struct { 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/ParsedMetaXml.h b/src/Cafe/TitleList/ParsedMetaXml.h index 2cb2e7a8..7537d603 100644 --- a/src/Cafe/TitleList/ParsedMetaXml.h +++ b/src/Cafe/TitleList/ParsedMetaXml.h @@ -16,6 +16,8 @@ struct ParsedMetaXml std::array m_short_name; std::array m_publisher; + uint32 m_olv_accesskey; + std::string GetShortName(CafeConsoleLanguage languageId) const { return m_short_name[(size_t)languageId].empty() ? m_short_name[(size_t)CafeConsoleLanguage::EN] : m_short_name[(size_t)languageId]; @@ -51,6 +53,11 @@ struct ParsedMetaXml return m_company_code; } + uint32 GetOlvAccesskey() const + { + return m_olv_accesskey; + } + static ParsedMetaXml* Parse(uint8* xmlData, size_t xmlSize) { if (xmlSize == 0) @@ -98,6 +105,8 @@ struct ParsedMetaXml if (index != -1) parsedMetaXml->m_publisher[index] = child.text().as_string(); } + else if (boost::starts_with(name, L"olv_accesskey")) + parsedMetaXml->m_olv_accesskey = child.text().as_uint(-1); } if (parsedMetaXml->m_title_id == 0) { diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 93498a3c..5f8ba8de 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -131,7 +131,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(); @@ -443,7 +444,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) @@ -452,13 +453,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; } @@ -483,7 +483,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(); @@ -505,10 +505,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; @@ -547,6 +553,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; } @@ -573,6 +581,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); @@ -601,7 +620,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) @@ -616,7 +635,6 @@ std::string TitleInfo::GetTitleName() const } if (m_cachedInfo) return m_cachedInfo->titleName; - cemu_assert_suspicious(); return ""; } @@ -627,10 +645,19 @@ CafeConsoleRegion TitleInfo::GetMetaRegion() const return m_parsedMetaXml->GetRegion(); if (m_cachedInfo) return m_cachedInfo->region; - cemu_assert_suspicious(); return CafeConsoleRegion::JPN; } +uint32 TitleInfo::GetOlvAccesskey() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedMetaXml) + return m_parsedMetaXml->GetOlvAccesskey(); + + cemu_assert_suspicious(); + return -1; +} + std::string TitleInfo::GetArgStr() const { cemu_assert_debug(m_parsedCosXml); diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index 86f89391..b8b781a4 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -22,6 +22,7 @@ struct ParsedAppXml uint16 title_version; uint32 app_type; uint32 group_id; + uint32 sdk_version; }; struct ParsedCosXml @@ -69,6 +70,7 @@ public: std::string subPath; // for WUA uint64 titleId; uint16 titleVersion; + uint32 sdkVersion; std::string titleName; CafeConsoleRegion region; uint32 group_id; @@ -115,13 +117,25 @@ public: return m_uid == rhs.m_uid; } + bool IsSystemDataTitle() const + { + if(!IsValid()) + return false; + uint32 appType = GetAppType(); + if(appType == 0) + return false; // not a valid app_type, but handle this in case some users use placeholder .xml data with fields zeroed-out + return ((appType>>24)&0x80) == 0; + } + // API which requires parsed meta data or cached info TitleId GetAppTitleId() const; // from app.xml uint16 GetAppTitleVersion() const; // from app.xml + uint32 GetAppSDKVersion() const; // from app.xml uint32 GetAppGroup() const; // from app.xml uint32 GetAppType() const; // from app.xml - std::string GetTitleName() const; // from meta.xml + std::string GetMetaTitleName() const; // from meta.xml CafeConsoleRegion GetMetaRegion() const; // from meta.xml + uint32 GetOlvAccesskey() const; // cos.xml std::string GetArgStr() const; diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 3d975204..6f39a760 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -70,6 +70,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"); @@ -81,6 +82,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); @@ -120,6 +122,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()); @@ -674,12 +677,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/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index b478e1c0..776baf33 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -67,6 +67,38 @@ CURLcode _sslctx_function_SOAP(CURL* curl, void* sslctx, void* param) return CURLE_OK; } +CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param) +{ + if (iosuCrypto_addCACertificate(sslctx, 105) == false) + { + cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); + cemuLog_log(LogType::Force, "Certificate error"); + } + if (iosuCrypto_addClientCertificate(sslctx, 7) == false) + { + cemuLog_log(LogType::Force, "Olive client certificate error"); + } + + // NSSLAddServerPKIGroups(sslCtx, 3, &x, &y); + { + std::vector certGroups = { + 100, 101, 102, 103, 104, 105, + 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, + 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, + 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, + 1030, 1031, 1032, 1033 + }; + + for (auto& certId : certGroups) + iosuCrypto_addCACertificate(sslctx, certId); + } + + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); + return CURLE_OK; +} + CurlRequestHelper::CurlRequestHelper() { m_curl = curl_easy_init(); @@ -122,6 +154,11 @@ void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } + else if (sslContext == SERVER_SSL_CONTEXT::OLIVE) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_OLIVE); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + } else { cemu_assert(false); @@ -178,9 +215,11 @@ bool CurlRequestHelper::submitRequest(bool isPost) // post if (isPost) { - curl_easy_setopt(m_curl, CURLOPT_POST, 1); - curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); - curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); + if (!m_isUsingMultipartFormData) { + curl_easy_setopt(m_curl, CURLOPT_POST, 1); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); + } } else curl_easy_setopt(m_curl, CURLOPT_POST, 0); diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h index 00d57e92..6403f074 100644 --- a/src/Cemu/napi/napi_helper.h +++ b/src/Cemu/napi/napi_helper.h @@ -27,6 +27,7 @@ public: CCS, // ccs. IDBE, // idbe-wup. TAGAYA, // tagaya.wup.shop.nintendo.net + OLIVE, // olv. }; CurlRequestHelper(); @@ -50,6 +51,11 @@ public: return m_receiveBuffer; } + void setUseMultipartFormData(bool isUsingMultipartFormData) + { + m_isUsingMultipartFormData = isUsingMultipartFormData; + } + private: static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); @@ -61,6 +67,8 @@ private: // write callback redirect bool (*m_cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast); void* m_writeCallbackUserData{}; + + bool m_isUsingMultipartFormData = false; }; class CurlSOAPHelper // todo - make this use CurlRequestHelper diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index 7fa0ff18..dc1ecd36 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -37,7 +37,10 @@ public: if (ptr == nullptr) m_value = 0; else - m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + { + cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); + m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + } } constexpr MEMPTR(const MEMPTR& memptr) @@ -63,10 +66,13 @@ public: MEMPTR& operator=(T* ptr) { - if (ptr == nullptr) + if (ptr == nullptr) m_value = 0; else - m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + { + cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); + m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + } return *this; } diff --git a/src/Common/SysAllocator.h b/src/Common/SysAllocator.h index a9bc0da1..7930a16b 100644 --- a/src/Common/SysAllocator.h +++ b/src/Common/SysAllocator.h @@ -49,6 +49,13 @@ public: m_tempData.insert(m_tempData.end(), count - l.size(), T()); } + template + SysAllocator(const char(&str)[N]) + { + m_tempData.reserve(count); + m_tempData.insert(m_tempData.begin(), str, str + N); + } + constexpr uint32 GetCount() const { return count; @@ -135,6 +142,9 @@ private: std::vector m_tempData; }; +template +SysAllocator(const char(&str)[N]) -> SysAllocator; + template class SysAllocator : public SysAllocatorBase { diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index ed8b63b4..c84dad56 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -351,6 +351,61 @@ inline unsigned char _addcarry_u64(unsigned char carry, unsigned long long a, un #endif +// asserts + + +inline void cemu_assert(bool _condition) +{ + if ((_condition) == false) + { + DEBUG_BREAK; + } +} + +#ifndef CEMU_DEBUG_ASSERT +//#define cemu_assert_debug(__cond) -> Forcing __cond not to be evaluated currently has unexpected side-effects + +inline void cemu_assert_debug(bool _condition) +{ +} + +inline void cemu_assert_unimplemented() +{ +} + +inline void cemu_assert_suspicious() +{ +} + +inline void cemu_assert_error() +{ + DEBUG_BREAK; +} +#else +inline void cemu_assert_debug(bool _condition) +{ + if ((_condition) == false) + DEBUG_BREAK; +} + +inline void cemu_assert_unimplemented() +{ + DEBUG_BREAK; +} + +inline void cemu_assert_suspicious() +{ + DEBUG_BREAK; +} + +inline void cemu_assert_error() +{ + DEBUG_BREAK; +} +#endif + +#define assert_dbg() DEBUG_BREAK // old style unconditional generic assert + // MEMPTR #include "Common/MemPtr.h" @@ -435,58 +490,6 @@ bool match_any_of(T1 value, T2 compareTo, Types&&... others) #endif } -inline void cemu_assert(bool _condition) -{ - if ((_condition) == false) - { - DEBUG_BREAK; - } -} - -#ifndef CEMU_DEBUG_ASSERT -//#define cemu_assert_debug(__cond) -> Forcing __cond not to be evaluated currently has unexpected side-effects - -inline void cemu_assert_debug(bool _condition) -{ -} - -inline void cemu_assert_unimplemented() -{ -} - -inline void cemu_assert_suspicious() -{ -} - -inline void cemu_assert_error() -{ - DEBUG_BREAK; -} -#else -inline void cemu_assert_debug(bool _condition) -{ - if ((_condition) == false) - DEBUG_BREAK; -} - -inline void cemu_assert_unimplemented() -{ - DEBUG_BREAK; -} - -inline void cemu_assert_suspicious() -{ - DEBUG_BREAK; -} - -inline void cemu_assert_error() -{ - DEBUG_BREAK; -} -#endif - -#define assert_dbg() DEBUG_BREAK // old style unconditional generic assert - // Some string conversion helpers because C++20 std::u8string is too cumbersome to use in practice // mixing string types generally causes loads of issues and many of the libraries we use dont expose interfaces for u8string diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 3352d58b..414237f5 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -178,16 +178,6 @@ void ActiveSettings::EnableDumpLibcurlRequests(bool state) s_dump_libcurl_requests = state; } -bool ActiveSettings::FrameProfilerEnabled() -{ - return s_frame_profiler_enabled; -} - -void ActiveSettings::EnableFrameProfiler(bool state) -{ - s_frame_profiler_enabled = state; -} - bool ActiveSettings::VPADDelayEnabled() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index 4ca6caee..33a673ac 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -108,10 +108,6 @@ public: static void EnableDumpTextures(bool state); static void EnableDumpLibcurlRequests(bool state); - // debug - [[nodiscard]] static bool FrameProfilerEnabled(); - static void EnableFrameProfiler(bool state); - // hacks [[nodiscard]] static bool VPADDelayEnabled(); [[nodiscard]] static bool ShaderPreventInfiniteLoopsEnabled(); @@ -128,7 +124,6 @@ private: inline static uint8 s_timer_shift = 3; // right shift factor, 0 -> 8x, 3 -> 1x, 4 -> 0.5x // debug - inline static bool s_frame_profiler_enabled = false; inline static bool s_audio_aux_only = false; inline static bool s_has_required_online_files = false; diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 8938da3e..96ffc054 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -98,6 +98,7 @@ void CemuConfig::Load(XMLConfigParser& parser) column_width.game_time = loadColumnSize("game_time_width", DefaultColumnSize::game_time); column_width.game_started = loadColumnSize("game_started_width", DefaultColumnSize::game_started); column_width.region = loadColumnSize("region_width", DefaultColumnSize::region); + column_width.title_id = loadColumnSize("title_id", DefaultColumnSize::title_id); recent_launch_files.clear(); auto launch_parser = parser.get("RecentLaunchFiles"); @@ -396,6 +397,7 @@ void CemuConfig::Save(XMLConfigParser& parser) gamelist.set("game_time_width", column_width.game_time); gamelist.set("game_started_width", column_width.game_started); gamelist.set("region_width", column_width.region); + gamelist.set("title_id", column_width.title_id); auto launch_files_parser = config.set("RecentLaunchFiles"); for (const auto& entry : recent_launch_files) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index f3836a9c..ef223cd6 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -338,6 +338,7 @@ namespace DefaultColumnSize { game_time = 140u, game_started = 160u, region = 80u, + title_id = 160u }; }; @@ -425,6 +426,7 @@ struct CemuConfig uint32 game_time = DefaultColumnSize::game_time; uint32 game_started = DefaultColumnSize::game_started; uint32 region = DefaultColumnSize::region; + uint32 title_id = 0; } column_width{}; // graphics diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index ca27b1b8..700e40f6 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -59,6 +59,7 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) ("version,v", "Displays the version of Cemu") ("game,g", po::wvalue(), "Path of game to launch") + ("title-id,t", po::value(), "Title ID of the title to be launched (overridden by --game)") ("mlc,m", po::wvalue(), "Custom mlc folder location") ("fullscreen,f", po::value()->implicit_value(true), "Launch games in fullscreen mode") @@ -132,6 +133,21 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) s_load_game_file = tmp; } + if (vm.count("title-id")) + { + auto title_param = vm["title-id"].as(); + try { + + if (title_param.starts_with('=')){ + title_param.erase(title_param.begin()); + } + s_load_title_id = std::stoull(title_param, nullptr, 16); + } + catch (std::invalid_argument const& e) + { + std::cerr << "Expected title_param ID as an unsigned 64-bit hexadecimal string\n"; + } + } if (vm.count("mlc")) { diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index a916679c..f87dc609 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -16,6 +16,7 @@ public: static bool HandleCommandline(const std::vector& args); static std::optional GetLoadFile() { return s_load_game_file; } + static std::optional GetLoadTitleID() {return s_load_title_id;} static std::optional GetMLCPath() { return s_mlc_path; } static std::optional RenderUpsideDownEnabled() { return s_render_upside_down; } @@ -35,6 +36,7 @@ public: private: inline static std::optional s_load_game_file{}; + inline static std::optional s_load_title_id{}; inline static std::optional s_mlc_path{}; inline static std::optional s_render_upside_down{}; diff --git a/src/config/NetworkSettings.cpp b/src/config/NetworkSettings.cpp index 20df8251..5cc66a91 100644 --- a/src/config/NetworkSettings.cpp +++ b/src/config/NetworkSettings.cpp @@ -29,6 +29,7 @@ void NetworkConfig::Load(XMLConfigParser& parser) urls.IDBE = u.get("idbe", NintendoURLs::IDBEURL); urls.BOSS = u.get("boss", NintendoURLs::BOSSURL); urls.TAGAYA = u.get("tagaya", NintendoURLs::TAGAYAURL); + urls.OLV = u.get("olv", NintendoURLs::OLVURL); if (static_cast(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) LaunchSettings::ChangeNetworkServiceURL(2); } diff --git a/src/config/NetworkSettings.h b/src/config/NetworkSettings.h index f289e679..26137cdd 100644 --- a/src/config/NetworkSettings.h +++ b/src/config/NetworkSettings.h @@ -30,6 +30,7 @@ struct NetworkConfig { ConfigValue IDBE; ConfigValue BOSS; ConfigValue TAGAYA; + ConfigValue OLV; }urls{}; public: @@ -50,6 +51,7 @@ struct NintendoURLs { inline static std::string IDBEURL = "https://idbe-wup.cdn.nintendo.net/icondata"; inline static std::string BOSSURL = "https://npts.app.nintendo.net/p01/tasksheet"; inline static std::string TAGAYAURL = "https://tagaya.wup.shop.nintendo.net/tagaya/versionlist"; + inline static std::string OLVURL = "https://discovery.olv.nintendo.net/v1/endpoint"; }; struct PretendoURLs { @@ -62,6 +64,7 @@ struct PretendoURLs { inline static std::string IDBEURL = "https://idbe-wup.cdn.pretendo.cc/icondata"; inline static std::string BOSSURL = "https://npts.app.pretendo.cc/p01/tasksheet"; inline static std::string TAGAYAURL = "https://tagaya.wup.shop.pretendo.cc/tagaya/versionlist"; + inline static std::string OLVURL = "https://discovery.olv.pretendo.cc/v1/endpoint"; }; typedef XMLDataConfig XMLNetworkConfig_t; diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index f23d33a2..f130ac67 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -110,13 +110,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; @@ -153,10 +154,6 @@ bool CemuApp::OnInit() __fastfail(0); } #endif - - // init input - InputManager::instance().load(); - InitializeGlobalVulkan(); Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this); diff --git a/src/gui/GameUpdateWindow.h b/src/gui/GameUpdateWindow.h index 60ec5d63..508e6465 100644 --- a/src/gui/GameUpdateWindow.h +++ b/src/gui/GameUpdateWindow.h @@ -26,7 +26,7 @@ public: //bool IsDLC() const { return m_game_info->IsDLC(); } //bool IsUpdate() const { return m_game_info->IsUpdate(); } const std::string& GetExceptionMessage() const { return m_thread_exception; } - const std::string GetGameName() const { return m_title_info.GetTitleName(); } + const std::string GetGameName() const { return m_title_info.GetMetaTitleName(); } uint32 GetTargetVersion() const { return m_title_info.GetAppTitleVersion(); } fs::path GetTargetPath() const { return fs::path(m_target_path); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index da6ce5fc..0fe0d9bd 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -72,8 +72,6 @@ MainWindow* g_mainFrame; -wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); - enum { // ui elements @@ -138,7 +136,6 @@ enum MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, - MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER, MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, @@ -161,8 +158,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) @@ -224,7 +223,6 @@ EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, MainWindow::OnDebugViewPPCThr EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MainWindow::OnDebugViewPPCDebugger) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MainWindow::OnDebugViewAudioDebugger) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugViewTextureRelations) -EVT_MENU(MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER, MainWindow::OnDebugSetting) // help menu EVT_MENU(MAINFRAME_MENU_ID_HELP_WEB, MainWindow::OnHelpVistWebpage) EVT_MENU(MAINFRAME_MENU_ID_HELP_ABOUT, MainWindow::OnHelpAbout) @@ -238,6 +236,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 @@ -292,6 +292,8 @@ MainWindow::MainWindow() GuiSystem::getWindowInfo().window_main = get_window_handle_info_for_wxWindow(this); g_mainFrame = this; DownloadManager::GetInstance()->setOnGameListRefreshRequested([this](){RequestGameListRefresh();}); + CafeSystem::SetImplementation(this); + RecreateMenu(); SetClientSize(1280, 720); SetIcon(wxICON(M_WND_ICON128)); @@ -306,59 +308,64 @@ MainWindow::MainWindow() #endif auto* main_sizer = new wxBoxSizer(wxVERTICAL); - if (!LaunchSettings::GetLoadFile().has_value()) - { - { - 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); + auto load_file = LaunchSettings::GetLoadFile(); + auto load_title_id = LaunchSettings::GetLoadTitleID(); + bool quick_launch = false; - // 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); + if (load_file) + { + MainWindow::RequestLaunchGame(load_file.value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); + quick_launch = true; + } + else if (load_title_id) + { + TitleInfo info; + TitleId baseId; + if (CafeTitleList::FindBaseTitleId(load_title_id.value(), baseId) && CafeTitleList::GetFirstByTitleId(baseId, info)) + { + MainWindow::RequestLaunchGame(info.GetPath(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); + quick_launch = true; + } + else + { + wxString errorMsg = fmt::format("Title ID {:016x} not found", load_title_id.value()); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - m_main_panel->SetSizer(sizer); - main_sizer->Add(m_main_panel, 1, wxEXPAND, 0, nullptr); - } - } - else - { - // launching game via -g option. Dont setup or load game list - m_game_list = nullptr; - m_info_bar = nullptr; - } - SetSizer(main_sizer); + } + } + 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); - m_last_mouse_move_time = std::chrono::steady_clock::now(); + m_last_mouse_move_time = std::chrono::steady_clock::now(); - m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1); - m_timer->Start(500); + m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1); + m_timer->Start(500); - LoadSettings(); + LoadSettings(); - auto& config = GetConfig(); -#ifdef ENABLE_DISCORD_RPC - if (config.use_discord_presence) - m_discord = std::make_unique(); -#endif + 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); + Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this); + Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this); - if (LaunchSettings::GetLoadFile().has_value()) - { - MainWindow::RequestLaunchGame(LaunchSettings::GetLoadFile().value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); - } - if (LaunchSettings::GDBStubEnabled()) - { - g_gdbstub = std::make_unique(config.gdb_port); - } + if (LaunchSettings::GDBStubEnabled()) + { + g_gdbstub = std::make_unique(config.gdb_port); + } CafeSystem::registerCafeSystemCallbacks(this); } @@ -367,7 +374,6 @@ MainWindow::~MainWindow() CafeSystem::unregisterCafeSystemCallbacks(); if (m_padView) { - //delete m_padView; m_padView->Destroy(); m_padView = nullptr; } @@ -465,13 +471,47 @@ void MainWindow::notifyGameLoaded() } +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()) @@ -504,7 +544,7 @@ void MainWindow::OnClose(wxCloseEvent& event) event.Skip(); - CafeSystem::ShutdownTitle(); + CafeSystem::Shutdown(); DestroyCanvas(); } @@ -627,36 +667,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) @@ -744,10 +761,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(); } } @@ -1126,10 +1145,6 @@ void MainWindow::OnDebugSetting(wxCommandEvent& event) wxMessageBox(_("Dump complete")); }*/ } - else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER) - { - ActiveSettings::EnableFrameProfiler(event.IsChecked()); - } else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS) { // toggle debug -> dump -> curl requests @@ -1717,7 +1732,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); @@ -1750,14 +1777,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) @@ -2220,9 +2251,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) { @@ -2256,7 +2287,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 } @@ -2266,7 +2297,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()) @@ -2278,23 +2309,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); @@ -2309,12 +2326,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(); @@ -2323,12 +2339,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")); @@ -2336,7 +2351,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); @@ -2346,12 +2361,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")); @@ -2388,7 +2403,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")); @@ -2418,13 +2433,12 @@ void MainWindow::RecreateMenu() debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, _("&View PPC debugger")); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, _("&View audio debugger")); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, _("&View texture cache info")); - debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER, _("&Show frame profiler"), wxEmptyString); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, _("&Dump current RAM")); // debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, _("&Dump WUD filesystem"))->Enable(false); 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")); @@ -2444,8 +2458,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); @@ -2516,31 +2530,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(); @@ -2551,7 +2543,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); @@ -2568,3 +2559,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 1fcc4e6b..ef7d0ae2 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -51,7 +51,7 @@ private: INITIATED_BY m_initiatedBy; }; -class MainWindow : public wxFrame, CafeSystem::CafeSystemCallbacks +class MainWindow : public wxFrame, public CafeSystem::CafeSystemCallbacks, public CafeSystem::SystemImplementation { friend class CemuApp; @@ -59,6 +59,10 @@ public: MainWindow(); ~MainWindow(); + void CreateGameListAndStatusBar(); + void DestroyGameListAndStatusBar(); + + virtual void updateWindowTitles(bool isIdle, bool isLoading, double fps) override; virtual void notifyGameLoaded() override; @@ -155,6 +159,11 @@ private: void OnTimer(wxTimerEvent& event); + // CafeSystem implementation + void CafeRecreateCanvas() override; + + void OnRequestRecreateCanvas(wxCommandEvent& event); + wxRect GetDesktopRect(); MemorySearcherTool* m_toolWindow = nullptr; @@ -187,8 +196,6 @@ private: void LoadSettings(); void SaveSettings(); - std::string GetRegionString(uint32 region) const; - void OnGraphicWindowClose(wxCloseEvent& event); void OnGraphicWindowOpen(wxTitleIdEvent& event); @@ -199,42 +206,40 @@ private: wxWindow* m_render_canvas{}; // gamelist - wxGameList* m_game_list; - wxInfoBar* m_info_bar; + wxGameList* m_game_list{}; + wxInfoBar* m_info_bar{}; // menu - wxMenuBar* m_menuBar = nullptr; + wxMenuBar* m_menuBar{}; // file - wxMenu* m_fileMenu; - wxMenuItem* m_fileMenuSeparator0; - wxMenuItem* m_fileMenuSeparator1; - wxMenuItem* m_loadMenuItem; - wxMenuItem* m_installUpdateMenuItem; - wxMenuItem* m_exitMenuItem; + wxMenu* m_fileMenu{}; + wxMenuItem* m_fileMenuSeparator0{}; + wxMenuItem* m_fileMenuSeparator1{}; + wxMenuItem* m_loadMenuItem{}; + wxMenuItem* m_installUpdateMenuItem{}; + wxMenuItem* m_exitMenuItem{}; // options - //wxMenu* m_gpuBufferCacheAccuracySubmenu; - wxMenu* m_optionsAccountMenu; + wxMenu* m_optionsAccountMenu{}; - wxMenuItem* m_fullscreenMenuItem; - wxMenuItem* m_padViewMenuItem; + wxMenuItem* m_fullscreenMenuItem{}; + wxMenuItem* m_padViewMenuItem{}; // tools - wxMenuItem* m_memorySearcherMenuItem; + wxMenuItem* m_memorySearcherMenuItem{}; // cpu - //wxMenu* m_cpuModeSubmenu; - wxMenu* m_cpuTimerSubmenu; + wxMenu* m_cpuTimerSubmenu{}; // nfc - wxMenu* m_nfcMenu; - wxMenuItem* m_nfcMenuSeparator0; + wxMenu* m_nfcMenu{}; + wxMenuItem* m_nfcMenuSeparator0{}; // debug - wxMenu* m_debugMenu; - wxMenu* m_loggingSubmenu; - wxMenuItem* m_asyncCompile; + wxMenu* m_debugMenu{}; + wxMenu* m_loggingSubmenu{}; + wxMenuItem* m_asyncCompile{}; wxDECLARE_EVENT_TABLE(); }; diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index 1209a55f..0a90fb1c 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -89,6 +89,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) { auto& windowInfo = GuiSystem::getWindowInfo(); 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 88b708f2..a703a74c 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 8f59c2c8..fed18279 100644 --- a/src/gui/canvas/VulkanCanvas.cpp +++ b/src/gui/canvas/VulkanCanvas.cpp @@ -54,8 +54,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 20cc49e2..3b24bac6 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -13,6 +13,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -30,6 +34,17 @@ #include "Cafe/IOSU/PDM/iosu_pdm.h" // for last played and play time +#if BOOST_OS_WINDOWS +// for shortcut creation +#include +#include +#include +#include +#include +#include +#include +#endif + // public events wxDEFINE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent); wxDEFINE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent); @@ -79,6 +94,7 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id) InsertColumn(ColumnGameTime, _("You've played"), wxLIST_FORMAT_LEFT, config.column_width.game_time); InsertColumn(ColumnGameStarted, _("Last played"), wxLIST_FORMAT_LEFT, config.column_width.game_started); InsertColumn(ColumnRegion, _("Region"), wxLIST_FORMAT_LEFT, config.column_width.region); + InsertColumn(ColumnTitleID, _("Title ID"), wxLIST_FORMAT_LEFT, config.column_width.title_id); const char transparent_bitmap[kIconWidth * kIconWidth * 4] = {0}; wxBitmap blank(transparent_bitmap, kIconWidth, kIconWidth); @@ -244,6 +260,8 @@ int wxGameList::GetColumnDefaultWidth(int column) return DefaultColumnSize::game_started; case ColumnRegion: return DefaultColumnSize::region; + case ColumnTitleID: + return DefaultColumnSize::title_id; default: return 80; } @@ -305,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; } @@ -528,6 +546,7 @@ enum ContextMenuEntries kContextMenuStyleList, kContextMenuStyleIcon, kContextMenuStyleIconSmall, + kContextMenuCreateShortcut }; void wxGameList::OnContextMenu(wxContextMenuEvent& event) { @@ -568,6 +587,10 @@ void wxGameList::OnContextMenu(wxContextMenuEvent& event) menu.Append(kContextMenuEditGraphicPacks, _("&Edit graphic packs")); menu.Append(kContextMenuEditGameProfile, _("&Edit game profile")); + menu.AppendSeparator(); +#if BOOST_OS_LINUX || BOOST_OS_WINDOWS + menu.Append(kContextMenuCreateShortcut, _("&Create shortcut")); +#endif menu.AppendSeparator(); } } @@ -687,6 +710,11 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) (new GameProfileWindow(GetParent(), title_id))->Show(); break; } + case kContextMenuCreateShortcut: +#if BOOST_OS_LINUX || BOOST_OS_WINDOWS + CreateShortcut(gameInfo); +#endif + break; } } } @@ -729,6 +757,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) ShowGameTime, ShowLastPlayed, ShowRegion, + ShowTitleId }; const int column = event.GetColumn(); wxMenu menu; @@ -744,6 +773,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) menu.AppendCheckItem(ShowGameTime, _("Show &game time"))->Check(GetColumnWidth(ColumnGameTime) > 0); menu.AppendCheckItem(ShowLastPlayed, _("Show &last played"))->Check(GetColumnWidth(ColumnGameStarted) > 0); menu.AppendCheckItem(ShowRegion, _("Show ®ion"))->Check(GetColumnWidth(ColumnRegion) > 0); + menu.AppendCheckItem(ShowTitleId, _("Show &title ID"))->Check(GetColumnWidth(ColumnTitleID) > 0); menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { @@ -773,6 +803,9 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) case ShowRegion: config.column_width.region = menu->IsChecked(ShowRegion) ? DefaultColumnSize::region : 0; break; + case ShowTitleId: + config.column_width.title_id = menu->IsChecked(ShowTitleId) ? DefaultColumnSize::title_id : 0; + break; case ResetWidth: { switch (column) @@ -797,6 +830,8 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) case ColumnRegion: config.column_width.region = DefaultColumnSize::region; break; + case ColumnTitleID: + config.column_width.title_id = DefaultColumnSize::title_id; default: return; } @@ -836,6 +871,7 @@ void wxGameList::ApplyGameListColumnWidths() SetColumnWidth(ColumnGameTime, config.column_width.game_time); SetColumnWidth(ColumnGameStarted, config.column_width.game_started); SetColumnWidth(ColumnRegion, config.column_width.region); + SetColumnWidth(ColumnTitleID, config.column_width.title_id); AdjustLastColumnWidth(); } @@ -931,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(); @@ -1003,6 +1037,7 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) const auto region_text = fmt::format("{}", gameInfo.GetRegion()); SetItem(index, ColumnRegion, _(region_text)); + SetItem(index, ColumnTitleID, _(fmt::format("{:016x}", titleId))); } else if (m_style == Style::kIcons) { @@ -1189,3 +1224,119 @@ void wxGameList::DeleteCachedStrings() { m_name_cache.clear(); } + +#if BOOST_OS_LINUX || BOOST_OS_WINDOWS +void wxGameList::CreateShortcut(GameInfo2& gameInfo) { + const auto title_id = gameInfo.GetBaseTitleId(); + const auto title_name = gameInfo.GetTitleName(); + auto exe_path = ActiveSettings::GetExecutablePath(); + + // GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path + wxString appimage_path; + if (wxGetEnv(("APPIMAGE"), &appimage_path)) { + exe_path = appimage_path.utf8_string(); + } + +#if BOOST_OS_LINUX + const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name); + wxFileDialog entry_dialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktop_entry_name, + "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); +#elif BOOST_OS_WINDOWS + // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path + PWSTR user_shortcut_folder; + SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &user_shortcut_folder); + const wxString shortcut_name = wxString::Format("%s.lnk", title_name); + wxFileDialog entry_dialog(this, _("Choose shortcut location"), _pathToUtf8(user_shortcut_folder), shortcut_name, + "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); +#endif + const auto result = entry_dialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto output_path = entry_dialog.GetPath(); + +#if BOOST_OS_LINUX + std::optional icon_path; + // Obtain and convert icon + { + m_icon_cache_mtx.lock(); + const auto icon_iter = m_icon_cache.find(title_id); + const auto result_index = (icon_iter != m_icon_cache.cend()) ? std::optional(icon_iter->second.first) : std::nullopt; + m_icon_cache_mtx.unlock(); + + // In most cases it should find it + if (!result_index){ + wxMessageBox("Icon is yet to load, so will not be used by the shortcut", "Warning", wxOK | wxCENTRE | wxICON_WARNING); + } + else { + const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons"); + + if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){ + wxMessageBox("Cannot access the icon directory, the shortcut will have no icon", "Warning", wxOK | wxCENTRE | wxICON_WARNING); + } + else { + icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + + auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage(); + + wxFileOutputStream png_file(_pathToUtf8(icon_path.value())); + wxPNGHandler pngHandler; + if (!pngHandler.SaveFile(&image, png_file, false)) { + icon_path = std::nullopt; + wxMessageBox("The icon was unable to be saved, the shortcut will have no icon", "Warning", wxOK | wxCENTRE | wxICON_WARNING); + } + } + } + } + const auto desktop_entry_string = + fmt::format("[Desktop Entry]\n" + "Name={}\n" + "Comment=Play {} on Cemu\n" + "Exec={} --title-id {:016x}\n" + "Icon={}\n" + "Terminal=false\n" + "Type=Application\n" + "Categories=Game;", + title_name, + title_name, + _pathToUtf8(exe_path), + title_id, + _pathToUtf8(icon_path.value_or(""))); + + std::ofstream output_stream(output_path); + if (!output_stream.good()) + { + const wxString errorMsg = fmt::format("Failed to save desktop entry to {}", output_path.utf8_string()); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return; + } + output_stream << desktop_entry_string; + +#elif BOOST_OS_WINDOWS + IShellLinkW *shell_link; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&shell_link)); + if (SUCCEEDED(hres)) + { + const auto description = wxString::Format("Play %s on Cemu", title_name); + const auto args = wxString::Format("-t %016llx", title_id); + + shell_link->SetPath(exe_path.wstring().c_str()); + shell_link->SetDescription(description.wc_str()); + shell_link->SetArguments(args.wc_str()); + shell_link->SetWorkingDirectory(exe_path.parent_path().wstring().c_str()); + // Use icon from Cemu exe for now since we can't embed icons into the shortcut + // in the future we could convert and store icons in AppData or ProgramData + shell_link->SetIconLocation(exe_path.wstring().c_str(), 0); + + IPersistFile *shell_link_file; + // save the shortcut + hres = shell_link->QueryInterface(IID_IPersistFile, reinterpret_cast(&shell_link_file)); + if (SUCCEEDED(hres)) + { + hres = shell_link_file->Save(output_path.wc_str(), TRUE); + shell_link_file->Release(); + } + shell_link->Release(); + } +#endif +} +#endif \ No newline at end of file diff --git a/src/gui/components/wxGameList.h b/src/gui/components/wxGameList.h index 7776d7f6..b285d259 100644 --- a/src/gui/components/wxGameList.h +++ b/src/gui/components/wxGameList.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "util/helpers/Semaphore.h" class wxTitleIdEvent : public wxCommandEvent @@ -52,6 +53,10 @@ public: void ReloadGameEntries(bool cached = false); void DeleteCachedStrings(); +#if BOOST_OS_LINUX || BOOST_OS_WINDOWS + void CreateShortcut(GameInfo2& gameInfo); +#endif + long FindListItemByTitleId(uint64 title_id) const; void OnClose(wxCloseEvent& event); @@ -75,8 +80,9 @@ private: ColumnGameTime, ColumnGameStarted, ColumnRegion, - //ColumnFavorite, - ColumnCounts + ColumnTitleID, + //ColumnFavorite, + ColumnCounts, }; int s_last_column = ColumnName; diff --git a/src/gui/components/wxProgressDialogManager.h b/src/gui/components/wxProgressDialogManager.h new file mode 100644 index 00000000..93b6c316 --- /dev/null +++ b/src/gui/components/wxProgressDialogManager.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include "util/helpers/Semaphore.h" + +wxDEFINE_EVENT(wxEVT_CREATE_PROGRESS_DIALOG, wxThreadEvent); +wxDEFINE_EVENT(wxEVT_DESTROY_PROGRESS_DIALOG, wxThreadEvent); +wxDEFINE_EVENT(wxEVT_UPDATE_PROGRESS_DIALOG, wxThreadEvent); + +// wrapper for wxGenericProgressDialog which can be used from any thread +class wxProgressDialogManager : public wxEvtHandler +{ +public: + wxProgressDialogManager(wxWindow* parent) : m_parent(parent), m_dialog(nullptr) + { + Bind(wxEVT_CREATE_PROGRESS_DIALOG, &wxProgressDialogManager::OnCreateProgressDialog, this); + Bind(wxEVT_DESTROY_PROGRESS_DIALOG, &wxProgressDialogManager::OnDestroyProgressDialog, this); + Bind(wxEVT_UPDATE_PROGRESS_DIALOG, &wxProgressDialogManager::OnUpdateProgressDialog, this); + } + + ~wxProgressDialogManager() + { + if (m_dialog) + Destroy(); + } + + void Create(const wxString& title, const wxString& message, int maximum, int style = wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME) + { + m_instanceSemaphore.increment(); + m_isCancelled = false; + m_isSkipped = false; + wxThreadEvent event(wxEVT_CREATE_PROGRESS_DIALOG); + event.SetString(title); + event.SetInt(maximum); + event.SetExtraLong(style); + wxQueueEvent(this, event.Clone()); + } + + void Destroy() + { + wxThreadEvent event(wxEVT_DESTROY_PROGRESS_DIALOG); + wxQueueEvent(this, event.Clone()); + m_instanceSemaphore.waitUntilZero(); // wait until destruction is complete + } + + // this also updates the cancel and skip state + void Update(int value, const wxString& newmsg = wxEmptyString) + { + wxThreadEvent event(wxEVT_UPDATE_PROGRESS_DIALOG); + event.SetInt(value); + event.SetString(newmsg); + wxQueueEvent(this, event.Clone()); + } + + bool IsCancelled() const + { + return m_isCancelled; + } + + bool IsSkipped() const + { + return m_isSkipped; + } + + bool IsCancelledOrSkipped() const + { + return m_isCancelled || m_isSkipped; + } + +private: + void OnCreateProgressDialog(wxThreadEvent& event) + { + if (m_dialog) + { + m_dialog->Destroy(); + m_instanceSemaphore.waitUntilZero(); + } + m_maximum = event.GetInt(); + m_dialog = new wxGenericProgressDialog(event.GetString(), "Please wait...", m_maximum, m_parent, event.GetExtraLong()); + } + + void OnDestroyProgressDialog(wxThreadEvent& event) + { + if (m_dialog) + { + m_dialog->Destroy(); + m_dialog = nullptr; + m_instanceSemaphore.decrement(); + } + } + + void OnUpdateProgressDialog(wxThreadEvent& event) + { + if (m_dialog) + { + // make sure that progress is never >= maximum + // because wxGenericProgressDialog seems to become crashy on destruction otherwise + int progress = event.GetInt(); + if(progress >= m_maximum) + progress = m_maximum - 1; + bool wasSkipped = false; + bool r = m_dialog->Update(progress, event.GetString(), &wasSkipped); + if(!r) + m_isCancelled = true; + if(wasSkipped) + m_isSkipped = true; + } + } + + wxWindow* m_parent; + wxGenericProgressDialog* m_dialog; + bool m_isCancelled{false}; + bool m_isSkipped{false}; + int m_maximum{0}; + CounterSemaphore m_instanceSemaphore; // used to synchronize destruction of the dialog +}; \ No newline at end of file diff --git a/src/gui/debugger/RegisterWindow.cpp b/src/gui/debugger/RegisterWindow.cpp index 5da20b6a..4076d105 100644 --- a/src/gui/debugger/RegisterWindow.cpp +++ b/src/gui/debugger/RegisterWindow.cpp @@ -145,7 +145,7 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, else if (value->GetForegroundColour() != COLOR_BLACK) value->SetForegroundColour(COLOR_BLACK); - value->SetLabelText(wxString::Format("%08x", registerValue)); + value->ChangeValue(wxString::Format("%08x", registerValue)); //const auto label = dynamic_cast(GetWindowChild(kRegisterLabelR0 + i)); //wxASSERT(label); @@ -177,7 +177,7 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, if (is_valid_string && buffer.tellp() > 1) { - label->SetLabelText(wxString::Format("\"%s\"", buffer.str().c_str())); + label->ChangeValue(wxString::Format("\"%s\"", buffer.str().c_str())); return; } @@ -206,7 +206,7 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, if (is_valid_string && buffer.tellp() > 1) { - label->SetLabelText(wxString::Format(L"ws\"%s\"", wbuffer.str().c_str())); + label->ChangeValue(wxString::Format(L"ws\"%s\"", wbuffer.str().c_str())); return; } } @@ -215,11 +215,11 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(registerValue); if (code_module) { - label->SetLabelText(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), registerValue - code_module->regionMappingBase_text.GetMPTR())); + label->ChangeValue(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), registerValue - code_module->regionMappingBase_text.GetMPTR())); return; } - label->SetLabelText(wxEmptyString); + label->ChangeValue(wxEmptyString); } void RegisterWindow::OnUpdateView() @@ -264,9 +264,9 @@ void RegisterWindow::OnUpdateView() continue; if(m_show_double_values) - value->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); + value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); else - value->SetLabelText(wxString::Format("%016llx", register_value)); + value->ChangeValue(wxString::Format("%016llx", register_value)); } for (int i = 0; i < 32; ++i) @@ -285,9 +285,9 @@ void RegisterWindow::OnUpdateView() continue; if (m_show_double_values) - value->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); + value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); else - value->SetLabelText(wxString::Format("%016llx", register_value)); + value->ChangeValue(wxString::Format("%016llx", register_value)); } // update CRs @@ -318,9 +318,9 @@ void RegisterWindow::OnUpdateView() joinArray.emplace_back("SO"); if (joinArray.empty()) - value->SetLabelText("-"); + value->ChangeValue("-"); else - value->SetLabelText(fmt::format("{}", fmt::join(joinArray, ", "))); + value->ChangeValue(fmt::format("{}", fmt::join(joinArray, ", "))); } memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot)); @@ -399,13 +399,13 @@ void RegisterWindow::OnFPViewModePress(wxCommandEvent& event) if (m_show_double_values) { - value0->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); - value1->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); + value0->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); + value1->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); } else { - value0->SetLabelText(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int)); - value1->SetLabelText(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int)); + value0->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int)); + value1->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int)); } } } diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index 6963555d..6cc3ecc5 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -5,6 +5,8 @@ #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "gui/components/wxProgressDialogManager.h" + #include enum @@ -23,17 +25,18 @@ enum THREADLIST_MENU_SUSPEND, THREADLIST_MENU_RESUME, THREADLIST_MENU_DUMP_STACK_TRACE, + THREADLIST_MENU_PROFILE_THREAD, }; wxBEGIN_EVENT_TABLE(DebugPPCThreadsWindow, wxFrame) - EVT_BUTTON(CLOSE_ID,DebugPPCThreadsWindow::OnCloseButton) - EVT_BUTTON(REFRESH_ID,DebugPPCThreadsWindow::OnRefreshButton) - - EVT_CLOSE(DebugPPCThreadsWindow::OnClose) + EVT_BUTTON(CLOSE_ID, DebugPPCThreadsWindow::OnCloseButton) + EVT_BUTTON(REFRESH_ID, DebugPPCThreadsWindow::OnRefreshButton) + EVT_CLOSE(DebugPPCThreadsWindow::OnClose) wxEND_EVENT_TABLE() DebugPPCThreadsWindow::DebugPPCThreadsWindow(wxFrame& parent) - : wxFrame(&parent, wxID_ANY, _("PPC threads"), wxDefaultPosition, wxSize(930, 280), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) + : wxFrame(&parent, wxID_ANY, _("PPC threads"), wxDefaultPosition, wxSize(930, 280), + wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) { wxFrame::SetBackgroundColour(*wxWHITE); @@ -158,7 +161,6 @@ void DebugPPCThreadsWindow::OnTimer(wxTimerEvent& event) RefreshThreadList(); } - #define _r(__idx) _swapEndianU32(cafeThread->context.gpr[__idx]) void DebugPPCThreadsWindow::RefreshThreadList() @@ -173,100 +175,103 @@ void DebugPPCThreadsWindow::RefreshThreadList() const int scrollPos = m_thread_list->GetScrollPos(0); m_thread_list->DeleteAllItems(); - __OSLockScheduler(); - srwlock_activeThreadList.LockWrite(); - for (sint32 i = 0; i < activeThreadCount; i++) - { - MPTR threadItrMPTR = activeThread[i]; - OSThread_t* cafeThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR); + if(activeThreadCount > 0) + { + __OSLockScheduler(); + srwlock_activeThreadList.LockWrite(); + for (sint32 i = 0; i < activeThreadCount; i++) + { + MPTR threadItrMPTR = activeThread[i]; + OSThread_t* cafeThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR); - char tempStr[512]; - sprintf(tempStr, "%08X", threadItrMPTR); + char tempStr[512]; + sprintf(tempStr, "%08X", threadItrMPTR); - wxListItem item; - item.SetId(i); - item.SetText(tempStr); - m_thread_list->InsertItem(item); - m_thread_list->SetItemData(item, (long)threadItrMPTR); - // entry point - sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->entrypoint)); - m_thread_list->SetItem(i, 1, tempStr); - // stack base (low) - sprintf(tempStr, "%08X - %08X", _swapEndianU32(cafeThread->stackEnd), _swapEndianU32(cafeThread->stackBase)); - m_thread_list->SetItem(i, 2, tempStr); - // pc - RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0); - if (symbol) - sprintf(tempStr, "%s (0x%08x)", (const char*)symbol->symbolName, cafeThread->context.srr0); - else - sprintf(tempStr, "%08X", cafeThread->context.srr0); - m_thread_list->SetItem(i, 3, tempStr); - // lr - sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->context.lr)); - m_thread_list->SetItem(i, 4, tempStr); - // state - OSThread_t::THREAD_STATE threadState = cafeThread->state; - wxString threadStateStr = "UNDEFINED"; - if (cafeThread->suspendCounter != 0) - threadStateStr = "SUSPENDED"; - else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE) - threadStateStr = "NONE"; - else if (threadState == OSThread_t::THREAD_STATE::STATE_READY) - threadStateStr = "READY"; - else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING) - threadStateStr = "RUNNING"; - else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING) - threadStateStr = "WAITING"; - else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND) - threadStateStr = "MORIBUND"; - m_thread_list->SetItem(i, 5, threadStateStr); - // affinity - uint8 affinity = cafeThread->attr&7; - uint8 affinityReal = cafeThread->context.affinity; - if(affinity != affinityReal) - sprintf(tempStr, "(!) %d%d%d real: %d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, (affinityReal >> 0) & 1, (affinityReal >> 1) & 1, (affinityReal >> 2) & 1); - else - sprintf(tempStr, "%d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1); - m_thread_list->SetItem(i, 6, tempStr); - // priority - sint32 effectivePriority = cafeThread->effectivePriority; - sprintf(tempStr, "%d", effectivePriority); - m_thread_list->SetItem(i, 7, tempStr); - // last awake in cycles - uint64 lastWakeUpTime = cafeThread->wakeUpTime; - sprintf(tempStr, "%" PRIu64, lastWakeUpTime); - m_thread_list->SetItem(i, 8, tempStr); - // awake time in cycles - uint64 awakeTime = cafeThread->totalCycles; - sprintf(tempStr, "%" PRIu64, awakeTime); - m_thread_list->SetItem(i, 9, tempStr); - // thread name - const char* threadName = "NULL"; - if (!cafeThread->threadName.IsNull()) - threadName = cafeThread->threadName.GetPtr(); - m_thread_list->SetItem(i, 10, threadName); - // GPR - sprintf(tempStr, "r3 %08x r4 %08x r5 %08x r6 %08x r7 %08x", _r(3), _r(4), _r(5), _r(6), _r(7)); - m_thread_list->SetItem(i, 11, tempStr); - // waiting condition / extra info - coreinit::OSMutex* mutex = cafeThread->waitingForMutex; - if (mutex) - sprintf(tempStr, "Mutex 0x%08x (Held by thread 0x%08X Lock-Count: %d)", memory_getVirtualOffsetFromPointer(mutex), mutex->owner.GetMPTR(), (uint32)mutex->lockCount); - else - sprintf(tempStr, ""); + wxListItem item; + item.SetId(i); + item.SetText(tempStr); + m_thread_list->InsertItem(item); + m_thread_list->SetItemData(item, (long)threadItrMPTR); + // entry point + sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->entrypoint)); + m_thread_list->SetItem(i, 1, tempStr); + // stack base (low) + sprintf(tempStr, "%08X - %08X", _swapEndianU32(cafeThread->stackEnd), _swapEndianU32(cafeThread->stackBase)); + m_thread_list->SetItem(i, 2, tempStr); + // pc + RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0); + if (symbol) + sprintf(tempStr, "%s (0x%08x)", (const char*)symbol->symbolName, cafeThread->context.srr0); + else + sprintf(tempStr, "%08X", cafeThread->context.srr0); + m_thread_list->SetItem(i, 3, tempStr); + // lr + sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->context.lr)); + m_thread_list->SetItem(i, 4, tempStr); + // state + OSThread_t::THREAD_STATE threadState = cafeThread->state; + wxString threadStateStr = "UNDEFINED"; + if (cafeThread->suspendCounter != 0) + threadStateStr = "SUSPENDED"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE) + threadStateStr = "NONE"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_READY) + threadStateStr = "READY"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING) + threadStateStr = "RUNNING"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING) + threadStateStr = "WAITING"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND) + threadStateStr = "MORIBUND"; + m_thread_list->SetItem(i, 5, threadStateStr); + // affinity + uint8 affinity = cafeThread->attr&7; + uint8 affinityReal = cafeThread->context.affinity; + if(affinity != affinityReal) + sprintf(tempStr, "(!) %d%d%d real: %d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, (affinityReal >> 0) & 1, (affinityReal >> 1) & 1, (affinityReal >> 2) & 1); + else + sprintf(tempStr, "%d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1); + m_thread_list->SetItem(i, 6, tempStr); + // priority + sint32 effectivePriority = cafeThread->effectivePriority; + sprintf(tempStr, "%d", effectivePriority); + m_thread_list->SetItem(i, 7, tempStr); + // last awake in cycles + uint64 lastWakeUpTime = cafeThread->wakeUpTime; + sprintf(tempStr, "%" PRIu64, lastWakeUpTime); + m_thread_list->SetItem(i, 8, tempStr); + // awake time in cycles + uint64 awakeTime = cafeThread->totalCycles; + sprintf(tempStr, "%" PRIu64, awakeTime); + m_thread_list->SetItem(i, 9, tempStr); + // thread name + const char* threadName = "NULL"; + if (!cafeThread->threadName.IsNull()) + threadName = cafeThread->threadName.GetPtr(); + m_thread_list->SetItem(i, 10, threadName); + // GPR + sprintf(tempStr, "r3 %08x r4 %08x r5 %08x r6 %08x r7 %08x", _r(3), _r(4), _r(5), _r(6), _r(7)); + m_thread_list->SetItem(i, 11, tempStr); + // waiting condition / extra info + coreinit::OSMutex* mutex = cafeThread->waitingForMutex; + if (mutex) + sprintf(tempStr, "Mutex 0x%08x (Held by thread 0x%08X Lock-Count: %d)", memory_getVirtualOffsetFromPointer(mutex), mutex->owner.GetMPTR(), (uint32)mutex->lockCount); + else + sprintf(tempStr, ""); - // OSSetThreadCancelState - if (cafeThread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL) - strcat(tempStr, "[Cancel requested]"); + // OSSetThreadCancelState + if (cafeThread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL) + strcat(tempStr, "[Cancel requested]"); - m_thread_list->SetItem(i, 12, tempStr); + m_thread_list->SetItem(i, 12, tempStr); - if(selected_thread != 0 && selected_thread == (long)threadItrMPTR) - m_thread_list->SetItemState(i, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); - } - srwlock_activeThreadList.UnlockWrite(); - __OSUnlockScheduler(); + if(selected_thread != 0 && selected_thread == (long)threadItrMPTR) + m_thread_list->SetItemState(i, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); + } + srwlock_activeThreadList.UnlockWrite(); + __OSUnlockScheduler(); + } m_thread_list->SetScrollPos(0, scrollPos, true); } @@ -275,30 +280,126 @@ 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, fmt::format("Dumping stack trace for thread {0:08x} LR: {1:08x}", + memory_getVirtualOffsetFromPointer(thread), + _swapEndianU32(thread->context.lr))); DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1])); } +void DebugPPCThreadsWindow::PresentProfileResults(OSThread_t* thread, const std::unordered_map& samples) +{ + std::vector> sortedSamples; + // count samples + uint32 totalSampleCount = 0; + for (auto& sample : samples) + totalSampleCount += sample.second; + cemuLog_log(LogType::Force, "--- Thread {:08x} profile results with {:} samples captured ---", + MEMPTR(thread).GetMPTR(), totalSampleCount); + cemuLog_log(LogType::Force, "Exclusive time, grouped by function:"); + // print samples grouped by function + sortedSamples.clear(); + for (auto& sample : samples) + { + RPLStoredSymbol* symbol = rplSymbolStorage_getByClosestAddress(sample.first); + VAddr sampleAddr = sample.first; + if (symbol) + sampleAddr = symbol->address; + auto it = std::find_if(sortedSamples.begin(), sortedSamples.end(), + [sampleAddr](const std::pair& a) { return a.first == sampleAddr; }); + if (it != sortedSamples.end()) + it->second += sample.second; + else + sortedSamples.push_back(std::make_pair(sampleAddr, sample.second)); + } + std::sort(sortedSamples.begin(), sortedSamples.end(), + [](const std::pair& a, const std::pair& b) { return a.second > b.second; }); + for (auto& sample : sortedSamples) + { + if (sample.second < 3) + continue; + VAddr sampleAddr = sample.first; + RPLStoredSymbol* symbol = rplSymbolStorage_getByClosestAddress(sample.first); + std::string strName; + if (symbol) + { + strName = fmt::format("{}.{}+0x{:x}", (const char*)symbol->libName, (const char*)symbol->symbolName, + sampleAddr - symbol->address); + } + else + strName = "Unknown"; + cemuLog_log(LogType::Force, "[{:08x}] {:8.2f}% (Samples: {:5}) Symbol: {}", sample.first, + (double)(sample.second * 100) / (double)totalSampleCount, sample.second, strName); + } +} + +void DebugPPCThreadsWindow::ProfileThreadWorker(OSThread_t* thread) +{ + wxProgressDialogManager progressDialog(this); + progressDialog.Create("Profiling thread", + _("Capturing samples..."), + 1000, // range + wxPD_CAN_SKIP); + + std::unordered_map samples; + // loop for one minute + uint64 startTime = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + uint32 totalSampleCount = 0; + while (true) + { + // suspend thread + coreinit::OSSuspendThread(thread); + // wait until thread is not running anymore + __OSLockScheduler(); + while (coreinit::OSIsThreadRunningNoLock(thread)) + { + __OSUnlockScheduler(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + __OSLockScheduler(); + } + uint32 sampleIP = thread->context.srr0; + __OSUnlockScheduler(); + coreinit::OSResumeThread(thread); + // count sample + samples[sampleIP]++; + totalSampleCount++; + if ((totalSampleCount % 50) == 0) + { + wxString msg = fmt::format("Capturing samples... ({:})\nResults will be written to log.txt\n", + totalSampleCount); + if (totalSampleCount < 30000) + msg.Append(_("Click Skip button for early results with lower accuracy")); + else + msg.Append(_("Click Skip button to finish")); + progressDialog.Update(totalSampleCount * 1000 / 30000, msg); + if (progressDialog.IsCancelledOrSkipped()) + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + PresentProfileResults(thread, samples); + progressDialog.Destroy(); +} + +void DebugPPCThreadsWindow::ProfileThread(OSThread_t* thread) +{ + std::thread profileThread(&DebugPPCThreadsWindow::ProfileThreadWorker, this, thread); + profileThread.detach(); +} + void DebugPPCThreadsWindow::OnThreadListPopupClick(wxCommandEvent& evt) { - MPTR threadMPTR = (MPTR)(size_t)static_cast(evt.GetEventObject())->GetClientData(); - // check if thread is still active - bool threadIsActive = false; - srwlock_activeThreadList.LockWrite(); - for (sint32 i = 0; i < activeThreadCount; i++) - { - MPTR threadItrMPTR = activeThread[i]; - if (threadItrMPTR == threadMPTR) - { - threadIsActive = true; - break; - } - } - srwlock_activeThreadList.UnlockWrite(); - if (threadIsActive == false) - return; - // handle command + MPTR threadMPTR = (MPTR)(size_t) static_cast(evt.GetEventObject())->GetClientData(); OSThread_t* osThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadMPTR); + __OSLockScheduler(); + if (!coreinit::__OSIsThreadActive(osThread)) + { + __OSUnlockScheduler(); + return; + } + __OSUnlockScheduler(); + // handle command switch (evt.GetId()) { case THREADLIST_MENU_BOOST_PRIO_5: @@ -322,6 +423,9 @@ void DebugPPCThreadsWindow::OnThreadListPopupClick(wxCommandEvent& evt) case THREADLIST_MENU_DUMP_STACK_TRACE: DumpStackTrace(osThread); break; + case THREADLIST_MENU_PROFILE_THREAD: + ProfileThread(osThread); + break; } coreinit::__OSUpdateThreadEffectivePriority(osThread); // update thread list @@ -338,26 +442,19 @@ void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) // select item m_thread_list->SetItemState(itemIndex, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); long sel = m_thread_list->GetNextItem(-1, wxLIST_NEXT_ALL, - wxLIST_STATE_SELECTED); + wxLIST_STATE_SELECTED); if (sel != -1) m_thread_list->SetItemState(sel, 0, wxLIST_STATE_SELECTED); m_thread_list->SetItemState(itemIndex, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); // check if thread is still on the list of active threads MPTR threadMPTR = (MPTR)m_thread_list->GetItemData(itemIndex); - bool threadIsActive = false; - srwlock_activeThreadList.LockWrite(); - for (sint32 i = 0; i < activeThreadCount; i++) + __OSLockScheduler(); + if (!coreinit::__OSIsThreadActive(MEMPTR(threadMPTR))) { - MPTR threadItrMPTR = activeThread[i]; - if (threadItrMPTR == threadMPTR) - { - threadIsActive = true; - break; - } - } - srwlock_activeThreadList.UnlockWrite(); - if (threadIsActive == false) + __OSUnlockScheduler(); return; + } + __OSUnlockScheduler(); // create menu entry wxMenu menu; menu.SetClientData((void*)(size_t)threadMPTR); @@ -371,6 +468,7 @@ void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) menu.Append(THREADLIST_MENU_SUSPEND, _("Suspend")); menu.AppendSeparator(); menu.Append(THREADLIST_MENU_DUMP_STACK_TRACE, _("Write stack trace to log")); + menu.Append(THREADLIST_MENU_PROFILE_THREAD, _("Profile thread")); menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(DebugPPCThreadsWindow::OnThreadListPopupClick), nullptr, this); PopupMenu(&menu); } diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h index e967ad7c..649780c5 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h @@ -15,12 +15,15 @@ public: void OnThreadListPopupClick(wxCommandEvent &evt); void OnThreadListRightClick(wxMouseEvent& event); - void DumpStackTrace(struct OSThread_t* thread); - void Close(); private: - wxListCtrl* m_thread_list; + void ProfileThread(struct OSThread_t* thread); + void ProfileThreadWorker(OSThread_t* thread); + void PresentProfileResults(OSThread_t* thread, const std::unordered_map& samples); + void DumpStackTrace(struct OSThread_t* thread); + + wxListCtrl* m_thread_list; wxCheckBox* m_auto_refresh; wxTimer* m_timer; diff --git a/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp b/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp index 62cb7ec6..4b930a87 100644 --- a/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp +++ b/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp @@ -48,52 +48,57 @@ TextureRelationViewerWindow::TextureRelationViewerWindow(wxFrame& parent) textureRelationListA->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New"));//wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT)); // add columns - wxListItem col0; + wxListItem colType; sint32 columnIndex = 0; - col0.SetId(columnIndex); columnIndex++; - col0.SetText("Type"); - col0.SetWidth(85); - textureRelationListA->InsertColumn(columnIndex-1, col0); - wxListItem col1; - col1.SetId(columnIndex); columnIndex++; - col1.SetText("PhysAddr"); - col1.SetWidth(80); - textureRelationListA->InsertColumn(columnIndex-1, col1); - wxListItem col2; - col2.SetId(columnIndex); columnIndex++; - col2.SetText("Dim"); - col2.SetWidth(80); - textureRelationListA->InsertColumn(columnIndex-1, col2); - wxListItem col3; - col3.SetId(columnIndex); columnIndex++; - col3.SetText("Resolution"); - col3.SetWidth(110); - textureRelationListA->InsertColumn(columnIndex-1, col3); - wxListItem col5; - col5.SetId(columnIndex); columnIndex++; - col5.SetText("Format"); - col5.SetWidth(70); - textureRelationListA->InsertColumn(columnIndex-1, col5); - wxListItem colPriority; - colPriority.SetId(columnIndex); columnIndex++; - colPriority.SetText("Pitch"); - colPriority.SetWidth(80); - textureRelationListA->InsertColumn(columnIndex-1, colPriority); - wxListItem col6; - col6.SetId(columnIndex); columnIndex++; - col6.SetText("Tilemode"); - col6.SetWidth(80); - textureRelationListA->InsertColumn(columnIndex-1, col6); - wxListItem col7; - col7.SetId(columnIndex); columnIndex++; - col7.SetText("SliceRange"); - col7.SetWidth(90); - textureRelationListA->InsertColumn(columnIndex-1, col7); - wxListItem col8; - col8.SetId(columnIndex); columnIndex++; - col8.SetText("MipRange"); - col8.SetWidth(90); - textureRelationListA->InsertColumn(columnIndex-1, col8); + colType.SetId(columnIndex); columnIndex++; + colType.SetText("Type"); + colType.SetWidth(85); + textureRelationListA->InsertColumn(columnIndex-1, colType); + wxListItem colPhysAddr; + colPhysAddr.SetId(columnIndex); columnIndex++; + colPhysAddr.SetText("PhysAddr"); + colPhysAddr.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, colPhysAddr); + wxListItem colPhysMipAddr; + colPhysMipAddr.SetId(columnIndex); columnIndex++; + colPhysMipAddr.SetText("MipPAddr"); + colPhysMipAddr.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, colPhysMipAddr); + wxListItem colDim; + colDim.SetId(columnIndex); columnIndex++; + colDim.SetText("Dim"); + colDim.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, colDim); + wxListItem colResolution; + colResolution.SetId(columnIndex); columnIndex++; + colResolution.SetText("Resolution"); + colResolution.SetWidth(110); + textureRelationListA->InsertColumn(columnIndex-1, colResolution); + wxListItem colFormat; + colFormat.SetId(columnIndex); columnIndex++; + colFormat.SetText("Format"); + colFormat.SetWidth(70); + textureRelationListA->InsertColumn(columnIndex-1, colFormat); + wxListItem colPitch; + colPitch.SetId(columnIndex); columnIndex++; + colPitch.SetText("Pitch"); + colPitch.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, colPitch); + wxListItem colTilemode; + colTilemode.SetId(columnIndex); columnIndex++; + colTilemode.SetText("Tilemode"); + colTilemode.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, colTilemode); + wxListItem colSliceRange; + colSliceRange.SetId(columnIndex); columnIndex++; + colSliceRange.SetText("SliceRange"); + colSliceRange.SetWidth(90); + textureRelationListA->InsertColumn(columnIndex-1, colSliceRange); + wxListItem colMipRange; + colMipRange.SetId(columnIndex); columnIndex++; + colMipRange.SetText("MipRange"); + colMipRange.SetWidth(90); + textureRelationListA->InsertColumn(columnIndex-1, colMipRange); wxListItem colAge; colAge.SetId(columnIndex); columnIndex++; colAge.SetText("Last access"); @@ -186,10 +191,14 @@ void TextureRelationViewerWindow::_setTextureRelationListItemTexture(wxListCtrl* uiList->InsertItem(item); sint32 columnIndex = 1; - // phys address - sprintf(tempStr, "%08X", texInfo->physAddress); - uiList->SetItem(rowIndex, columnIndex, tempStr); - columnIndex++; + // phys address + sprintf(tempStr, "%08X", texInfo->physAddress); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // phys mip address + sprintf(tempStr, "%08X", texInfo->physMipAddress); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; // dim if (texInfo->dim == Latte::E_DIM::DIM_2D) strcpy(tempStr, "2D"); @@ -278,10 +287,14 @@ void TextureRelationViewerWindow::_setTextureRelationListItemView(wxListCtrl* ui uiList->InsertItem(item); //uiList->SetItemPtrData(item, (wxUIntPtr)viewInfo); sint32 columnIndex = 1; - // phys address - sprintf(tempStr, ""); - uiList->SetItem(rowIndex, columnIndex, tempStr); - columnIndex++; + // phys address + sprintf(tempStr, ""); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // phys mip address + sprintf(tempStr, ""); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; // dim if (viewInfo->dim == Latte::E_DIM::DIM_2D) strcpy(tempStr, "2D"); diff --git a/src/imgui/imgui_extension.cpp b/src/imgui/imgui_extension.cpp index 33c49dd3..da9b2475 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/api/SDL/SDLControllerProvider.cpp b/src/input/api/SDL/SDLControllerProvider.cpp index fcec0f7e..9e0c09b5 100644 --- a/src/input/api/SDL/SDLControllerProvider.cpp +++ b/src/input/api/SDL/SDLControllerProvider.cpp @@ -32,7 +32,7 @@ SDLControllerProvider::SDLControllerProvider() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1"); if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) < 0) - throw std::runtime_error(fmt::format("couldn't initialize SDL: %s", SDL_GetError())); + throw std::runtime_error(fmt::format("couldn't initialize SDL: {}", SDL_GetError())); if (SDL_GameControllerEventState(SDL_ENABLE) < 0) { diff --git a/src/main.cpp b/src/main.cpp index 45332109..e25223c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,13 +2,15 @@ #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/CemuConfig.h" #include "config/NetworkSettings.h" -#include "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" @@ -110,7 +112,6 @@ void infoLog_cemuStartup() cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); - // check for wine version checkForWine(); // CPU and RAM info logCPUAndMemoryInfo(); @@ -155,16 +156,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) @@ -172,24 +166,54 @@ void mainEmulatorCommonInit() else executablePath.clear(); SetCurrentDirectory(executablePath.c_str()); - // set high priority SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); -#endif + #endif +} + +void CemuCommonInit() +{ + reconfigureGLDrivers(); + reconfigureVkDrivers(); + // crypto init + AES128_init(); + // init PPC timer + // call this as early as possible because it measures frequency of RDTSC using an asynchronous thread over 3 seconds + PPCTimer_init(); + + WindowsInitCwd(); ExceptionHandler_Init(); // read config g_config.Load(); if (NetworkConfig::XMLExists()) - n_config.Load(); + n_config.Load(); // symbol storage rplSymbolStorage_init(); - // static initialization - IAudioAPI::InitializeStatic(); - IAudioInputAPI::InitializeStatic(); - // load graphic packs (must happen before config is loaded) - GraphicPack2::LoadAll(); - // initialize file system - fsc_init(); + // parallelize expensive init code + std::future futureInitAudioAPI = std::async(std::launch::async, []{ IAudioAPI::InitializeStatic(); IAudioInputAPI::InitializeStatic(); return 0; }); + std::future futureInitGraphicPacks = std::async(std::launch::async, []{ GraphicPack2::LoadAll(); return 0; }); + InputManager::instance().load(); + futureInitAudioAPI.wait(); + futureInitGraphicPacks.wait(); + // log Cemu startup info + infoLog_cemuStartup(); + // init Cafe system + CafeSystem::Initialize(); + // init title list + CafeTitleList::Initialize(ActiveSettings::GetUserDataPath("title_list_cache.xml")); + for (auto& it : GetConfig().game_paths) + CafeTitleList::AddScanPath(it); + fs::path mlcPath = ActiveSettings::GetMlcPath(); + if (!mlcPath.empty()) + CafeTitleList::SetMLCPath(mlcPath); + CafeTitleList::Refresh(); + // init save list + CafeSaveList::Initialize(); + if (!mlcPath.empty()) + { + CafeSaveList::SetMLCPath(mlcPath); + CafeSaveList::Refresh(); + } } void mainEmulatorLLE(); @@ -215,35 +239,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 72b42b9d..f33384f7 100644 --- a/src/mainLLE.cpp +++ b/src/mainLLE.cpp @@ -1,8 +1,8 @@ #include "util/crypto/aes128.h" #include "Common/FileStream.h" -void mainEmulatorCommonInit(); void gui_create(); +void CemuCommonInit(); typedef struct { @@ -31,7 +31,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; + }; };