From 81acd80a97faa3905d27ad170f16244e17581d2d Mon Sep 17 00:00:00 2001 From: Squall Leonhart Date: Sun, 18 Feb 2024 15:51:00 +1100 Subject: [PATCH 001/130] Cubeb: Add a default device to the selection (#1017) --- src/audio/CubebAPI.cpp | 13 +++++++++---- src/audio/CubebInputAPI.cpp | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp index 09e4501..2b4aec4 100644 --- a/src/audio/CubebAPI.cpp +++ b/src/audio/CubebAPI.cpp @@ -188,15 +188,20 @@ std::vector CubebAPI::GetDevices() return {}; std::vector result; - result.reserve(devices.count); + result.reserve(devices.count + 1); // Reserve space for the default device + + // Add the default device to the list + auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); + result.emplace_back(defaultDevice); + for (size_t i = 0; i < devices.count; ++i) { - //const auto& device = devices.device[i]; + // const auto& device = devices.device[i]; if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, - boost::nowide::widen( - devices.device[i].friendly_name)); + boost::nowide::widen( + devices.device[i].friendly_name)); result.emplace_back(device); } } diff --git a/src/audio/CubebInputAPI.cpp b/src/audio/CubebInputAPI.cpp index de030fd..c0fa73f 100644 --- a/src/audio/CubebInputAPI.cpp +++ b/src/audio/CubebInputAPI.cpp @@ -180,15 +180,20 @@ std::vector CubebInputAPI::GetDevices() return {}; std::vector result; - result.reserve(devices.count); + result.reserve(devices.count + 1); // Reserve space for the default device + + // Add the default device to the list + auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); + result.emplace_back(defaultDevice); + for (size_t i = 0; i < devices.count; ++i) { - //const auto& device = devices.device[i]; + // const auto& device = devices.device[i]; if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, - boost::nowide::widen( - devices.device[i].friendly_name)); + boost::nowide::widen( + devices.device[i].friendly_name)); result.emplace_back(device); } } From 6a08d04af9c22d2b6ec432b9ac48a299abcfb9f8 Mon Sep 17 00:00:00 2001 From: Squall Leonhart Date: Sun, 18 Feb 2024 15:52:11 +1100 Subject: [PATCH 002/130] UI: Make Alt+F4/Ctrl+Q more reliable (#1035) --- src/gui/MainWindow.cpp | 14 ++++++++++++++ src/gui/MainWindow.h | 1 + 2 files changed, 15 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index dc9ff0a..d271ca3 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1485,6 +1485,19 @@ void MainWindow::OnKeyUp(wxKeyEvent& event) g_window_info.has_screenshot_request = true; // async screenshot request } +void MainWindow::OnKeyDown(wxKeyEvent& event) +{ + if ((event.AltDown() && event.GetKeyCode() == WXK_F4) || + (event.CmdDown() && event.GetKeyCode() == 'Q')) + { + Close(true); + } + else + { + event.Skip(); + } +} + void MainWindow::OnChar(wxKeyEvent& event) { if (swkbd_hasKeyboardInputHook()) @@ -1590,6 +1603,7 @@ void MainWindow::CreateCanvas() // key events m_render_canvas->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this); + m_render_canvas->Bind(wxEVT_KEY_DOWN, &MainWindow::OnKeyDown, this); m_render_canvas->Bind(wxEVT_CHAR, &MainWindow::OnChar, this); m_render_canvas->SetDropTarget(new wxAmiiboDropTarget(this)); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 25100b7..88d2a1d 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -124,6 +124,7 @@ public: void OnSetWindowTitle(wxCommandEvent& event); void OnKeyUp(wxKeyEvent& event); + void OnKeyDown(wxKeyEvent& event); void OnChar(wxKeyEvent& event); void OnToolsInput(wxCommandEvent& event); From 9bbb7c8b97ff6e5a080984c818b1e66b0f2ce609 Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Sat, 17 Feb 2024 20:54:41 -0800 Subject: [PATCH 003/130] Add support for portable directory without build flag (#1071) --- .github/workflows/build.yml | 3 +- CMakeLists.txt | 5 ---- src/gui/CemuApp.cpp | 59 ++++++++++++++++++++++--------------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c01ba7..00aac0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,7 +75,7 @@ jobs: - name: "cmake" run: | - cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DPORTABLE=OFF -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja + cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja - name: "Build Cemu" run: | @@ -258,7 +258,6 @@ jobs: cd build cmake .. ${{ env.BUILD_FLAGS }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ - -DPORTABLE=OFF \ -DMACOS_BUNDLE=ON \ -DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \ -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \ diff --git a/CMakeLists.txt b/CMakeLists.txt index ec6abed..6b5f388 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.21.1) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) -option(PORTABLE "All data created and maintained by Cemu will be in the directory where the executable file is located" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version @@ -45,10 +44,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_definitions($<$:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT -if(PORTABLE) - add_compile_definitions(PORTABLE) -endif() - set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 4acc1cf..fde4bcc 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -59,33 +59,44 @@ bool CemuApp::OnInit() fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); -#ifdef PORTABLE -#if MACOS_BUNDLE - exePath = exePath.parent_path().parent_path().parent_path(); + + // Try a portable path first, if it exists. + user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; +#if BOOST_OS_MACOS + // If run from an app bundle, use its parent directory. + fs::path appPath = exePath.parent_path().parent_path().parent_path(); + if (appPath.extension() == ".app") + user_data_path = config_path = cache_path = data_path = appPath.parent_path() / "portable"; #endif - user_data_path = config_path = cache_path = data_path = exePath.parent_path(); -#else - SetAppName("Cemu"); - wxString appName=GetAppName(); - #if BOOST_OS_LINUX - standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); - auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) + + if (!fs::exists(user_data_path)) { - wxString dir; - if (!wxGetEnv(varName, &dir) || dir.empty()) - return defaultValue; - return dir; - }; - wxString homeDir=wxFileName::GetHomeDir(); - user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); - config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); - #else - user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); - #endif - data_path = standardPaths.GetDataDir().ToStdString(); - cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); - cache_path /= appName.ToStdString(); +#if BOOST_OS_WINDOWS + user_data_path = config_path = cache_path = data_path = exePath.parent_path(); +#else + SetAppName("Cemu"); + wxString appName=GetAppName(); +#if BOOST_OS_LINUX + standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); + auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) + { + wxString dir; + if (!wxGetEnv(varName, &dir) || dir.empty()) + return defaultValue; + return dir; + }; + wxString homeDir=wxFileName::GetHomeDir(); + user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); + config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); +#else + user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); #endif + data_path = standardPaths.GetDataDir().ToStdString(); + cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); + cache_path /= appName.ToStdString(); +#endif + } + auto failed_write_access = ActiveSettings::LoadOnce(exePath, user_data_path, config_path, cache_path, data_path); for (auto&& path : failed_write_access) wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), From ed01eaf5f949847d8be16d271abdeafe901e3971 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 Feb 2024 04:56:36 +0000 Subject: [PATCH 004/130] Gamelist: Add right-click actions for copying title ID, name, and icon (#1089) --- src/gui/components/wxGameList.cpp | 51 +++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 5ceaf71..88934cd 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -17,6 +17,8 @@ #include #include #include +#include + #include #include @@ -546,7 +548,12 @@ enum ContextMenuEntries kContextMenuStyleList, kContextMenuStyleIcon, kContextMenuStyleIconSmall, - kContextMenuCreateShortcut + + kContextMenuCreateShortcut, + + kContextMenuCopyTitleName, + kContextMenuCopyTitleId, + kContextMenuCopyTitleImage }; void wxGameList::OnContextMenu(wxContextMenuEvent& event) { @@ -591,6 +598,10 @@ void wxGameList::OnContextMenu(wxContextMenuEvent& event) #if BOOST_OS_LINUX || BOOST_OS_WINDOWS menu.Append(kContextMenuCreateShortcut, _("&Create shortcut")); #endif + menu.AppendSeparator(); + menu.Append(kContextMenuCopyTitleName, _("&Copy Title Name")); + menu.Append(kContextMenuCopyTitleId, _("&Copy Title ID")); + menu.Append(kContextMenuCopyTitleImage, _("&Copy Title Image")); menu.AppendSeparator(); } } @@ -711,10 +722,44 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) break; } case kContextMenuCreateShortcut: + { #if BOOST_OS_LINUX || BOOST_OS_WINDOWS - CreateShortcut(gameInfo); + CreateShortcut(gameInfo); #endif break; + } + case kContextMenuCopyTitleName: + { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName())); + wxTheClipboard->Close(); + } + break; + } + case kContextMenuCopyTitleId: + { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData(new wxTextDataObject(fmt::format("{:016x}", gameInfo.GetBaseTitleId()))); + wxTheClipboard->Close(); + } + break; + } + case kContextMenuCopyTitleImage: + { + if (wxTheClipboard->Open()) + { + int icon_large; + int icon_small; + if (!QueryIconForTitle(title_id, icon_large, icon_small)) + break; + auto icon = m_image_list->GetBitmap(icon_large); + wxTheClipboard->SetData(new wxBitmapDataObject(icon)); + wxTheClipboard->Close(); + } + break; + } } } } @@ -1042,7 +1087,7 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) const auto region_text = fmt::format("{}", gameInfo.GetRegion()); SetItem(index, ColumnRegion, wxGetTranslation(region_text)); - SetItem(index, ColumnTitleID, fmt::format("{:016x}", titleId)); + SetItem(index, ColumnTitleID, fmt::format("{:016x}", baseTitleId)); } else if (m_style == Style::kIcons) { From 8d7fc98275f3eae1f2e105df678956b7540e65eb Mon Sep 17 00:00:00 2001 From: rawdatafeel <108900299+rawdatafeel@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:59:00 -0500 Subject: [PATCH 005/130] Improve BUILD.md (#1093) --- BUILD.md | 153 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 39 deletions(-) diff --git a/BUILD.md b/BUILD.md index e4993ca..034000a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,4 +1,26 @@ -# Build instructions +# Build Instructions + +## Table of Contents + +- [Windows](#windows) +- [Linux](#linux) + - [Dependencies](#dependencies) + - [For Arch and derivatives:](#for-arch-and-derivatives) + - [For Fedora and derivatives:](#for-fedora-and-derivatives) + - [For Ubuntu and derivatives](#for-ubuntu-and-derivatives) + - [Build Cemu](#build-cemu) + - [CMake and Clang](#cmake-and-clang) + - [GCC](#gcc) + - [Debug Build](#debug-build) + - [Troubleshooting Steps](#troubleshooting-steps) + - [Compiling Errors](#compiling-errors) + - [Building Errors](#building-errors) +- [macOS](#macos) + - [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used) + - [Installing brew](#installing-brew) + - [Installing Dependencies](#installing-dependencies) + - [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang) + - [Updating Cemu and source code](#updating-cemu-and-source-code) ## Windows @@ -19,17 +41,9 @@ Any other IDE should also work as long as it has CMake and MSVC support. CLion a ## Linux -To compile Cemu, a recent enough compiler and STL with C++20 support is required! clang-15 or higher is what we recommend. +To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend. -### Installing dependencies - -#### For Ubuntu and derivatives: -`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build` - -You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. - -At step 3 while building, use: - `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +### Dependencies #### For Arch and derivatives: `sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` @@ -37,39 +51,99 @@ At step 3 while building, use: #### For Fedora and derivatives: `sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` -### Build Cemu using cmake and clang -1. `git clone --recursive https://github.com/cemu-project/Cemu` -2. `cd Cemu` -3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` -4. `cmake --build build` -5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`. +#### For Ubuntu and derivatives: +`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` -#### Using GCC -While we build and test Cemu using clang, using GCC might work better with your distro (they should be fairly similar performance/issues wise and should only be considered if compilation is the issue). +You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. -You can use GCC by doing the following: -- make sure you have g++ installed in your system - - installation for Ubuntu and derivatives: `sudo apt install g++` - - installation for Fedora and derivatives: `sudo dnf install gcc-c++` -- replace the step 3 with the following: -`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja` +At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead: + `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` -#### Troubleshooting steps - - If step 3 gives you an error about not being able to find ninja, try appending `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` to the command and running it again. - - If step 3 fails while compiling the boost-build dependency, it means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact. - - If step 3 gives a random error, read the `[package-name-and-platform]-out.log` and `[package-name-and-platform]-err.log` for the actual reason to see if you might be lacking the headers from a dependency. - - If step 3 is still failing or if you're not able to find the cause, please make an issue on our Github about it! - - If step 3 fails during rebuild after `git pull` with an error that mentions RPATH, add this to the end of step 3: `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON` - - If step 4 gives you an error that contains something like `main.cpp.o: in function 'std::__cxx11::basic_string...`, you likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see below. - - If step 4 gives you a different error, you could report it to this repo or try using GCC. Just make sure your standard library and compilers are updated since Cemu uses a lot of modern features! - - If step 4 gives you undefined libdecor_xx, you are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile. - - If step 4 gives you `fatal error: 'span' file not found`, then you're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See #644. +### Build Cemu + +#### CMake and Clang + +``` +git clone --recursive https://github.com/cemu-project/Cemu +cd Cemu +cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja +cmake --build build +``` + +#### GCC + +If you are building using GCC, make sure you have g++ installed: +- Installation for Arch and derivatives: `sudo pacman -S gcc` +- Installation for Fedora and derivatives: `sudo dnf install gcc-c++` +- Installation for Ubuntu and derivatives: `sudo apt install g++` + +``` +git clone --recursive https://github.com/cemu-project/Cemu +cd Cemu +cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja +cmake --build build +``` + +#### Debug Build + +``` +git clone --recursive https://github.com/cemu-project/Cemu +cd Cemu +cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja +cmake --build build +``` + +If you are using GCC, replace `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` with `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja` + +#### Troubleshooting Steps + +##### Compiling Errors + +This section refers to running `cmake -S...` (truncated). + +* `vcpkg install failed` + * Run the following in the root directory and try running the command again (don't forget to change directories afterwards): + * `cd dependencies/vcpkg && git fetch --unshallow` +* `Please ensure you're using the latest port files with git pull and vcpkg update.` + * Either: + * Update vcpkg by running by the following command: + * `git submodule update --remote dependencies/vcpkg` + * If you are sure vcpkg is up to date, check the following logs: + * `Cemu/dependencies/vcpkg/buildtrees/wxwidgets/config-x64-linux-out.log` + * `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-meson-log.txt.log` + * `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-out.log` +* Not able to find Ninja. + * Add the following and try running the command again: + * `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +* Compiling failed during the boost-build dependency. + * It means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact. +* Compiling failed during rebuild after `git pull` with an error that mentions RPATH + * Add the following and try running the command again: + * `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON` +* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency. + + +If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features! + + +##### Building Errors + +This section refers to running `cmake --build build`. + +* `main.cpp.o: in function 'std::__cxx11::basic_string...` + * You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc). +* `fatal error: 'span' file not found` + * You're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See [#644](https://github.com/cemu-project/Cemu/issues/644). +* `undefined libdecor_xx` + * You are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile. + +If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features! ## macOS -To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and +To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics -API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the +API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the Molten-VK compatibility layer ### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used @@ -84,11 +158,11 @@ You can skip this section if you have an Intel Mac. Every time you compile, you 1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env -### Installing dependencies +### Installing Dependencies `brew install boost git cmake llvm ninja nasm molten-vk automake libtool` -### Build Cemu using cmake and clang +### Build Cemu using CMake and Clang 1. `git clone --recursive https://github.com/cemu-project/Cemu` 2. `cd Cemu` 3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja` @@ -104,3 +178,4 @@ You can skip this section if you have an Intel Mac. Every time you compile, you 2. Then, you can rebuild Cemu using the steps listed above, according to whether you use Linux or Windows. If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building. + From 3a02490a1f37c7f437b99a2ef459ab886d08d79f Mon Sep 17 00:00:00 2001 From: MoonlightWave-12 <123384363+MoonlightWave-12@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:12:09 +0100 Subject: [PATCH 006/130] BUILD.md: Mention Debian in the build-instructions for Ubuntu (#1096) --- BUILD.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/BUILD.md b/BUILD.md index 034000a..9f3a35b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -6,8 +6,8 @@ - [Linux](#linux) - [Dependencies](#dependencies) - [For Arch and derivatives:](#for-arch-and-derivatives) + - [For Debian, Ubuntu and derivatives](#for-debian-ubuntu-and-derivatives) - [For Fedora and derivatives:](#for-fedora-and-derivatives) - - [For Ubuntu and derivatives](#for-ubuntu-and-derivatives) - [Build Cemu](#build-cemu) - [CMake and Clang](#cmake-and-clang) - [GCC](#gcc) @@ -48,10 +48,7 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required #### For Arch and derivatives: `sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` -#### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` - -#### For Ubuntu and derivatives: +#### For Debian, Ubuntu and derivatives: `sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. @@ -59,6 +56,9 @@ You may also need to install `libusb-1.0-0-dev` as a workaround for an issue wit At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +#### For Fedora and derivatives: +`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` + ### Build Cemu #### CMake and Clang @@ -74,8 +74,8 @@ cmake --build build If you are building using GCC, make sure you have g++ installed: - Installation for Arch and derivatives: `sudo pacman -S gcc` +- Installation for Debian, Ubuntu and derivatives: `sudo apt install g++` - Installation for Fedora and derivatives: `sudo dnf install gcc-c++` -- Installation for Ubuntu and derivatives: `sudo apt install g++` ``` git clone --recursive https://github.com/cemu-project/Cemu From 96bbd3bd259eccb767be3a8a3dd406cdbff4b905 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:03:16 +0100 Subject: [PATCH 007/130] Latte: Avoid assert in texture view check --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 101 ++++++------------------ 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index d38af8e..707428a 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -790,81 +790,30 @@ enum VIEWCOMPATIBILITY VIEW_NOT_COMPATIBLE, }; -bool IsDimensionCompatibleForView(Latte::E_DIM baseDim, Latte::E_DIM viewDim) +bool IsDimensionCompatibleForGX2View(Latte::E_DIM baseDim, Latte::E_DIM viewDim) { - bool incompatibleDim = false; - if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D) - ; - else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_1D) - ; - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_CUBEMAP) - ; - else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) - ; - else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D_MSAA) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) - { - // not compatible on OpenGL - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) - { - // incompatible by default, but may be compatible if the view matches the depth of the base texture and starts at mip/slice 0 - incompatibleDim = true; - } - else if ((baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) || - (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D)) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D) - { - // not compatible (probably?) - incompatibleDim = true; - } - else - { - cemu_assert_debug(false); - incompatibleDim = true; - } - return !incompatibleDim; + // Note that some combinations depend on the exact view/slice index and count which we currently ignore (like a 3D view of a 3D texture) + bool isCompatible = + (baseDim == viewDim) || + (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D) || + (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) || + (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) || + (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) || + (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) || + (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY); + if(isCompatible) + return true; + // these combinations have been seen in use by games and are considered incompatible: + // (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) -> Not allowed on OpenGL + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA) + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D) + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D) + // (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D) + // (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) -> Only compatible if the same depth and shared at mip/slice 0 + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) + // (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D) + // (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D) + return false; } VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseTexture, uint32 physAddr, sint32 width, sint32 height, sint32 pitch, Latte::E_DIM dimView, Latte::E_GX2SURFFMT format, bool isDepth, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32& relativeMipIndex, sint32& relativeSliceIndex) @@ -881,7 +830,7 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT return VIEW_NOT_COMPATIBLE; // 3D views are only compatible on Vulkan if they match the base texture in regards to mip and slice count bool isCompatible3DView = dimView == Latte::E_DIM::DIM_3D && baseTexture->dim == dimView && firstSlice == 0 && firstMip == 0 && baseTexture->mipLevels == numMip && baseTexture->depth == numSlice; - if (!isCompatible3DView && !IsDimensionCompatibleForView(baseTexture->dim, dimView)) + if (!isCompatible3DView && !IsDimensionCompatibleForGX2View(baseTexture->dim, dimView)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { @@ -933,7 +882,7 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) ) return VIEW_NOT_COMPATIBLE; - if (!IsDimensionCompatibleForView(baseTexture->dim, dimView)) + if (!IsDimensionCompatibleForGX2View(baseTexture->dim, dimView)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { From 72ce4838ea79252f9ec0df3f3eeb5959ca6616e6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:07:03 +0100 Subject: [PATCH 008/130] Latte: Optimize uniform register array size for known shaders --- src/Cafe/HW/Latte/Core/LatteShader.cpp | 2 +- .../LatteDecompilerAnalyzer.cpp | 2 +- .../LatteDecompilerEmitGLSLHeader.hpp | 4 ++-- .../LatteDecompilerInternal.h | 18 +++++++++++------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index 503fb66..b59702c 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -652,7 +652,7 @@ LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompi } else { - shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister; + shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister; } // calculate aux hash if (calculateAuxHash) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index 2e83719..cf22f05 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -787,7 +787,7 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD continue; LatteDecompilerShader::QuickBufferEntry entry; entry.index = i; - entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16; + entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(shaderContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16; shader->list_quickBufferList.push_back(entry); } // get dimension of each used texture diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp index 21cae09..428f864 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp @@ -37,7 +37,7 @@ namespace LatteDecompiler } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { - uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(256); + uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(decompilerContext->shaderBaseHash, 256); // full or partial uniform register file has to be present if (shaderType == LatteConst::ShaderType::Vertex) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterVS[{}];" _CRLF, cfileSize); @@ -156,7 +156,7 @@ namespace LatteDecompiler shaderSrc->addFmt("uniform {}{}" _CRLF, _getShaderUniformBlockInterfaceName(decompilerContext->shaderType), i); shaderSrc->add("{" _CRLF); - shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE)); + shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(decompilerContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE)); shaderSrc->add("};" _CRLF _CRLF); shaderSrc->add(_CRLF); } diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h index ac2a1fe..ed1858b 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h @@ -157,19 +157,23 @@ struct LatteDecompilerBufferAccessTracker } } - sint32 DetermineSize(sint32 maximumSize) const + sint32 DetermineSize(uint64 shaderBaseHash, sint32 maximumSize) const { - // here we try to predict the accessed range so we dont have to upload the whole buffer - // potential risky optimization: assume that if there is a fixed-index access on an index higher than any other non-zero relative accesses, it bounds the prior relative access + // here we try to predict the accessed byte range so we dont have to upload the whole buffer + // if no bound can be determined then return maximumSize + // for some known shaders we use hand-tuned values instead of the maximumSize fallback value that those shaders would normally use + if(shaderBaseHash == 0x8ff56afdf1a2f837) // XCX text rendering + return 24; + if(shaderBaseHash == 0x37b9100c1310d3bb) // BotW UI backdrops 1 + return 24; + if(shaderBaseHash == 0xf7ba548c1fefe24a) // BotW UI backdrops 2 + return 30; + sint32 highestAccessIndex = -1; if(hasStaticIndexAccess) - { highestAccessIndex = highestAccessStaticIndex; - } if(hasDynamicIndexAccess) - { return maximumSize; // dynamic index exists and no bound can be determined - } if (highestAccessIndex < 0) return 1; // no access at all? But avoid zero as a size return highestAccessIndex + 1; From a63678c1f40c21151c6daa6f20cbb8fc600ae92a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:10:35 +0100 Subject: [PATCH 009/130] Update SDL2 vcpkg port to 2.30.0 --- .../sdl2/alsa-dep-fix.patch | 13 ++ .../vcpkg_overlay_ports/sdl2/deps.patch | 13 ++ .../vcpkg_overlay_ports/sdl2/portfile.cmake | 137 ++++++++++++++++++ dependencies/vcpkg_overlay_ports/sdl2/usage | 8 + .../vcpkg_overlay_ports/sdl2/vcpkg.json | 68 +++++++++ .../sdl2/alsa-dep-fix.patch | 13 ++ .../vcpkg_overlay_ports_linux/sdl2/deps.patch | 13 ++ .../sdl2/portfile.cmake | 137 ++++++++++++++++++ .../vcpkg_overlay_ports_linux/sdl2/usage | 8 + .../vcpkg_overlay_ports_linux/sdl2/vcpkg.json | 68 +++++++++ .../sdl2/alsa-dep-fix.patch | 13 ++ .../vcpkg_overlay_ports_mac/sdl2/deps.patch | 13 ++ .../sdl2/portfile.cmake | 137 ++++++++++++++++++ .../vcpkg_overlay_ports_mac/sdl2/usage | 8 + .../vcpkg_overlay_ports_mac/sdl2/vcpkg.json | 68 +++++++++ 15 files changed, 717 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/deps.patch create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/usage create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/usage create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/usage create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch new file mode 100644 index 0000000..5b2c77b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch @@ -0,0 +1,13 @@ +diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in +index cc8bcf26d..ead829767 100644 +--- a/SDL2Config.cmake.in ++++ b/SDL2Config.cmake.in +@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") + + set(SDL_ALSA @SDL_ALSA@) + set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) +-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) ++if(SDL_ALSA) + sdlFindALSA() + endif() + unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch new file mode 100644 index 0000000..a8637d8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake +index 65a98efbe..2f99f28f1 100644 +--- a/cmake/sdlchecks.cmake ++++ b/cmake/sdlchecks.cmake +@@ -352,7 +352,7 @@ endmacro() + # - HAVE_SDL_LOADSO opt + macro(CheckLibSampleRate) + if(SDL_LIBSAMPLERATE) +- find_package(SampleRate QUIET) ++ find_package(SampleRate CONFIG REQUIRED) + if(SampleRate_FOUND AND TARGET SampleRate::samplerate) + set(HAVE_LIBSAMPLERATE TRUE) + set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake new file mode 100644 index 0000000..22685e6 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake @@ -0,0 +1,137 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libsdl-org/SDL + REF "release-${VERSION}" + SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c + HEAD_REF main + PATCHES + deps.patch + alsa-dep-fix.patch +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + alsa SDL_ALSA + alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA + ibus SDL_IBUS + samplerate SDL_LIBSAMPLERATE + vulkan SDL_VULKAN + wayland SDL_WAYLAND + x11 SDL_X11 + INVERTED_FEATURES + alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA +) + +if ("x11" IN_LIST FEATURES) + message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") +endif() +if ("wayland" IN_LIST FEATURES) + message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") +endif() +if ("ibus" IN_LIST FEATURES) + message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") +endif() + +if(VCPKG_TARGET_IS_UWP) + set(configure_opts WINDOWS_USE_MSBUILD) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + ${configure_opts} + OPTIONS ${FEATURE_OPTIONS} + -DSDL_STATIC=${SDL_STATIC} + -DSDL_SHARED=${SDL_SHARED} + -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} + -DSDL_LIBC=ON + -DSDL_TEST=OFF + -DSDL_INSTALL_CMAKEDIR="cmake" + -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + -DSDL_LIBSAMPLERATE_SHARED=OFF + MAYBE_UNUSED_VARIABLES + SDL_FORCE_STATIC_VCRT + PKG_CONFIG_USE_CMAKE_PREFIX_PATH +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH cmake) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/share/licenses" + "${CURRENT_PACKAGES_DIR}/share/aclocal" +) + +file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") +if(NOT BINS) + file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/bin" + "${CURRENT_PACKAGES_DIR}/debug/bin" + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") + endif() + + file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") + foreach(SHARE_FILE ${SHARE_FILES}) + vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") + endforeach() +endif() + +vcpkg_copy_pdbs() + +set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") +set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) +string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") +string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") + +if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports/sdl2/usage b/dependencies/vcpkg_overlay_ports/sdl2/usage new file mode 100644 index 0000000..1cddcd4 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/usage @@ -0,0 +1,8 @@ +sdl2 provides CMake targets: + + find_package(SDL2 CONFIG REQUIRED) + target_link_libraries(main + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json new file mode 100644 index 0000000..1f46037 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json @@ -0,0 +1,68 @@ +{ + "name": "sdl2", + "version": "2.30.0", + "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", + "homepage": "https://www.libsdl.org/download-2.0.php", + "license": "Zlib", + "dependencies": [ + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + { + "name": "ibus", + "platform": "linux" + }, + { + "name": "wayland", + "platform": "linux" + }, + { + "name": "x11", + "platform": "linux" + } + ], + "features": { + "alsa": { + "description": "Support for alsa audio", + "dependencies": [ + { + "name": "alsa", + "platform": "linux" + } + ] + }, + "ibus": { + "description": "Build with ibus IME support", + "supports": "linux" + }, + "samplerate": { + "description": "Use libsamplerate for audio rate conversion", + "dependencies": [ + "libsamplerate" + ] + }, + "vulkan": { + "description": "Vulkan functionality for SDL" + }, + "wayland": { + "description": "Build with Wayland support", + "supports": "linux" + }, + "x11": { + "description": "Build with X11 support", + "supports": "!windows" + } + } +} diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch new file mode 100644 index 0000000..5b2c77b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch @@ -0,0 +1,13 @@ +diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in +index cc8bcf26d..ead829767 100644 +--- a/SDL2Config.cmake.in ++++ b/SDL2Config.cmake.in +@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") + + set(SDL_ALSA @SDL_ALSA@) + set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) +-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) ++if(SDL_ALSA) + sdlFindALSA() + endif() + unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch new file mode 100644 index 0000000..a8637d8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake +index 65a98efbe..2f99f28f1 100644 +--- a/cmake/sdlchecks.cmake ++++ b/cmake/sdlchecks.cmake +@@ -352,7 +352,7 @@ endmacro() + # - HAVE_SDL_LOADSO opt + macro(CheckLibSampleRate) + if(SDL_LIBSAMPLERATE) +- find_package(SampleRate QUIET) ++ find_package(SampleRate CONFIG REQUIRED) + if(SampleRate_FOUND AND TARGET SampleRate::samplerate) + set(HAVE_LIBSAMPLERATE TRUE) + set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake new file mode 100644 index 0000000..22685e6 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake @@ -0,0 +1,137 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libsdl-org/SDL + REF "release-${VERSION}" + SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c + HEAD_REF main + PATCHES + deps.patch + alsa-dep-fix.patch +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + alsa SDL_ALSA + alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA + ibus SDL_IBUS + samplerate SDL_LIBSAMPLERATE + vulkan SDL_VULKAN + wayland SDL_WAYLAND + x11 SDL_X11 + INVERTED_FEATURES + alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA +) + +if ("x11" IN_LIST FEATURES) + message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") +endif() +if ("wayland" IN_LIST FEATURES) + message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") +endif() +if ("ibus" IN_LIST FEATURES) + message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") +endif() + +if(VCPKG_TARGET_IS_UWP) + set(configure_opts WINDOWS_USE_MSBUILD) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + ${configure_opts} + OPTIONS ${FEATURE_OPTIONS} + -DSDL_STATIC=${SDL_STATIC} + -DSDL_SHARED=${SDL_SHARED} + -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} + -DSDL_LIBC=ON + -DSDL_TEST=OFF + -DSDL_INSTALL_CMAKEDIR="cmake" + -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + -DSDL_LIBSAMPLERATE_SHARED=OFF + MAYBE_UNUSED_VARIABLES + SDL_FORCE_STATIC_VCRT + PKG_CONFIG_USE_CMAKE_PREFIX_PATH +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH cmake) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/share/licenses" + "${CURRENT_PACKAGES_DIR}/share/aclocal" +) + +file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") +if(NOT BINS) + file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/bin" + "${CURRENT_PACKAGES_DIR}/debug/bin" + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") + endif() + + file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") + foreach(SHARE_FILE ${SHARE_FILES}) + vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") + endforeach() +endif() + +vcpkg_copy_pdbs() + +set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") +set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) +string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") +string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") + +if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/usage b/dependencies/vcpkg_overlay_ports_linux/sdl2/usage new file mode 100644 index 0000000..1cddcd4 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/usage @@ -0,0 +1,8 @@ +sdl2 provides CMake targets: + + find_package(SDL2 CONFIG REQUIRED) + target_link_libraries(main + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json new file mode 100644 index 0000000..1f46037 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json @@ -0,0 +1,68 @@ +{ + "name": "sdl2", + "version": "2.30.0", + "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", + "homepage": "https://www.libsdl.org/download-2.0.php", + "license": "Zlib", + "dependencies": [ + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + { + "name": "ibus", + "platform": "linux" + }, + { + "name": "wayland", + "platform": "linux" + }, + { + "name": "x11", + "platform": "linux" + } + ], + "features": { + "alsa": { + "description": "Support for alsa audio", + "dependencies": [ + { + "name": "alsa", + "platform": "linux" + } + ] + }, + "ibus": { + "description": "Build with ibus IME support", + "supports": "linux" + }, + "samplerate": { + "description": "Use libsamplerate for audio rate conversion", + "dependencies": [ + "libsamplerate" + ] + }, + "vulkan": { + "description": "Vulkan functionality for SDL" + }, + "wayland": { + "description": "Build with Wayland support", + "supports": "linux" + }, + "x11": { + "description": "Build with X11 support", + "supports": "!windows" + } + } +} diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch new file mode 100644 index 0000000..5b2c77b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch @@ -0,0 +1,13 @@ +diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in +index cc8bcf26d..ead829767 100644 +--- a/SDL2Config.cmake.in ++++ b/SDL2Config.cmake.in +@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") + + set(SDL_ALSA @SDL_ALSA@) + set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) +-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) ++if(SDL_ALSA) + sdlFindALSA() + endif() + unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch new file mode 100644 index 0000000..a8637d8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake +index 65a98efbe..2f99f28f1 100644 +--- a/cmake/sdlchecks.cmake ++++ b/cmake/sdlchecks.cmake +@@ -352,7 +352,7 @@ endmacro() + # - HAVE_SDL_LOADSO opt + macro(CheckLibSampleRate) + if(SDL_LIBSAMPLERATE) +- find_package(SampleRate QUIET) ++ find_package(SampleRate CONFIG REQUIRED) + if(SampleRate_FOUND AND TARGET SampleRate::samplerate) + set(HAVE_LIBSAMPLERATE TRUE) + set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake new file mode 100644 index 0000000..22685e6 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake @@ -0,0 +1,137 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libsdl-org/SDL + REF "release-${VERSION}" + SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c + HEAD_REF main + PATCHES + deps.patch + alsa-dep-fix.patch +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + alsa SDL_ALSA + alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA + ibus SDL_IBUS + samplerate SDL_LIBSAMPLERATE + vulkan SDL_VULKAN + wayland SDL_WAYLAND + x11 SDL_X11 + INVERTED_FEATURES + alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA +) + +if ("x11" IN_LIST FEATURES) + message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") +endif() +if ("wayland" IN_LIST FEATURES) + message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") +endif() +if ("ibus" IN_LIST FEATURES) + message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") +endif() + +if(VCPKG_TARGET_IS_UWP) + set(configure_opts WINDOWS_USE_MSBUILD) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + ${configure_opts} + OPTIONS ${FEATURE_OPTIONS} + -DSDL_STATIC=${SDL_STATIC} + -DSDL_SHARED=${SDL_SHARED} + -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} + -DSDL_LIBC=ON + -DSDL_TEST=OFF + -DSDL_INSTALL_CMAKEDIR="cmake" + -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + -DSDL_LIBSAMPLERATE_SHARED=OFF + MAYBE_UNUSED_VARIABLES + SDL_FORCE_STATIC_VCRT + PKG_CONFIG_USE_CMAKE_PREFIX_PATH +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH cmake) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/share/licenses" + "${CURRENT_PACKAGES_DIR}/share/aclocal" +) + +file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") +if(NOT BINS) + file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/bin" + "${CURRENT_PACKAGES_DIR}/debug/bin" + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") + endif() + + file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") + foreach(SHARE_FILE ${SHARE_FILES}) + vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") + endforeach() +endif() + +vcpkg_copy_pdbs() + +set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") +set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) +string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") +string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") + +if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage new file mode 100644 index 0000000..1cddcd4 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage @@ -0,0 +1,8 @@ +sdl2 provides CMake targets: + + find_package(SDL2 CONFIG REQUIRED) + target_link_libraries(main + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json new file mode 100644 index 0000000..1f46037 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json @@ -0,0 +1,68 @@ +{ + "name": "sdl2", + "version": "2.30.0", + "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", + "homepage": "https://www.libsdl.org/download-2.0.php", + "license": "Zlib", + "dependencies": [ + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + { + "name": "ibus", + "platform": "linux" + }, + { + "name": "wayland", + "platform": "linux" + }, + { + "name": "x11", + "platform": "linux" + } + ], + "features": { + "alsa": { + "description": "Support for alsa audio", + "dependencies": [ + { + "name": "alsa", + "platform": "linux" + } + ] + }, + "ibus": { + "description": "Build with ibus IME support", + "supports": "linux" + }, + "samplerate": { + "description": "Use libsamplerate for audio rate conversion", + "dependencies": [ + "libsamplerate" + ] + }, + "vulkan": { + "description": "Vulkan functionality for SDL" + }, + "wayland": { + "description": "Build with Wayland support", + "supports": "linux" + }, + "x11": { + "description": "Build with X11 support", + "supports": "!windows" + } + } +} From 8b37e316d0537da9c717cb0698c9141e668d6fff Mon Sep 17 00:00:00 2001 From: Leif Liddy Date: Sat, 24 Feb 2024 20:47:06 +0100 Subject: [PATCH 010/130] BUILD.md: Add llvm package for Fedora (#1101) --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 9f3a35b..3ff2254 100644 --- a/BUILD.md +++ b/BUILD.md @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` +`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel` ### Build Cemu @@ -128,7 +128,7 @@ If you are getting a different error than any of the errors listed above, you ma ##### Building Errors -This section refers to running `cmake --build build`. +This section refers to running `cmake --build build`. * `main.cpp.o: in function 'std::__cxx11::basic_string...` * You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc). From 49c55a3f561eed2da750cbacfcef4fc5ffe1075e Mon Sep 17 00:00:00 2001 From: Simon <113838661+ssievert42@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:37:36 +0100 Subject: [PATCH 011/130] nsyshid: remove stray print statements (#1106) --- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp | 1 - src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 520a0d3..23da579 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -446,7 +446,6 @@ namespace nsyshid::backend::windows { sprintf(debugOutput + i * 3, "%02x ", data[i]); } - fmt::print("{} Data: {}\n", prefix, debugOutput); cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); } } // namespace nsyshid::backend::windows diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index b21e2a4..ba3e3b9 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -332,7 +332,6 @@ namespace nsyshid { sprintf(debugOutput + i * 3, "%02x ", data[i]); } - fmt::print("{} Data: {}\n", prefix, debugOutput); cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); } From 8f1cd4f9255e16aeddb2e72d35a47f37e1e478bc Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:52:33 +0100 Subject: [PATCH 012/130] Vulkan: Update some code to use VK_KHR_synchronization2 --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 30 ++- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 220 +++++++++--------- .../Renderer/Vulkan/VulkanRendererCore.cpp | 35 +-- 3 files changed, 140 insertions(+), 145 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 616f57e..631f1d0 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -468,6 +468,15 @@ VulkanRenderer::VulkanRenderer() void* deviceExtensionFeatures = nullptr; + // enable VK_KHR_synchonization_2 + VkPhysicalDeviceSynchronization2FeaturesKHR sync2Feature{}; + { + sync2Feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR; + sync2Feature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &sync2Feature; + sync2Feature.synchronization2 = VK_TRUE; + } + // enable VK_EXT_pipeline_creation_cache_control VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT cacheControlFeature{}; if (m_featureControl.deviceExtensions.pipeline_creation_cache_control) @@ -2852,13 +2861,20 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu ClearColorbuffer(padView); // barrier for input texture - VkMemoryBarrier memoryBarrier{}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; - VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - memoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + { + VkMemoryBarrier2 memoryBarrier2{}; + memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR; + memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR; + memoryBarrier2.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; + memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT; + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.dependencyFlags = 0; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + } auto pipeline = backbufferBlit_createGraphicsPipeline(m_swapchainDescriptorSetLayout, padView, shader); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index b61a0b4..7565d26 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -728,201 +728,192 @@ private: IMAGE_READ = 0x20, IMAGE_WRITE = 0x40, - }; template - void barrier_calcStageAndMask(VkPipelineStageFlags& stages, VkAccessFlags& accessFlags) + void barrier_calcStageAndMask(VkPipelineStageFlags2& stages, VkAccessFlags2& accessFlags) { stages = 0; accessFlags = 0; if constexpr ((TSyncOp & BUFFER_SHADER_READ) != 0) { - // in theory: VK_ACCESS_INDEX_READ_BIT should be set here too but indices are currently separated - stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + // in theory: VK_ACCESS_2_INDEX_READ_BIT should be set here too but indices are currently separated + stages |= VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; } - + if constexpr ((TSyncOp & BUFFER_SHADER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_2_SHADER_WRITE_BIT; } if constexpr ((TSyncOp & ANY_TRANSFER) != 0) { - //stages |= VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; - //accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; - stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; - accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - - //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; - //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; } if constexpr ((TSyncOp & TRANSFER_READ) != 0) { - stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; - accessFlags |= VK_ACCESS_TRANSFER_READ_BIT; - - //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT; } if constexpr ((TSyncOp & TRANSFER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; - accessFlags |= VK_ACCESS_TRANSFER_WRITE_BIT; - - //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + accessFlags |= VK_ACCESS_2_TRANSFER_WRITE_BIT; } if constexpr ((TSyncOp & HOST_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_HOST_BIT; - accessFlags |= VK_ACCESS_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_HOST_BIT; + accessFlags |= VK_ACCESS_2_HOST_WRITE_BIT; } if constexpr ((TSyncOp & HOST_READ) != 0) { - stages |= VK_PIPELINE_STAGE_HOST_BIT; - accessFlags |= VK_ACCESS_HOST_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_HOST_BIT; + accessFlags |= VK_ACCESS_2_HOST_READ_BIT; } if constexpr ((TSyncOp & IMAGE_READ) != 0) { - stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_SHADER_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_2_SHADER_READ_BIT; - stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT; - stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } if constexpr ((TSyncOp & IMAGE_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; - stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } } template void barrier_bufferRange(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) { - VkBufferMemoryBarrier bufMemBarrier{}; - bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + VkBufferMemoryBarrier2 bufMemBarrier{}; + bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; bufMemBarrier.pNext = nullptr; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - VkPipelineStageFlags srcStages = 0; - VkPipelineStageFlags dstStages = 0; - - bufMemBarrier.srcAccessMask = 0; - bufMemBarrier.dstAccessMask = 0; - - barrier_calcStageAndMask(srcStages, bufMemBarrier.srcAccessMask); - barrier_calcStageAndMask(dstStages, bufMemBarrier.dstAccessMask); + barrier_calcStageAndMask(bufMemBarrier.srcStageMask, bufMemBarrier.srcAccessMask); + barrier_calcStageAndMask(bufMemBarrier.dstStageMask, bufMemBarrier.dstAccessMask); bufMemBarrier.buffer = buffer; bufMemBarrier.offset = offset; bufMemBarrier.size = size; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); + + VkDependencyInfo depInfo{}; + depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depInfo.pNext = nullptr; + depInfo.bufferMemoryBarrierCount = 1; + depInfo.pBufferMemoryBarriers = &bufMemBarrier; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &depInfo); } template void barrier_bufferRange(VkBuffer bufferA, VkDeviceSize offsetA, VkDeviceSize sizeA, VkBuffer bufferB, VkDeviceSize offsetB, VkDeviceSize sizeB) { - VkPipelineStageFlags srcStagesA = 0; - VkPipelineStageFlags dstStagesA = 0; - VkPipelineStageFlags srcStagesB = 0; - VkPipelineStageFlags dstStagesB = 0; + VkBufferMemoryBarrier2 bufMemBarrier2[2] = {}; + bufMemBarrier2[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + bufMemBarrier2[0].pNext = nullptr; + bufMemBarrier2[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[0].buffer = bufferA; + bufMemBarrier2[0].offset = offsetA; + bufMemBarrier2[0].size = sizeA; + barrier_calcStageAndMask(bufMemBarrier2[0].srcStageMask, bufMemBarrier2[0].srcAccessMask); + barrier_calcStageAndMask(bufMemBarrier2[0].dstStageMask, bufMemBarrier2[0].dstAccessMask); - VkBufferMemoryBarrier bufMemBarrier[2]; + bufMemBarrier2[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + bufMemBarrier2[1].pNext = nullptr; + bufMemBarrier2[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[1].buffer = bufferB; + bufMemBarrier2[1].offset = offsetB; + bufMemBarrier2[1].size = sizeB; + barrier_calcStageAndMask(bufMemBarrier2[1].srcStageMask, bufMemBarrier2[1].srcAccessMask); + barrier_calcStageAndMask(bufMemBarrier2[1].dstStageMask, bufMemBarrier2[1].dstAccessMask); - bufMemBarrier[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - bufMemBarrier[0].pNext = nullptr; - bufMemBarrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[0].srcAccessMask = 0; - bufMemBarrier[0].dstAccessMask = 0; - barrier_calcStageAndMask(srcStagesA, bufMemBarrier[0].srcAccessMask); - barrier_calcStageAndMask(dstStagesA, bufMemBarrier[0].dstAccessMask); - bufMemBarrier[0].buffer = bufferA; - bufMemBarrier[0].offset = offsetA; - bufMemBarrier[0].size = sizeA; - - bufMemBarrier[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - bufMemBarrier[1].pNext = nullptr; - bufMemBarrier[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[1].srcAccessMask = 0; - bufMemBarrier[1].dstAccessMask = 0; - barrier_calcStageAndMask(srcStagesB, bufMemBarrier[1].srcAccessMask); - barrier_calcStageAndMask(dstStagesB, bufMemBarrier[1].dstAccessMask); - bufMemBarrier[1].buffer = bufferB; - bufMemBarrier[1].offset = offsetB; - bufMemBarrier[1].size = sizeB; - - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStagesA|srcStagesB, dstStagesA|dstStagesB, 0, 0, nullptr, 2, bufMemBarrier, 0, nullptr); + VkDependencyInfo dependencyInfo = {}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.pNext = nullptr; + dependencyInfo.bufferMemoryBarrierCount = 2; + dependencyInfo.pBufferMemoryBarriers = bufMemBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } void barrier_sequentializeTransfer() { - VkMemoryBarrier memBarrier{}; - memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - memBarrier.pNext = nullptr; + VkMemoryBarrier2 memoryBarrier2{}; + memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memoryBarrier2.pNext = nullptr; + memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + memoryBarrier2.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; + memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; - VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; - VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - - memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - memBarrier.dstAccessMask = 0; - - memBarrier.srcAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); - memBarrier.dstAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); - - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 1, &memBarrier, 0, nullptr, 0, nullptr); + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.pNext = nullptr; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier2; + dependencyInfo.bufferMemoryBarrierCount = 0; + dependencyInfo.imageMemoryBarrierCount = 0; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } void barrier_sequentializeCommand() { - VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkMemoryBarrier2 memoryBarrier = {}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memoryBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR; + memoryBarrier.srcAccessMask = 0; + memoryBarrier.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR; + memoryBarrier.dstAccessMask = 0; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 0, nullptr); + VkDependencyInfo dependencyInfo = {}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.dependencyFlags = 0; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } template void barrier_image(VkImage imageVk, VkImageSubresourceRange& subresourceRange, VkImageLayout oldLayout, VkImageLayout newLayout) { - VkPipelineStageFlags srcStages = 0; - VkPipelineStageFlags dstStages = 0; + VkImageMemoryBarrier2 imageMemBarrier2{}; + imageMemBarrier2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + imageMemBarrier2.oldLayout = oldLayout; + imageMemBarrier2.newLayout = newLayout; + imageMemBarrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier2.image = imageVk; + imageMemBarrier2.subresourceRange = subresourceRange; - VkImageMemoryBarrier imageMemBarrier{}; - imageMemBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - imageMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier.srcAccessMask = 0; - imageMemBarrier.dstAccessMask = 0; - barrier_calcStageAndMask(srcStages, imageMemBarrier.srcAccessMask); - barrier_calcStageAndMask(dstStages, imageMemBarrier.dstAccessMask); - imageMemBarrier.image = imageVk; - imageMemBarrier.subresourceRange = subresourceRange; - imageMemBarrier.oldLayout = oldLayout; - imageMemBarrier.newLayout = newLayout; + barrier_calcStageAndMask(imageMemBarrier2.srcStageMask, imageMemBarrier2.srcAccessMask); + barrier_calcStageAndMask(imageMemBarrier2.dstStageMask, imageMemBarrier2.dstAccessMask); - vkCmdPipelineBarrier(m_state.currentCommandBuffer, - srcStages, dstStages, - 0, - 0, NULL, - 0, NULL, - 1, &imageMemBarrier); + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &imageMemBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } template @@ -942,7 +933,6 @@ private: vkTexture->SetImageLayout(subresourceRange, newLayout); } - public: bool GetDisableMultithreadedCompilation() const { return m_featureControl.disableMultithreadedCompilation; } bool UseTFViaSSBO() const { return m_featureControl.mode.useTFEmulationViaSSBO; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 320357f..b6cae7f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1033,29 +1033,18 @@ void VulkanRenderer::sync_inputTexturesChanged() // barrier here if (writeFlushRequired) { - VkMemoryBarrier memoryBarrier{}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - memoryBarrier.srcAccessMask = 0; - memoryBarrier.dstAccessMask = 0; - - VkPipelineStageFlags srcStage = 0; - VkPipelineStageFlags dstStage = 0; - - // src - srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - // dst - dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - - dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + VkMemoryBarrier2 memoryBarrier2{}; + memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR; + memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; + memoryBarrier2.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR; + memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; + memoryBarrier2.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_SHADER_READ_BIT_KHR; + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR; + dependencyInfo.dependencyFlags = 0; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); From b8d81283e86f91238a166b25ac46281620a3260b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 27 Feb 2024 00:15:43 +0100 Subject: [PATCH 013/130] Vulkan: Remove unnecessary index buffer for backbuffer drawcall --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 18 +----------------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 7 ------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 631f1d0..a86d3a1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -540,7 +540,6 @@ VulkanRenderer::VulkanRenderer() QueryMemoryInfo(); QueryAvailableFormats(); - CreateBackbufferIndexBuffer(); CreateCommandPool(); CreateCommandBuffers(); CreateDescriptorPool(); @@ -624,7 +623,6 @@ VulkanRenderer::~VulkanRenderer() DeleteNullObjects(); // delete buffers - memoryManager->DeleteBuffer(m_indexBuffer, m_indexBufferMemory); memoryManager->DeleteBuffer(m_uniformVarBuffer, m_uniformVarBufferMemory); memoryManager->DeleteBuffer(m_textureReadbackBuffer, m_textureReadbackBufferMemory); memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory); @@ -2836,18 +2834,6 @@ void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceInde vkTexture->SetImageLayout(subresourceRange, outputLayout); } -void VulkanRenderer::CreateBackbufferIndexBuffer() -{ - const VkDeviceSize bufferSize = sizeof(uint16) * 6; - memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_indexBuffer, m_indexBufferMemory); - - uint16* data; - vkMapMemory(m_logicalDevice, m_indexBufferMemory, 0, bufferSize, 0, (void**)&data); - const uint16 tmp[] = { 0, 1, 2, 3, 4, 5 }; - std::copy(std::begin(tmp), std::end(tmp), data); - vkUnmapMemory(m_logicalDevice, m_indexBufferMemory); -} - void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) { if(!AcquireNextSwapchainImage(!padView)) @@ -2906,11 +2892,9 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); m_state.currentPipeline = pipeline; - vkCmdBindIndexBuffer(m_state.currentCommandBuffer, m_indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr); - vkCmdDrawIndexed(m_state.currentCommandBuffer, 6, 1, 0, 0, 0); + vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); vkCmdEndRenderPass(m_state.currentCommandBuffer); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 7565d26..3e55fc0 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -548,15 +548,11 @@ private: void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); // command buffer - VkCommandBuffer getCurrentCommandBuffer() const { return m_state.currentCommandBuffer; } // uniform void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader); - // indices - void CreateBackbufferIndexBuffer(); - // misc void CreatePipelineCache(); VkPipelineShaderStageCreateInfo CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const; @@ -580,9 +576,6 @@ private: void occlusionQuery_notifyBeginCommandBuffer(); private: - VkBuffer m_indexBuffer = VK_NULL_HANDLE; - VkDeviceMemory m_indexBufferMemory = VK_NULL_HANDLE; - std::vector m_layerNames; VkInstance m_instance = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; From 9f9bc9865f23d3a1f07ce905003b7d5d640aab82 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Mar 2024 03:12:26 +0100 Subject: [PATCH 014/130] Vulkan: Avoid calling vkCmdClearColorImage() on compressed textures This is not allowed according to the spec and can crash drivers. Fixes #1100 --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 58 ++++++++++++------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 - 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a86d3a1..bb83607 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1412,8 +1412,7 @@ bool VulkanRenderer::IsSwapchainInfoValid(bool mainWindow) const void VulkanRenderer::CreateNullTexture(NullTexture& nullTex, VkImageType imageType) { - // these are used when the game requests NULL ptr textures or buffers - // texture + // these are used when the game requests NULL ptr textures VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; if (imageType == VK_IMAGE_TYPE_1D) @@ -2818,6 +2817,35 @@ void VulkanRenderer::ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout) { + if(vkTexture->isDepth) + { + cemu_assert_suspicious(); + return; + } + if (vkTexture->IsCompressedFormat()) + { + // vkCmdClearColorImage cannot be called on compressed formats + // for now we ignore affected clears but still transition the image to the correct layout + auto imageObj = vkTexture->GetImageObj(); + imageObj->flagForCurrentCommandBuffer(); + VkImageSubresourceLayers subresourceRange{}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.mipLevel = mipIndex; + subresourceRange.baseArrayLayer = sliceIndex; + subresourceRange.layerCount = 1; + barrier_image(vkTexture, subresourceRange, outputLayout); + if(color.float32[0] == 0.0f && color.float32[1] == 0.0f && color.float32[2] == 0.0f && color.float32[3] == 0.0f) + { + static bool dbgMsgPrinted = false; + if(!dbgMsgPrinted) + { + cemuLog_logDebug(LogType::Force, "Unsupported compressed texture clear to zero"); + dbgMsgPrinted = true; + } + } + return; + } + VkImageSubresourceRange subresourceRange; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -3154,32 +3182,18 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI else { cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); - if (hostTexture->IsCompressedFormat()) - { - auto imageObj = vkTexture->GetImageObj(); - imageObj->flagForCurrentCommandBuffer(); - - cemuLog_logDebug(LogType::Force, "Compressed texture ({}/{} fmt {:04x}) unsupported clear", vkTexture->width, vkTexture->height, (uint32)vkTexture->format); - - VkImageSubresourceLayers subresourceRange{}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.mipLevel = mipIndex; - subresourceRange.baseArrayLayer = sliceIndex; - subresourceRange.layerCount = 1; - barrier_image(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); - } - else - { - ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); - } + ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); } } void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { auto vkTexture = (LatteTextureVk*)hostTexture; - cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); - ClearColorImage(vkTexture, sliceIndex, mipIndex, { r,g,b,a }, VK_IMAGE_LAYOUT_GENERAL); + if(vkTexture->dim == Latte::E_DIM::DIM_3D) + { + cemu_assert_unimplemented(); + } + ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL); } void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 3e55fc0..d4eda78 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -130,7 +130,6 @@ class VulkanRenderer : public Renderer using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices; static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB - static const inline int INDEX_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MB static const inline int TEXTURE_READBACK_SIZE = 32 * 1024 * 1024; // 32 MB From ea68f787eb6b8054805502a8b4aabae08ae59d94 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:41:01 +0100 Subject: [PATCH 015/130] Vulkan: For MSAA surface copies make the target MSAA too Fixes #1108 --- src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp | 2 +- src/Cafe/HW/Latte/ISA/LatteReg.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp index 4f5b24a..45be684 100644 --- a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp @@ -37,7 +37,7 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s if (!destinationTexture) { LatteTexture* renderTargetConf = nullptr; - destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::E_DIM::DIM_2D, false); + destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::IsMSAA(dstDim) ? Latte::E_DIM::DIM_2D_MSAA : Latte::E_DIM::DIM_2D, false); destinationTexture = destinationView->baseTexture; } // copy texture diff --git a/src/Cafe/HW/Latte/ISA/LatteReg.h b/src/Cafe/HW/Latte/ISA/LatteReg.h index d571dc6..d1a2a02 100644 --- a/src/Cafe/HW/Latte/ISA/LatteReg.h +++ b/src/Cafe/HW/Latte/ISA/LatteReg.h @@ -345,6 +345,11 @@ namespace Latte return IsCompressedFormat((Latte::E_HWSURFFMT)((uint32)format & 0x3F)); } + inline bool IsMSAA(Latte::E_DIM dim) + { + return dim == E_DIM::DIM_2D_MSAA || dim == E_DIM::DIM_2D_ARRAY_MSAA; + } + enum GPU_LIMITS { NUM_VERTEX_BUFFERS = 16, From b390023bc5b04a0b50c6fd2a1bc10feb19a22f59 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:48:59 +0100 Subject: [PATCH 016/130] README.md: Fix minor ambiguity --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e57cb48..dfd3579 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Before submitting a pull request, please read and follow our code style guidelin If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated! -Questions about Cemu's software architecture can also be answered on Discord (through the Matrix bridge). +Questions about Cemu's software architecture can also be answered on Discord (or through the Matrix bridge). ## License Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively. From d9e8ca2c833e2b2adf8f1c1cc71f7846fd87b816 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:25:40 +0100 Subject: [PATCH 017/130] Revert "Vulkan: Update some code to use VK_KHR_synchronization2" This reverts commit 8f1cd4f9255e16aeddb2e72d35a47f37e1e478bc. We received reports from users stuck with Vulkan drivers from 2019. (E.g. Kepler on Windows). So let's not unnecessarily increase the Vulkan requirement for now and postpone this to after the next stable release --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 30 +-- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 220 +++++++++--------- .../Renderer/Vulkan/VulkanRendererCore.cpp | 35 ++- 3 files changed, 145 insertions(+), 140 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index bb83607..8711359 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -468,15 +468,6 @@ VulkanRenderer::VulkanRenderer() void* deviceExtensionFeatures = nullptr; - // enable VK_KHR_synchonization_2 - VkPhysicalDeviceSynchronization2FeaturesKHR sync2Feature{}; - { - sync2Feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR; - sync2Feature.pNext = deviceExtensionFeatures; - deviceExtensionFeatures = &sync2Feature; - sync2Feature.synchronization2 = VK_TRUE; - } - // enable VK_EXT_pipeline_creation_cache_control VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT cacheControlFeature{}; if (m_featureControl.deviceExtensions.pipeline_creation_cache_control) @@ -2875,20 +2866,13 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu ClearColorbuffer(padView); // barrier for input texture - { - VkMemoryBarrier2 memoryBarrier2{}; - memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; - memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR; - memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR; - memoryBarrier2.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; - memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.dependencyFlags = 0; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); - } + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + memoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); auto pipeline = backbufferBlit_createGraphicsPipeline(m_swapchainDescriptorSetLayout, padView, shader); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index d4eda78..479c9e5 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -720,192 +720,201 @@ private: IMAGE_READ = 0x20, IMAGE_WRITE = 0x40, + }; template - void barrier_calcStageAndMask(VkPipelineStageFlags2& stages, VkAccessFlags2& accessFlags) + void barrier_calcStageAndMask(VkPipelineStageFlags& stages, VkAccessFlags& accessFlags) { stages = 0; accessFlags = 0; if constexpr ((TSyncOp & BUFFER_SHADER_READ) != 0) { - // in theory: VK_ACCESS_2_INDEX_READ_BIT should be set here too but indices are currently separated - stages |= VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + // in theory: VK_ACCESS_INDEX_READ_BIT should be set here too but indices are currently separated + stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT; } - + if constexpr ((TSyncOp & BUFFER_SHADER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_2_SHADER_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; } if constexpr ((TSyncOp & ANY_TRANSFER) != 0) { - stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; - accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; + //stages |= VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; + //accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; + //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; } if constexpr ((TSyncOp & TRANSFER_READ) != 0) { - stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; - accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_READ_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; } if constexpr ((TSyncOp & TRANSFER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; - accessFlags |= VK_ACCESS_2_TRANSFER_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_WRITE_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; } if constexpr ((TSyncOp & HOST_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_HOST_BIT; - accessFlags |= VK_ACCESS_2_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_HOST_BIT; + accessFlags |= VK_ACCESS_HOST_WRITE_BIT; } if constexpr ((TSyncOp & HOST_READ) != 0) { - stages |= VK_PIPELINE_STAGE_2_HOST_BIT; - accessFlags |= VK_ACCESS_2_HOST_READ_BIT; + stages |= VK_PIPELINE_STAGE_HOST_BIT; + accessFlags |= VK_ACCESS_HOST_READ_BIT; } if constexpr ((TSyncOp & IMAGE_READ) != 0) { - stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_2_SHADER_READ_BIT; + stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_SHADER_READ_BIT; - stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; - stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } if constexpr ((TSyncOp & IMAGE_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } } template void barrier_bufferRange(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) { - VkBufferMemoryBarrier2 bufMemBarrier{}; - bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + VkBufferMemoryBarrier bufMemBarrier{}; + bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier.pNext = nullptr; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier_calcStageAndMask(bufMemBarrier.srcStageMask, bufMemBarrier.srcAccessMask); - barrier_calcStageAndMask(bufMemBarrier.dstStageMask, bufMemBarrier.dstAccessMask); + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + + bufMemBarrier.srcAccessMask = 0; + bufMemBarrier.dstAccessMask = 0; + + barrier_calcStageAndMask(srcStages, bufMemBarrier.srcAccessMask); + barrier_calcStageAndMask(dstStages, bufMemBarrier.dstAccessMask); bufMemBarrier.buffer = buffer; bufMemBarrier.offset = offset; bufMemBarrier.size = size; - - VkDependencyInfo depInfo{}; - depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - depInfo.pNext = nullptr; - depInfo.bufferMemoryBarrierCount = 1; - depInfo.pBufferMemoryBarriers = &bufMemBarrier; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &depInfo); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); } template void barrier_bufferRange(VkBuffer bufferA, VkDeviceSize offsetA, VkDeviceSize sizeA, VkBuffer bufferB, VkDeviceSize offsetB, VkDeviceSize sizeB) { - VkBufferMemoryBarrier2 bufMemBarrier2[2] = {}; - bufMemBarrier2[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; - bufMemBarrier2[0].pNext = nullptr; - bufMemBarrier2[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[0].buffer = bufferA; - bufMemBarrier2[0].offset = offsetA; - bufMemBarrier2[0].size = sizeA; - barrier_calcStageAndMask(bufMemBarrier2[0].srcStageMask, bufMemBarrier2[0].srcAccessMask); - barrier_calcStageAndMask(bufMemBarrier2[0].dstStageMask, bufMemBarrier2[0].dstAccessMask); + VkPipelineStageFlags srcStagesA = 0; + VkPipelineStageFlags dstStagesA = 0; + VkPipelineStageFlags srcStagesB = 0; + VkPipelineStageFlags dstStagesB = 0; - bufMemBarrier2[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; - bufMemBarrier2[1].pNext = nullptr; - bufMemBarrier2[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[1].buffer = bufferB; - bufMemBarrier2[1].offset = offsetB; - bufMemBarrier2[1].size = sizeB; - barrier_calcStageAndMask(bufMemBarrier2[1].srcStageMask, bufMemBarrier2[1].srcAccessMask); - barrier_calcStageAndMask(bufMemBarrier2[1].dstStageMask, bufMemBarrier2[1].dstAccessMask); + VkBufferMemoryBarrier bufMemBarrier[2]; - VkDependencyInfo dependencyInfo = {}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.pNext = nullptr; - dependencyInfo.bufferMemoryBarrierCount = 2; - dependencyInfo.pBufferMemoryBarriers = bufMemBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + bufMemBarrier[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier[0].pNext = nullptr; + bufMemBarrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[0].srcAccessMask = 0; + bufMemBarrier[0].dstAccessMask = 0; + barrier_calcStageAndMask(srcStagesA, bufMemBarrier[0].srcAccessMask); + barrier_calcStageAndMask(dstStagesA, bufMemBarrier[0].dstAccessMask); + bufMemBarrier[0].buffer = bufferA; + bufMemBarrier[0].offset = offsetA; + bufMemBarrier[0].size = sizeA; + + bufMemBarrier[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier[1].pNext = nullptr; + bufMemBarrier[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[1].srcAccessMask = 0; + bufMemBarrier[1].dstAccessMask = 0; + barrier_calcStageAndMask(srcStagesB, bufMemBarrier[1].srcAccessMask); + barrier_calcStageAndMask(dstStagesB, bufMemBarrier[1].dstAccessMask); + bufMemBarrier[1].buffer = bufferB; + bufMemBarrier[1].offset = offsetB; + bufMemBarrier[1].size = sizeB; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStagesA|srcStagesB, dstStagesA|dstStagesB, 0, 0, nullptr, 2, bufMemBarrier, 0, nullptr); } void barrier_sequentializeTransfer() { - VkMemoryBarrier2 memoryBarrier2{}; - memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; - memoryBarrier2.pNext = nullptr; - memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; - memoryBarrier2.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; - memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; - memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + VkMemoryBarrier memBarrier{}; + memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memBarrier.pNext = nullptr; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.pNext = nullptr; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier2; - dependencyInfo.bufferMemoryBarrierCount = 0; - dependencyInfo.imageMemoryBarrierCount = 0; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + memBarrier.dstAccessMask = 0; + + memBarrier.srcAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + memBarrier.dstAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 1, &memBarrier, 0, nullptr, 0, nullptr); } void barrier_sequentializeCommand() { - VkMemoryBarrier2 memoryBarrier = {}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; - memoryBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR; - memoryBarrier.srcAccessMask = 0; - memoryBarrier.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR; - memoryBarrier.dstAccessMask = 0; + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkDependencyInfo dependencyInfo = {}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.dependencyFlags = 0; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 0, nullptr); } template void barrier_image(VkImage imageVk, VkImageSubresourceRange& subresourceRange, VkImageLayout oldLayout, VkImageLayout newLayout) { - VkImageMemoryBarrier2 imageMemBarrier2{}; - imageMemBarrier2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - imageMemBarrier2.oldLayout = oldLayout; - imageMemBarrier2.newLayout = newLayout; - imageMemBarrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier2.image = imageVk; - imageMemBarrier2.subresourceRange = subresourceRange; + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; - barrier_calcStageAndMask(imageMemBarrier2.srcStageMask, imageMemBarrier2.srcAccessMask); - barrier_calcStageAndMask(imageMemBarrier2.dstStageMask, imageMemBarrier2.dstAccessMask); + VkImageMemoryBarrier imageMemBarrier{}; + imageMemBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier.srcAccessMask = 0; + imageMemBarrier.dstAccessMask = 0; + barrier_calcStageAndMask(srcStages, imageMemBarrier.srcAccessMask); + barrier_calcStageAndMask(dstStages, imageMemBarrier.dstAccessMask); + imageMemBarrier.image = imageVk; + imageMemBarrier.subresourceRange = subresourceRange; + imageMemBarrier.oldLayout = oldLayout; + imageMemBarrier.newLayout = newLayout; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.imageMemoryBarrierCount = 1; - dependencyInfo.pImageMemoryBarriers = &imageMemBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, + srcStages, dstStages, + 0, + 0, NULL, + 0, NULL, + 1, &imageMemBarrier); } template @@ -925,6 +934,7 @@ private: vkTexture->SetImageLayout(subresourceRange, newLayout); } + public: bool GetDisableMultithreadedCompilation() const { return m_featureControl.disableMultithreadedCompilation; } bool UseTFViaSSBO() const { return m_featureControl.mode.useTFEmulationViaSSBO; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index b6cae7f..320357f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1033,18 +1033,29 @@ void VulkanRenderer::sync_inputTexturesChanged() // barrier here if (writeFlushRequired) { - VkMemoryBarrier2 memoryBarrier2{}; - memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR; - memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; - memoryBarrier2.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR; - memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; - memoryBarrier2.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_SHADER_READ_BIT_KHR; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR; - dependencyInfo.dependencyFlags = 0; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = 0; + memoryBarrier.dstAccessMask = 0; + + VkPipelineStageFlags srcStage = 0; + VkPipelineStageFlags dstStage = 0; + + // src + srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + // dst + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); From f69fddc6e50aabf71d1c78e73d7bcd6545b8ab92 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:25:16 +0100 Subject: [PATCH 018/130] TitleManager: Fix crash when sorting by format (#1113) --- src/gui/components/wxTitleManagerList.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index d6ad811..c02bffb 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -1143,7 +1143,7 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 // check column: title id -> type -> path if (column == ColumnTitleId) { - // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?) + // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed separately?) if (entry1.title_id == entry2.title_id) return SortFunc(ColumnType, v1, v2); @@ -1159,7 +1159,7 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 } else if (column == ColumnType) { - if(std::underlying_type_t(entry1.type) == std::underlying_type_t(entry2.type)) + if(entry1.type == entry2.type) return SortFunc(-1, v1, v2); return std::underlying_type_t(entry1.type) < std::underlying_type_t(entry2.type); @@ -1178,6 +1178,13 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 return std::underlying_type_t(entry1.region) < std::underlying_type_t(entry2.region); } + else if (column == ColumnFormat) + { + if(entry1.format == entry2.format) + return SortFunc(ColumnType, v1, v2); + + return std::underlying_type_t(entry1.format) < std::underlying_type_t(entry2.format); + } return false; } From a2d74972d4e0cd3b61dc4deb32566fc385942963 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 00:55:31 +0100 Subject: [PATCH 019/130] Prevent changing of console language while a game is running (#1114) --- src/gui/MainWindow.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d271ca3..311ddfb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1019,8 +1019,11 @@ void MainWindow::OnConsoleLanguage(wxCommandEvent& event) default: cemu_assert_debug(false); } - m_game_list->DeleteCachedStrings(); - m_game_list->ReloadGameEntries(false); + if (m_game_list) + { + m_game_list->DeleteCachedStrings(); + m_game_list->ReloadGameEntries(false); + } g_config.Save(); } @@ -2159,6 +2162,14 @@ void MainWindow::RecreateMenu() optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, _("&Portuguese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::PT); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, _("&Russian"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::RU); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, _("&Taiwanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::TW); + if(IsGameLaunched()) + { + auto items = optionsConsoleLanguageMenu->GetMenuItems(); + for (auto& item : items) + { + item->Enable(false); + } + } // options submenu wxMenu* optionsMenu = new wxMenu(); From e1435066ee0ccc65e3ec6244c334214243236883 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 00:57:31 +0100 Subject: [PATCH 020/130] OpenGL: Fix crash related to wxWidgets handling of vsync (#1112) --- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 14 ++++++++++++++ src/Common/GLInclude/GLInclude.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index f09f04f..8548fa1 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -24,11 +24,14 @@ #define STRINGIFY2(X) #X #define STRINGIFY(X) STRINGIFY2(X) +namespace CemuGL +{ #define GLFUNC(__type, __name) __type __name; #define EGLFUNC(__type, __name) __type __name; #include "Common/GLInclude/glFunctions.h" #undef GLFUNC #undef EGLFUNC +} #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" @@ -241,6 +244,17 @@ void LoadOpenGLImports() #undef GLFUNC #undef EGLFUNC } + +#if BOOST_OS_LINUX +// dummy function for all code that is statically linked with cemu and attempts to use eglSwapInterval +// used to suppress wxWidgets calls to eglSwapInterval +extern "C" +EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval) +{ + return EGL_TRUE; +} +#endif + #elif BOOST_OS_MACOS void LoadOpenGLImports() { diff --git a/src/Common/GLInclude/GLInclude.h b/src/Common/GLInclude/GLInclude.h index bf7a6bf..86df023 100644 --- a/src/Common/GLInclude/GLInclude.h +++ b/src/Common/GLInclude/GLInclude.h @@ -36,6 +36,8 @@ typedef struct __GLXFBConfigRec *GLXFBConfig; #endif +namespace CemuGL +{ #define GLFUNC(__type, __name) extern __type __name; #define EGLFUNC(__type, __name) extern __type __name; #include "glFunctions.h" @@ -213,6 +215,8 @@ static void glCompressedTextureSubImage3DWrapper(GLenum target, GLuint texture, glBindTexture(target, originalTexture); } +} +using namespace CemuGL; // this prevents Windows GL.h from being included: #define __gl_h_ #define __GL_H__ From 788da3cdf73741a10f714772b74b0675e2e98282 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:47:31 +0100 Subject: [PATCH 021/130] CafeSystem: Init recompiler after game profile has been loaded (#1115) --- src/Cafe/CafeSystem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 30dab1d..76f8ae7 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -779,10 +779,10 @@ namespace CafeSystem return r; // setup memory space and PPC recompiler SetupMemorySpace(); - PPCRecompiler_init(); r = SetupExecutable(); // load RPX if (r != STATUS_CODE::SUCCESS) return r; + PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } @@ -821,11 +821,11 @@ namespace CafeSystem uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); - // setup memory space and ppc recompiler + // setup memory space SetupMemorySpace(); - PPCRecompiler_init(); // load executable SetupExecutable(); + PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } From ccabd9315947cedbbf198c1e0dabffe963b67550 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 02:13:53 +0100 Subject: [PATCH 022/130] Linux: Exit on SIGTERM (#1116) --- src/Common/ExceptionHandler/ExceptionHandler_posix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp index cf54711..7afbf19 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp @@ -155,6 +155,7 @@ void ExceptionHandler_Init() action.sa_handler = handler_SIGINT; sigaction(SIGINT, &action, nullptr); + sigaction(SIGTERM, &action, nullptr); action.sa_flags = SA_SIGINFO; action.sa_handler = nullptr; From bb88b5c36dd145c4d176b26b749c0817c300b2e6 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 02:40:47 +0100 Subject: [PATCH 023/130] Fix crash introduced by #1115 (#1117) * Revert "CafeSystem: Init recompiler after game profile has been loaded (#1115)" * Instead move gameprofile load call --- src/Cafe/CafeSystem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 76f8ae7..75cb111 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -748,7 +748,6 @@ namespace CafeSystem } } LoadMainExecutable(); - gameProfile_load(); return STATUS_CODE::SUCCESS; } @@ -777,12 +776,13 @@ namespace CafeSystem STATUS_CODE r = LoadAndMountForegroundTitle(titleId); if (r != STATUS_CODE::SUCCESS) return r; + gameProfile_load(); // setup memory space and PPC recompiler SetupMemorySpace(); + PPCRecompiler_init(); r = SetupExecutable(); // load RPX if (r != STATUS_CODE::SUCCESS) return r; - PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } @@ -821,11 +821,11 @@ namespace CafeSystem uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); - // setup memory space + // setup memory space and ppc recompiler SetupMemorySpace(); + PPCRecompiler_init(); // load executable SetupExecutable(); - PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } From 3d0d987d895686d749073006c9eca96aa650b9ac Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Mar 2024 01:10:19 +0100 Subject: [PATCH 024/130] Logging: Introduce logOnce helper For cases where printing a message once is enough and to avoid spamming log.txt --- src/Cemu/Logging/CemuLogging.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 728c8b9..388e51a 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -7,7 +7,7 @@ enum class LogType : sint32 // note: IDs must be in range 1-64 Force = 63, // always enabled Placeholder = 62, // always disabled - APIErrors = Force, // alias for Force. Logs bad parameters or other API errors in OS libs + APIErrors = Force, // alias for Force. Logs bad parameters or other API usage mistakes or unintended errors in OS libs CoreinitFile = 0, GX2 = 1, @@ -99,6 +99,8 @@ bool cemuLog_log(LogType type, const T* format, TArgs&&... args) return cemuLog_log(type, format_str, std::forward(args)...); } +#define cemuLog_logOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_log(__VA_ARGS__); } } + // same as cemuLog_log, but only outputs in debug mode template bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) From 0993658c82e89dbda35d29b36a69d1e9e3d47678 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Mar 2024 01:21:04 +0100 Subject: [PATCH 025/130] GX2: Rework GX2Set*UniformReg - Use cafeExportRegister() instead of legacy export - Submit as a single PM4 packet - Add logging for the special case of the size parameter (not sure if this is used by any game?) - Add some extra validation and logging which may be helpful to homebrew devs --- src/Cafe/OS/libs/gx2/GX2.cpp | 3 -- src/Cafe/OS/libs/gx2/GX2.h | 2 -- src/Cafe/OS/libs/gx2/GX2_Shader.cpp | 34 +++++++++++++++++++++ src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp | 35 ---------------------- src/Cafe/OS/libs/nn_act/nn_act.cpp | 2 -- 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/Cafe/OS/libs/gx2/GX2.cpp b/src/Cafe/OS/libs/gx2/GX2.cpp index 82aef16..c2ea34a 100644 --- a/src/Cafe/OS/libs/gx2/GX2.cpp +++ b/src/Cafe/OS/libs/gx2/GX2.cpp @@ -396,16 +396,13 @@ void gx2_load() osLib_addFunction("gx2", "GX2GetCurrentScanBuffer", gx2Export_GX2GetCurrentScanBuffer); // shader stuff - //osLib_addFunction("gx2", "GX2SetVertexShader", gx2Export_GX2SetVertexShader); osLib_addFunction("gx2", "GX2SetPixelShader", gx2Export_GX2SetPixelShader); osLib_addFunction("gx2", "GX2SetGeometryShader", gx2Export_GX2SetGeometryShader); osLib_addFunction("gx2", "GX2SetComputeShader", gx2Export_GX2SetComputeShader); - osLib_addFunction("gx2", "GX2SetVertexUniformReg", gx2Export_GX2SetVertexUniformReg); osLib_addFunction("gx2", "GX2SetVertexUniformBlock", gx2Export_GX2SetVertexUniformBlock); osLib_addFunction("gx2", "GX2RSetVertexUniformBlock", gx2Export_GX2RSetVertexUniformBlock); osLib_addFunction("gx2", "GX2SetPixelUniformBlock", gx2Export_GX2SetPixelUniformBlock); - osLib_addFunction("gx2", "GX2SetPixelUniformReg", gx2Export_GX2SetPixelUniformReg); osLib_addFunction("gx2", "GX2SetGeometryUniformBlock", gx2Export_GX2SetGeometryUniformBlock); osLib_addFunction("gx2", "GX2SetShaderModeEx", gx2Export_GX2SetShaderModeEx); diff --git a/src/Cafe/OS/libs/gx2/GX2.h b/src/Cafe/OS/libs/gx2/GX2.h index 58d9819..a22719f 100644 --- a/src/Cafe/OS/libs/gx2/GX2.h +++ b/src/Cafe/OS/libs/gx2/GX2.h @@ -18,11 +18,9 @@ void gx2_load(); void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU); -void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU); void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU); -void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU); void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index ad17dc4..d004288 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -417,6 +417,37 @@ namespace GX2 } } + void _GX2SubmitUniformReg(uint32 offsetRegBase, uint32 aluRegisterOffset, uint32be* dataWords, uint32 sizeInU32s) + { + if(aluRegisterOffset&0x8000) + { + cemuLog_logDebug(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset"); + return; + } + if((aluRegisterOffset+sizeInU32s) > 0x400) + { + cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 0x400)", aluRegisterOffset, sizeInU32s); + } + if( (sizeInU32s&3) != 0) + { + cemuLog_logOnce(LogType::APIErrors, "GX2Set*UniformReg must be called with a size that is a multiple of 4 (size: {:})", sizeInU32s); + sizeInU32s &= ~3; + } + GX2ReserveCmdSpace(2 + sizeInU32s); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_ALU_CONST, 1 + sizeInU32s), offsetRegBase + aluRegisterOffset); + gx2WriteGather_submitU32AsLEArray((uint32*)dataWords, sizeInU32s); + } + + void GX2SetVertexUniformReg(uint32 offset, uint32 sizeInU32s, uint32be* values) + { + _GX2SubmitUniformReg(0x400, offset, values, sizeInU32s); + } + + void GX2SetPixelUniformReg(uint32 offset, uint32 sizeInU32s, uint32be* values) + { + _GX2SubmitUniformReg(0, offset, values, sizeInU32s); + } + void GX2ShaderInit() { cafeExportRegister("gx2", GX2CalcFetchShaderSizeEx, LogType::GX2); @@ -428,5 +459,8 @@ namespace GX2 cafeExportRegister("gx2", GX2GetPixelShaderStackEntries, LogType::GX2); cafeExportRegister("gx2", GX2SetFetchShader, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexShader, LogType::GX2); + + cafeExportRegister("gx2", GX2SetVertexUniformReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPixelUniformReg, LogType::GX2); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp index 1cb61a7..b0a5d2f 100644 --- a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp @@ -270,41 +270,6 @@ void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -void _GX2SubmitUniformReg(uint32 aluRegisterOffset, MPTR virtualAddress, uint32 count) -{ - uint32* dataWords = (uint32*)memory_getPointerFromVirtualOffset(virtualAddress); - GX2ReserveCmdSpace(2 + (count / 0xFF) * 2 + count); - // write PM4 command(s) - uint32 currentRegisterOffset = aluRegisterOffset; - while (count > 0) - { - uint32 subCount = std::min(count, 0xFFu); // a single command can write at most 0xFF values - gx2WriteGather_submit(pm4HeaderType3(IT_SET_ALU_CONST, 1 + subCount), - currentRegisterOffset); - gx2WriteGather_submitU32AsLEArray(dataWords, subCount); - - dataWords += subCount; - count -= subCount; - currentRegisterOffset += subCount; - } -} - -void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2SetVertexUniformReg(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - _GX2SubmitUniformReg(hCPU->gpr[3] + 0x400, hCPU->gpr[5], hCPU->gpr[4]); - cemu_assert_debug((hCPU->gpr[3] + hCPU->gpr[4]) <= 0x400); - osLib_returnFromFunction(hCPU, 0); -} - -void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2SetPixelUniformReg(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - _GX2SubmitUniformReg(hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); - cemu_assert_debug((hCPU->gpr[3] + hCPU->gpr[4]) <= 0x400); - osLib_returnFromFunction(hCPU, 0); -} - void _GX2SubmitUniformBlock(uint32 registerBase, uint32 index, MPTR virtualAddress, uint32 size) { GX2ReserveCmdSpace(9); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 0fd9df5..2a9f61b 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -543,8 +543,6 @@ void nnActExport_GetDefaultAccount(PPCInterpreter_t* hCPU) void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU) { // id of active account - // uint8 GetSlotNo(void); - cemuLog_logDebug(LogType::Force, "nn_act.GetSlotNo()"); osLib_returnFromFunction(hCPU, 1); // 1 is the first slot (0 is invalid) } From dd7cb74cd21202471634e991865be242f8e45c58 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:36:47 +0100 Subject: [PATCH 026/130] Latte: Small refactor and clean up for texture size code --- .gitignore | 1 + src/Cafe/HW/Latte/Core/LatteBufferCache.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteCachedFBO.h | 4 +- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 28 ++++------- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 8 +-- src/Cafe/HW/Latte/Core/LatteTexture.h | 25 +++++++++- src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 50 ++----------------- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 23 ++++----- .../Renderer/OpenGL/OpenGLSurfaceCopy.cpp | 5 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 +- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 22 +++----- src/Cafe/OS/libs/gx2/GX2_Resource.cpp | 2 +- src/Cemu/Logging/CemuLogging.h | 2 + 13 files changed, 64 insertions(+), 110 deletions(-) diff --git a/.gitignore b/.gitignore index 18f14cf..c10b38d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ bin/sdcard/* bin/screenshots/* bin/dump/* bin/cafeLibs/* +bin/keys.txt !bin/shaderCache/info.txt bin/shaderCache/* diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp index 92c2d1b..716312a 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp @@ -309,7 +309,7 @@ public: { if ((rangeBegin & 0xF)) { - cemuLog_logDebug(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd); + cemuLog_logDebugOnce(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd); rangeBegin = (rangeBegin + 0xF) & ~0xF; rangeEnd = std::max(rangeBegin, rangeEnd); } diff --git a/src/Cafe/HW/Latte/Core/LatteCachedFBO.h b/src/Cafe/HW/Latte/Core/LatteCachedFBO.h index 6d5925f..5f3aaed 100644 --- a/src/Cafe/HW/Latte/Core/LatteCachedFBO.h +++ b/src/Cafe/HW/Latte/Core/LatteCachedFBO.h @@ -42,7 +42,7 @@ private: if(colorBuffer[i].texture == nullptr) continue; sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(colorBuffer[i].texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorBuffer[i].texture->firstMip); + colorBuffer[i].texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorBuffer[i].texture->firstMip); if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) { rtEffectiveSize.x = effectiveWidth; @@ -64,7 +64,7 @@ private: if (depthBuffer.texture) { sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(depthBuffer.texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, depthBuffer.texture->firstMip); + depthBuffer.texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBuffer.texture->firstMip); if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) { rtEffectiveSize.x = effectiveWidth; diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index abdfda2..8c29ccc 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -516,14 +516,12 @@ bool LatteMRT::UpdateCurrentFBO() sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = colorAttachmentView; sLatteRenderTargetState.rtUpdateListCount++; - sint32 colorAttachmentWidth; - sint32 colorAttachmentHeight; - - LatteTexture_getSize(colorAttachmentView->baseTexture, &colorAttachmentWidth, &colorAttachmentHeight, nullptr, colorAttachmentView->firstMip); + sint32 colorAttachmentWidth, colorAttachmentHeight; + colorAttachmentView->baseTexture->GetSize(colorAttachmentWidth, colorAttachmentHeight, colorAttachmentView->firstMip); // set effective size sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(colorAttachmentView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorAttachmentView->firstMip); + colorAttachmentView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorAttachmentView->firstMip); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; @@ -531,9 +529,7 @@ bool LatteMRT::UpdateCurrentFBO() } else if (rtEffectiveSize->width != effectiveWidth && rtEffectiveSize->height != effectiveHeight) { -#ifdef CEMU_DEBUG_ASSERT - cemuLog_log(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format); -#endif + cemuLog_logDebug(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format); } // currently the first color attachment defines the size of the current render target if (rtRealSize->width == 0 && rtRealSize->height == 0) @@ -608,15 +604,11 @@ bool LatteMRT::UpdateCurrentFBO() if (depthBufferPhysMem != MPTR_NULL) { - bool depthBufferWasFound = false; LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true); if (depthBufferView == nullptr) { - // create depth buffer view - if(depthBufferViewFirstSlice == 0) - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, 1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, 0, 1, depthBufferFormat, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); - else - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true); + // create new depth buffer view and if it doesn't exist then also create the texture + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); LatteGPUState.repeatTextureInitialization = true; } else @@ -626,7 +618,7 @@ bool LatteMRT::UpdateCurrentFBO() } // set effective size sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(depthBufferView->baseTexture, &effectiveWidth, &effectiveHeight, NULL); + depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferViewFirstSlice); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; @@ -917,10 +909,8 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa // mark source texture as still in use LatteTC_MarkTextureStillInUse(textureView->baseTexture); - sint32 effectiveWidth; - sint32 effectiveHeight; - sint32 effectiveDepth; - LatteTexture_getEffectiveSize(textureView->baseTexture, &effectiveWidth, &effectiveHeight, &effectiveDepth, 0); + sint32 effectiveWidth, effectiveHeight; + textureView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); _currentOutputImageWidth = effectiveWidth; _currentOutputImageHeight = effectiveHeight; diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 707428a..91a1aa5 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -297,9 +297,9 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s else { sint32 effectiveWidth_dst, effectiveHeight_dst; - LatteTexture_getEffectiveSize(srcTexture, &effectiveWidth_dst, &effectiveHeight_dst, NULL, 0); + srcTexture->GetEffectiveSize(effectiveWidth_dst, effectiveHeight_dst, 0); sint32 effectiveWidth_src, effectiveHeight_src; - LatteTexture_getEffectiveSize(dstTexture, &effectiveWidth_src, &effectiveHeight_src, NULL, 0); + dstTexture->GetEffectiveSize(effectiveWidth_src, effectiveHeight_src, 0); debug_printf("texture_copyData(): Effective size mismatch\n"); cemuLog_logDebug(LogType::Force, "texture_copyData(): Effective size mismatch (due to texture rule)"); @@ -307,8 +307,6 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s cemuLog_logDebug(LogType::Force, "Source: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, 0); return; } - catchOpenGLError(); - for (sint32 mipIndex = 0; mipIndex < mipCount; mipIndex++) { sint32 sliceCopyWidth = std::max(effectiveCopyWidth >> mipIndex, 1); @@ -323,9 +321,7 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s LatteTextureSliceMipInfo* dstTexSliceInfo = dstTexture->sliceMipInfo + dstTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); dstTexSliceInfo->lastDynamicUpdate = srcTexSliceInfo->lastDynamicUpdate; } - catchOpenGLError(); } - catchOpenGLError(); } template diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.h b/src/Cafe/HW/Latte/Core/LatteTexture.h index d5e872e..b46c132 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.h +++ b/src/Cafe/HW/Latte/Core/LatteTexture.h @@ -55,6 +55,29 @@ public: bool Is3DTexture() const { return dim == Latte::E_DIM::DIM_3D; }; + void GetSize(sint32& width, sint32& height, sint32 mipLevel) const + { + width = std::max(1, this->width >> mipLevel); + height = std::max(1, this->height >> mipLevel); + } + + // similar to GetSize, but returns the real size of the texture taking into account any resolution overwrite by gfx pack rules + void GetEffectiveSize(sint32& effectiveWidth, sint32& effectiveHeight, sint32 mipLevel) const + { + if( overwriteInfo.hasResolutionOverwrite ) + { + effectiveWidth = overwriteInfo.width; + effectiveHeight = overwriteInfo.height; + } + else + { + effectiveWidth = this->width; + effectiveHeight = this->height; + } + effectiveWidth = std::max(1, effectiveWidth >> mipLevel); + effectiveHeight = std::max(1, effectiveHeight >> mipLevel); + } + sint32 GetMipDepth(sint32 mipIndex) { cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); @@ -310,8 +333,6 @@ void LatteTexture_Delete(LatteTexture* texture); void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData); -void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel); -void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel = 0); sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture); bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2); void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel); diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index 0260002..b35f608 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -206,14 +206,10 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture - LatteTextureView* textureView; - if (isDepthSampler == false) - textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); - else - textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); - if (textureView == nullptr) + LatteTextureView* textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, isDepthSampler); + if (!textureView) { - // create new mapping + // view not found, create a new mapping which will also create a new texture if necessary textureView = LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, dim, isDepthSampler); if (textureView == nullptr) continue; @@ -273,9 +269,7 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u // check for changes if (LatteTC_HasTextureChanged(textureView->baseTexture) || swizzleChanged) { -#ifdef CEMU_DEBUG_ASSERT debug_printf("Reload texture 0x%08x res %dx%d memRange %08x-%08x SwizzleChange: %s\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->texDataPtrLow, textureView->baseTexture->texDataPtrHigh, swizzleChanged ? "yes" : "no"); -#endif // update swizzle / changed mip address if (swizzleChanged) { @@ -338,44 +332,6 @@ void LatteTexture_updateTextures() LatteTexture_updateTexturesForStage(geometryShader, LATTE_CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS); } -// returns the width, height, depth of the texture -void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel) -{ - *width = texture->width; - *height = texture->height; - if (depth != NULL) - *depth = texture->depth; - // handle mip level - *width = std::max(1, *width >> mipLevel); - *height = std::max(1, *height >> mipLevel); - if(texture->Is3DTexture() && depth) - *depth = std::max(1, *depth >> mipLevel); -} - -/* - * Returns the internally used width/height/depth of the texture - * Usually this is the width/height/depth specified by the game, - * unless the texture resolution was redefined via graphic pack texture rules - */ -void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel) -{ - *effectiveWidth = texture->width; - *effectiveHeight = texture->height; - if( effectiveDepth != NULL ) - *effectiveDepth = texture->depth; - if( texture->overwriteInfo.hasResolutionOverwrite ) - { - *effectiveWidth = texture->overwriteInfo.width; - *effectiveHeight = texture->overwriteInfo.height; - if( effectiveDepth != NULL ) - *effectiveDepth = texture->overwriteInfo.depth; - } - // handle mipLevel - // todo: Mip-mapped 3D textures decrease in depth also? - *effectiveWidth = std::max(1, *effectiveWidth >> mipLevel); - *effectiveHeight = std::max(1, *effectiveHeight >> mipLevel); -} - sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture) { if (texture->overwriteInfo.hasResolutionOverwrite) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 8548fa1..68d7def 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -569,10 +569,8 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu g_renderer->ClearColorbuffer(padView); } - // calculate effective size - sint32 effectiveWidth; - sint32 effectiveHeight; - LatteTexture_getEffectiveSize(texView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, 0); + sint32 effectiveWidth, effectiveHeight; + texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); shader_unbind(RendererShader::ShaderType::kGeometry); shader_bind(shader->GetVertexShader()); @@ -1127,8 +1125,8 @@ void OpenGLRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; cemu_assert_debug(!texGL->isDepth); - sint32 eWidth, eHeight, eDepth; - LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex); + sint32 eWidth, eHeight; + hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex); renderstate_resetColorControl(); renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); LatteMRT::BindColorBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1)); @@ -1141,8 +1139,8 @@ void OpenGLRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; cemu_assert_debug(texGL->isDepth); - sint32 eWidth, eHeight, eDepth; - LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex); + sint32 eWidth, eHeight; + hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex); renderstate_resetColorControl(); renderstate_resetDepthControl(); renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); @@ -1170,13 +1168,12 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 LatteTextureGL::FormatInfoGL formatInfoGL; LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->format, hostTexture->dim, &formatInfoGL); // get effective size of mip - sint32 effectiveWidth; - sint32 effectiveHeight; - LatteTexture_getEffectiveSize(hostTexture, &effectiveWidth, &effectiveHeight, nullptr, mipIndex); + sint32 effectiveWidth, effectiveHeight; + hostTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, mipIndex); // on Nvidia glClearTexImage and glClearTexSubImage has bad performance (clearing a 4K texture takes up to 50ms) // clearing with glTextureSubImage2D from a CPU RAM buffer is only slightly slower - // clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage) + // clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage // clearing with FBO and glClear is orders of magnitude faster than the other methods // (these are results from 2018, may be different now) @@ -1207,7 +1204,6 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 } if (glClearTexSubImage == nullptr) return; - // clear glClearTexSubImage(hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, formatInfoGL.glSuppliedFormat, formatInfoGL.glSuppliedFormatType, NULL); } @@ -1215,7 +1211,6 @@ LatteTexture* OpenGLRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR phy uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { return new LatteTextureGL(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); - } void OpenGLRenderer::texture_setActiveTextureUnit(sint32 index) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp index c49a57e..d578b84 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp @@ -30,9 +30,8 @@ void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); - sint32 sourceEffectiveWidth; - sint32 sourceEffectiveHeight; - LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip); + sint32 sourceEffectiveWidth, sourceEffectiveHeight; + sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); // reset everything renderstate_resetColorControl(); renderstate_resetDepthControl(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 8711359..5285e4a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -764,7 +764,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad //dumpImage->flagForCurrentCommandBuffer(); int width, height; - LatteTexture_getEffectiveSize(baseImageTex, &width, &height, nullptr, 0); + baseImageTex->GetEffectiveSize(width, height, 0); VkImage image = nullptr; VkDeviceMemory imageMemory = nullptr;; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index 6d5d940..d89cdaa 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -464,9 +464,8 @@ VKRObjectFramebuffer* VulkanRenderer::surfaceCopy_getOrCreateFramebuffer(VkCopyS VKRObjectTextureView* vkObjTextureView = surfaceCopy_createImageView(state.destinationTexture, state.dstSlice, state.dstMip); // create new framebuffer - sint32 effectiveWidth = 0; - sint32 effectiveHeight = 0; - LatteTexture_getEffectiveSize(state.destinationTexture, &effectiveWidth, &effectiveHeight, nullptr, state.dstMip); + sint32 effectiveWidth, effectiveHeight; + state.destinationTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, state.dstMip); std::array fbAttachments; fbAttachments[0] = vkObjTextureView; @@ -595,15 +594,11 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 // get descriptor set VKRObjectDescriptorSet* vkObjDescriptorSet = surfaceCopy_getOrCreateDescriptorSet(copySurfaceState, copySurfacePipelineInfo); - // get extend - sint32 effectiveWidth = 0; - sint32 effectiveHeight = 0; - LatteTexture_getEffectiveSize(dstTextureVk, &effectiveWidth, &effectiveHeight, nullptr, texDstMip); + sint32 dstEffectiveWidth, dstEffectiveHeight; + dstTextureVk->GetEffectiveSize(dstEffectiveWidth, dstEffectiveHeight, texDstMip); - // get extend - sint32 srcEffectiveWidth = 0; - sint32 srcEffectiveHeight = 0; - LatteTexture_getEffectiveSize(srcTextureVk, &srcEffectiveWidth, &srcEffectiveHeight, nullptr, texSrcMip); + sint32 srcEffectiveWidth, srcEffectiveHeight; + srcTextureVk->GetEffectiveSize(srcEffectiveWidth, srcEffectiveHeight, texSrcMip); CopyShaderPushConstantData_t pushConstantData; @@ -878,9 +873,8 @@ void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); - sint32 sourceEffectiveWidth; - sint32 sourceEffectiveHeight; - LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip); + sint32 sourceEffectiveWidth, sourceEffectiveHeight; + sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); sint32 texSrcMip = srcMip; sint32 texSrcSlice = srcSlice; diff --git a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp index 7039092..97f51a0 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp @@ -114,7 +114,7 @@ namespace GX2 void GX2RSetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* soBuffer) { - // seen in CoD: Ghosts + // seen in CoD: Ghosts and CoD: Black Ops 2 GX2SetStreamOutBuffer(bufferIndex, soBuffer); } diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 388e51a..bbffd16 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -112,6 +112,8 @@ bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) #endif } +#define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } } + // cafe lib calls bool cemuLog_advancedPPCLoggingEnabled(); From 40d1eaeb72f050916b29396805b8ea990345d418 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:37:44 +0100 Subject: [PATCH 027/130] nn_ac: Refactor and implement more API Doesn't fix any issue as far as I know but it removes some of the unsupported API complaints in debug logging --- src/Cafe/OS/libs/nn_ac/nn_ac.cpp | 189 +++++++++++++++++-------------- 1 file changed, 105 insertions(+), 84 deletions(-) diff --git a/src/Cafe/OS/libs/nn_ac/nn_ac.cpp b/src/Cafe/OS/libs/nn_ac/nn_ac.cpp index bb7d4af..5f23149 100644 --- a/src/Cafe/OS/libs/nn_ac/nn_ac.cpp +++ b/src/Cafe/OS/libs/nn_ac/nn_ac.cpp @@ -8,83 +8,14 @@ // AC lib (manages internet connection) -#define AC_STATUS_FAILED (-1) -#define AC_STATUS_OK (0) - -void nn_acExport_ConnectAsync(PPCInterpreter_t* hCPU) +enum class AC_STATUS : uint32 { - cemuLog_logDebug(LogType::Force, "nn_ac.ConnectAsync();"); - uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_Connect(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nn_ac.Connect();"); - - // Terraria expects this (or GetLastErrorCode) to return 0 on success - // investigate on the actual console - // maybe all success codes are always 0 and dont have any of the other fields set? - - uint32 nnResultCode = 0;// BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); // Splatoon freezes if this function fails? - osLib_returnFromFunction(hCPU, nnResultCode); -} + FAILED = (uint32)-1, + OK = 0, +}; static_assert(TRUE == 1, "TRUE not 1"); -void nn_acExport_IsApplicationConnected(PPCInterpreter_t* hCPU) -{ - //cemuLog_logDebug(LogType::Force, "nn_ac.IsApplicationConnected(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamMEMPTR(connected, uint8, 0); - if (connected) - *connected = TRUE; - //memory_writeU8(hCPU->gpr[3], 1); // always return true regardless of actual online state - - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetConnectStatus(PPCInterpreter_t* hCPU) -{ - ppcDefineParamMEMPTR(status, uint32, 0); - if (status) - *status = AC_STATUS_OK; - - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetLastErrorCode(PPCInterpreter_t* hCPU) -{ - //cemuLog_logDebug(LogType::Force, "nn_ac.GetLastErrorCode();"); - ppcDefineParamMEMPTR(errorCode, uint32, 0); - if (errorCode) - *errorCode = 0; - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetStatus(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nn_ac.GetStatus();"); - ppcDefineParamMEMPTR(status, uint32, 0); - if (status) - *status = AC_STATUS_OK; - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetConnectResult(PPCInterpreter_t* hCPU) -{ - // GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status - cemuLog_logDebug(LogType::Force, "nn_ac.GetConnectResult(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamMEMPTR(result, uint32, 0); - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - if (result) - *result = nnResultCode; - osLib_returnFromFunction(hCPU, nnResultCode); -} - void _GetLocalIPAndSubnetMaskFallback(uint32& localIp, uint32& subnetMask) { // default to some hardcoded values @@ -227,37 +158,127 @@ void nnAcExport_IsConfigExisting(PPCInterpreter_t* hCPU) namespace nn_ac { + nnResult Initialize() + { + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult ConnectAsync() + { + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult IsApplicationConnected(uint8be* connected) + { + if (connected) + *connected = TRUE; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + uint32 Connect() + { + // Terraria expects this (or GetLastErrorCode) to return 0 on success + // investigate on the actual console + // maybe all success codes are always 0 and dont have any of the other fields set? + uint32 nnResultCode = 0;// BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); // Splatoon freezes if this function fails? + return nnResultCode; + } + + nnResult GetConnectStatus(betype* status) + { + if (status) + *status = AC_STATUS::OK; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult GetStatus(betype* status) + { + return GetConnectStatus(status); + } + + nnResult GetLastErrorCode(uint32be* errorCode) + { + if (errorCode) + *errorCode = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult GetConnectResult(uint32be* connectResult) + { + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + if (connectResult) + *connectResult = nnResultCode; + return nnResultCode; + } + + static_assert(sizeof(betype) == 4); + static_assert(sizeof(betype) == 4); + + nnResult ACInitialize() + { + return Initialize(); + } + bool ACIsSuccess(betype* r) { return NN_RESULT_IS_SUCCESS(*r) ? 1 : 0; } - nnResult ACGetConnectStatus(uint32be* connectionStatus) + bool ACIsFailure(betype* r) { + return NN_RESULT_IS_FAILURE(*r) ? 1 : 0; + } - *connectionStatus = 0; // 0 means connected? + nnResult ACGetConnectStatus(betype* connectionStatus) + { + return GetConnectStatus(connectionStatus); + } - return NN_RESULT_SUCCESS; + nnResult ACGetStatus(betype* connectionStatus) + { + return GetStatus(connectionStatus); + } + + nnResult ACConnectAsync() + { + return ConnectAsync(); + } + + nnResult ACIsApplicationConnected(uint32be* connectedU32) + { + uint8be connected = 0; + nnResult r = IsApplicationConnected(&connected); + *connectedU32 = connected; // convert to uint32 + return r; } void load() { + cafeExportRegisterFunc(Initialize, "nn_ac", "Initialize__Q2_2nn2acFv", LogType::Placeholder); + + cafeExportRegisterFunc(Connect, "nn_ac", "Connect__Q2_2nn2acFv", LogType::Placeholder); + cafeExportRegisterFunc(ConnectAsync, "nn_ac", "ConnectAsync__Q2_2nn2acFv", LogType::Placeholder); + + cafeExportRegisterFunc(GetConnectResult, "nn_ac", "GetConnectResult__Q2_2nn2acFPQ2_2nn6Result", LogType::Placeholder); + cafeExportRegisterFunc(GetLastErrorCode, "nn_ac", "GetLastErrorCode__Q2_2nn2acFPUi", LogType::Placeholder); + cafeExportRegisterFunc(GetConnectStatus, "nn_ac", "GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", LogType::Placeholder); + cafeExportRegisterFunc(GetStatus, "nn_ac", "GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", LogType::Placeholder); + cafeExportRegisterFunc(IsApplicationConnected, "nn_ac", "IsApplicationConnected__Q2_2nn2acFPb", LogType::Placeholder); + + // AC also offers C-style wrappers + cafeExportRegister("nn_ac", ACInitialize, LogType::Placeholder); cafeExportRegister("nn_ac", ACIsSuccess, LogType::Placeholder); + cafeExportRegister("nn_ac", ACIsFailure, LogType::Placeholder); cafeExportRegister("nn_ac", ACGetConnectStatus, LogType::Placeholder); + cafeExportRegister("nn_ac", ACGetStatus, LogType::Placeholder); + cafeExportRegister("nn_ac", ACConnectAsync, LogType::Placeholder); + cafeExportRegister("nn_ac", ACIsApplicationConnected, LogType::Placeholder); } } void nnAc_load() { - osLib_addFunction("nn_ac", "Connect__Q2_2nn2acFv", nn_acExport_Connect); - osLib_addFunction("nn_ac", "ConnectAsync__Q2_2nn2acFv", nn_acExport_ConnectAsync); - osLib_addFunction("nn_ac", "IsApplicationConnected__Q2_2nn2acFPb", nn_acExport_IsApplicationConnected); - osLib_addFunction("nn_ac", "GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", nn_acExport_GetConnectStatus); - osLib_addFunction("nn_ac", "GetConnectResult__Q2_2nn2acFPQ2_2nn6Result", nn_acExport_GetConnectResult); - osLib_addFunction("nn_ac", "GetLastErrorCode__Q2_2nn2acFPUi", nn_acExport_GetLastErrorCode); - osLib_addFunction("nn_ac", "GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", nn_acExport_GetStatus); - osLib_addFunction("nn_ac", "GetAssignedAddress__Q2_2nn2acFPUl", nnAcExport_GetAssignedAddress); osLib_addFunction("nn_ac", "GetAssignedSubnet__Q2_2nn2acFPUl", nnAcExport_GetAssignedSubnet); From 1f9b89116f2bad17f9dbd0017b38e0da181fa4c9 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:55:58 +0100 Subject: [PATCH 028/130] Vulkan: Fix crash during shutdown if shaders are still compiling Make sure the async shader compiler threads are stopped before the shaders are deleted --- .../Renderer/Vulkan/RendererShaderVk.cpp | 23 +++++++++---------- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 970f551..437ef51 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -129,19 +129,18 @@ class _ShaderVkThreadPool public: void StartThreads() { - if (s_threads.empty()) - { - // create thread pool - m_shutdownThread.store(false); - const uint32 threadCount = 2; - for (uint32 i = 0; i < threadCount; ++i) - s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this); - } + if (m_threadsActive.exchange(true)) + return; + // create thread pool + const uint32 threadCount = 2; + for (uint32 i = 0; i < threadCount; ++i) + s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this); } void StopThreads() { - m_shutdownThread.store(true); + if (!m_threadsActive.exchange(false)) + return; for (uint32 i = 0; i < s_threads.size(); ++i) s_compilationQueueCount.increment(); for (auto& it : s_threads) @@ -156,7 +155,7 @@ public: void CompilerThreadFunc() { - while (!m_shutdownThread.load(std::memory_order::relaxed)) + while (m_threadsActive.load(std::memory_order::relaxed)) { s_compilationQueueCount.decrementWithWait(); s_compilationQueueMutex.lock(); @@ -181,7 +180,7 @@ public: } } - bool HasThreadsRunning() const { return !m_shutdownThread; } + bool HasThreadsRunning() const { return m_threadsActive; } public: std::vector s_threads; @@ -191,7 +190,7 @@ public: std::mutex s_compilationQueueMutex; private: - std::atomic m_shutdownThread; + std::atomic m_threadsActive; }ShaderVkThreadPool; RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 5285e4a..876baa0 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -600,7 +600,7 @@ VulkanRenderer::~VulkanRenderer() SubmitCommandBuffer(); WaitDeviceIdle(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); - // shut down compilation threads + // make sure compilation threads have been shut down RendererShaderVk::Shutdown(); // shut down pipeline save thread m_destructionRequested = true; @@ -1558,12 +1558,12 @@ void VulkanRenderer::Shutdown() Renderer::Shutdown(); SubmitCommandBuffer(); WaitDeviceIdle(); - if (m_imguiRenderPass != VK_NULL_HANDLE) { vkDestroyRenderPass(m_logicalDevice, m_imguiRenderPass, nullptr); m_imguiRenderPass = VK_NULL_HANDLE; } + RendererShaderVk::Shutdown(); } void VulkanRenderer::UnrecoverableError(const char* errMsg) const From a50e25300d1c3d4eec9ba3085067facff035815f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:01:37 +0100 Subject: [PATCH 029/130] Vulkan: Remove unused code path for texture copies In 2020 we switched to drawcalls for texture copies replacing the copy-via-buffer path. It's not been used since so lets remove it --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 14 -- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 6 - .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 127 +----------------- 3 files changed, 1 insertion(+), 146 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 876baa0..c7f8c04 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -577,20 +577,6 @@ VulkanRenderer::VulkanRenderer() for (sint32 i = 0; i < OCCLUSION_QUERY_POOL_SIZE; i++) m_occlusionQueries.list_availableQueryIndices.emplace_back(i); - // enable surface copies via buffer if we have plenty of memory available (otherwise use drawcalls) - size_t availableSurfaceCopyBufferMem = memoryManager->GetTotalMemoryForBufferType(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - //m_featureControl.mode.useBufferSurfaceCopies = availableSurfaceCopyBufferMem >= 2000ull * 1024ull * 1024ull; // enable if at least 2000MB VRAM - m_featureControl.mode.useBufferSurfaceCopies = false; - - if (m_featureControl.mode.useBufferSurfaceCopies) - { - //cemuLog_log(LogType::Force, "Enable surface copies via buffer"); - } - else - { - //cemuLog_log(LogType::Force, "Disable surface copies via buffer (Requires 2GB. Has only {}MB available)", availableSurfaceCopyBufferMem / 1024ull / 1024ull); - } - // start compilation threads RendererShaderVk::Init(); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 479c9e5..226edad 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -311,7 +311,6 @@ public: void surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture); private: - void surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcLevel, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstLevel, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); void surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); void surfaceCopy_cleanup(); @@ -328,10 +327,6 @@ private: std::unordered_map m_copySurfacePipelineCache; - VkBuffer m_surfaceCopyBuffer = VK_NULL_HANDLE; - VkDeviceMemory m_surfaceCopyBufferMemory = VK_NULL_HANDLE; - size_t m_surfaceCopyBufferSize{}; - public: // renderer interface void bufferCache_init(const sint32 bufferSize) override; @@ -470,7 +465,6 @@ private: struct { - bool useBufferSurfaceCopies; // if GPU has enough VRAM to spare, allow to use a buffer to copy surfaces (instead of drawcalls) bool useTFEmulationViaSSBO = true; // emulate transform feedback via shader writes to a storage buffer }mode; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index d89cdaa..479b7e6 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -763,110 +763,6 @@ bool vkIsBitCompatibleColorDepthFormat(VkFormat format1, VkFormat format2) return false; } -void VulkanRenderer::surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight) -{ - cemu_assert_debug(false); // not used currently - - cemu_assert_debug(m_featureControl.mode.useBufferSurfaceCopies); - - if (srcTextureVk->dim == Latte::E_DIM::DIM_3D) - { - cemu_assert_debug(false); - return; - } - if (dstTextureVk->dim == Latte::E_DIM::DIM_3D) - { - cemu_assert_debug(false); - return; - } - - draw_endRenderPass(); - - // calculate buffer size required for copy - VkDeviceSize copySize = std::max(srcTextureVk->getAllocation()->getAllocationSize(), dstTextureVk->getAllocation()->getAllocationSize()); - - // make sure allocated buffer is large enough - if (m_surfaceCopyBuffer == VK_NULL_HANDLE || copySize > m_surfaceCopyBufferSize) - { - if (m_surfaceCopyBuffer != VK_NULL_HANDLE) - { - // free existing buffer - destroyDeviceMemory(m_surfaceCopyBufferMemory); - m_surfaceCopyBufferMemory = VK_NULL_HANDLE; - destroyBuffer(m_surfaceCopyBuffer); - m_surfaceCopyBuffer = VK_NULL_HANDLE; - } - VkDeviceSize allocSize = (copySize + 1024ull * 1024ull - 1ull) & ~(1024ull * 1024ull - 1ull); // align to whole MB - m_surfaceCopyBufferSize = allocSize; - memoryManager->CreateBuffer(m_surfaceCopyBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_surfaceCopyBuffer, m_surfaceCopyBufferMemory); - if (m_surfaceCopyBuffer == VK_NULL_HANDLE) - { - cemuLog_log(LogType::Force, "Vulkan: Failed to allocate surface copy buffer with size {}", allocSize); - return; - } - } - if (m_surfaceCopyBuffer == VK_NULL_HANDLE) - return; - - auto vkObjSrcTexture = srcTextureVk->GetImageObj(); - auto vkObjDstTexture = dstTextureVk->GetImageObj(); - vkObjSrcTexture->flagForCurrentCommandBuffer(); - vkObjDstTexture->flagForCurrentCommandBuffer(); - - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = effectiveCopyWidth; - region.bufferImageHeight = effectiveCopyHeight; - - if (srcTextureVk->isDepth) - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - else - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.baseArrayLayer = texSrcSlice; - region.imageSubresource.layerCount = 1; - region.imageSubresource.mipLevel = texSrcMip; - - region.imageOffset = { 0,0,0 }; - region.imageExtent = { (uint32)effectiveCopyWidth, (uint32)effectiveCopyHeight, 1 }; - - // make sure all write operations to the src image have finished - barrier_image(srcTextureVk, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); - - vkCmdCopyImageToBuffer(getCurrentCommandBuffer(), vkObjSrcTexture->m_image, VK_IMAGE_LAYOUT_GENERAL, m_surfaceCopyBuffer, 1, ®ion); - - // copy buffer to image - - VkBufferImageCopy imageRegion[2]{}; - sint32 imageRegionCount = 0; - - // color or depth only copy - imageRegion[0].bufferOffset = 0; - imageRegion[0].imageExtent.width = effectiveCopyWidth; - imageRegion[0].imageExtent.height = effectiveCopyHeight; - imageRegion[0].imageExtent.depth = 1; - - imageRegion[0].imageSubresource.mipLevel = texDstMip; - if (dstTextureVk->isDepth) - imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - else - imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - imageRegion[0].imageSubresource.baseArrayLayer = texDstSlice; - imageRegion[0].imageSubresource.layerCount = 1; - - imageRegionCount = 1; - - // make sure the transfer to the buffer finished - barrier_bufferRange(m_surfaceCopyBuffer, 0, VK_WHOLE_SIZE); - - // make sure all read and write operations to the dst image have finished - barrier_image(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - vkCmdCopyBufferToImage(m_state.currentCommandBuffer, m_surfaceCopyBuffer, vkObjDstTexture->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); - - // make sure transfer has finished before any other operation - barrier_image(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_GENERAL); -} - void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { // scale copy size to effective size @@ -899,28 +795,7 @@ void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s return; } - VkFormat srcFormatVk = srcTextureVk->GetFormat(); - VkFormat dstFormatVk = dstTextureVk->GetFormat(); - - if ((srcTextureVk->isDepth && !dstTextureVk->isDepth) || - !srcTextureVk->isDepth && dstTextureVk->isDepth) - { - // depth to color or - // color to depth - if (m_featureControl.mode.useBufferSurfaceCopies && vkIsBitCompatibleColorDepthFormat(srcFormatVk, dstFormatVk)) - surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - else - surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - } - else - { - // depth to depth or - // color to color - if (m_featureControl.mode.useBufferSurfaceCopies && srcFormatVk == dstFormatVk) - surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - else - surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - } + surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); } // called whenever a texture is destroyed From 224866c3d218995458d957c1c3777313e289da63 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 12 Mar 2024 01:37:07 +0100 Subject: [PATCH 030/130] CI: Work around a vcpkg issue by checking out an earlier commit --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00aac0f..f3b834b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git pull --all + git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} @@ -133,7 +133,7 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git pull --all + git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} From 6fa77feba3ec7437b57b6c4c221cde9eb07cd399 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 12 Mar 2024 05:52:53 +0100 Subject: [PATCH 031/130] Latte: Fix regression in dd7cb74 --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 8c29ccc..d7c5408 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -618,7 +618,7 @@ bool LatteMRT::UpdateCurrentFBO() } // set effective size sint32 effectiveWidth, effectiveHeight; - depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferViewFirstSlice); + depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index b35f608..4e5c303 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -206,7 +206,11 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture - LatteTextureView* textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, isDepthSampler); + LatteTextureView* textureView; + if (isDepthSampler == false) + textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); + else + textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); if (!textureView) { // view not found, create a new mapping which will also create a new texture if necessary From 8bc444bb97cfeb747a66142ad31884323785af32 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:16:52 +0100 Subject: [PATCH 032/130] Latte: Derive framebuffer size from correct mip of depth buffer --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 4 ++-- src/Cafe/HW/Latte/Core/LatteTextureView.cpp | 3 +-- src/Cafe/HW/Latte/Core/LatteTextureView.h | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index d7c5408..f84bbec 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -618,7 +618,7 @@ bool LatteMRT::UpdateCurrentFBO() } // set effective size sint32 effectiveWidth, effectiveHeight; - depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); + depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferView->firstMip); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index 4e5c303..b9ccbac 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -207,10 +207,10 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture LatteTextureView* textureView; - if (isDepthSampler == false) + if (!isDepthSampler) textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); else - textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); + textureView = LatteTextureViewLookupCache::lookupWithColorOrDepthType(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); if (!textureView) { // view not found, create a new mapping which will also create a new texture if necessary diff --git a/src/Cafe/HW/Latte/Core/LatteTextureView.cpp b/src/Cafe/HW/Latte/Core/LatteTextureView.cpp index cac5bcc..2773a34 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureView.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureView.cpp @@ -143,7 +143,6 @@ void LatteTextureViewLookupCache::RemoveAll(LatteTextureView* view) } } - LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim) { // todo - add tileMode param to this and the other lookup functions? @@ -163,7 +162,7 @@ LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 widt return nullptr; } -LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth) +LatteTextureView* LatteTextureViewLookupCache::lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth) { cemu_assert_debug(firstSlice == 0); uint32 key = _getViewBucketKey(physAddr, width, height, pitch); diff --git a/src/Cafe/HW/Latte/Core/LatteTextureView.h b/src/Cafe/HW/Latte/Core/LatteTextureView.h index a6d2e16..abda084 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureView.h +++ b/src/Cafe/HW/Latte/Core/LatteTextureView.h @@ -41,7 +41,7 @@ public: static void RemoveAll(LatteTextureView* view); static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim); - static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth); + static LatteTextureView* lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth); static LatteTextureView* lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); static LatteTextureView* lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); static LatteTextureView* lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth); From bc04662525acbab50251a28a0a2b39d95fda7707 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 13 Mar 2024 02:41:42 +0100 Subject: [PATCH 033/130] Latte+GL+VK: Improve handling of gfx pack texture overwrite format Graphic packs can overwrite the format of a texture (e.g. for higher bitdepth to lessen banding) but the code for this wasn't correctly working anymore. - Fixes overwrite format being ignored for texture views on Vulkan backend - Fixes overwrite format not being used for texture views on OpenGL Format aliasing is complicated enough as it is, even without overwrites, so this adds a new rule to make behavior more well defined: If two textures share memory but only one uses an overwrite format, then they are no longer synchronized and are considered separate textures. Bonus fixes for OpenGL: - Use fbo 0 instead of -1 as the default. This silences some warnings in debug output - On OpenGL, bind new framebuffers on handle generation so they are considered created --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 11 ++++++ .../Latte/Renderer/OpenGL/LatteTextureGL.cpp | 2 +- .../Renderer/OpenGL/LatteTextureViewGL.cpp | 14 ++++--- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 37 ++++++++++--------- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 2 +- .../Renderer/Vulkan/LatteTextureViewVk.cpp | 7 +++- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 91a1aa5..21b49c9 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -434,6 +434,11 @@ void LatteTexture_SyncSlice(LatteTexture* srcTexture, sint32 srcSliceIndex, sint sint32 dstWidth = dstTexture->width; sint32 dstHeight = dstTexture->height; + if(srcTexture->overwriteInfo.hasFormatOverwrite != dstTexture->overwriteInfo.hasFormatOverwrite) + return; // dont sync: format overwrite state needs to match. Not strictly necessary but it simplifies logic down the road + else if(srcTexture->overwriteInfo.hasFormatOverwrite && srcTexture->overwriteInfo.format != dstTexture->overwriteInfo.format) + return; // both are overwritten but with different formats + if (srcMipIndex == 0 && dstMipIndex == 0 && (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) && srcTexture->height > dstTexture->height && (srcTexture->height % dstTexture->height) == 0) { bool isMatch = srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED; @@ -816,6 +821,12 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT { relativeMipIndex = 0; relativeSliceIndex = 0; + if (baseTexture->overwriteInfo.hasFormatOverwrite) + { + // if the base format is overwritten, then we only allow aliasing if the view format matches the base format + if (baseTexture->format != format) + return VIEW_NOT_COMPATIBLE; + } if (LatteTexture_IsFormatViewCompatible(baseTexture->format, format) == false) return VIEW_NOT_COMPATIBLE; if (baseTexture->physAddress == physAddr && baseTexture->pitch == pitch) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index 584af40..cd36361 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -26,7 +26,7 @@ LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipA GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget, true); // set format info FormatInfoGL glFormatInfo; - GetOpenGLFormatInfo(isDepth, format, dim, &glFormatInfo); + GetOpenGLFormatInfo(isDepth, overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)overwriteInfo.format : format, dim, &glFormatInfo); this->glInternalFormat = glFormatInfo.glInternalFormat; this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format? diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp index 2908564..3e8abe8 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp @@ -55,12 +55,16 @@ LatteTextureViewGL::~LatteTextureViewGL() void LatteTextureViewGL::InitAliasView() { const auto texture = (LatteTextureGL*)baseTexture; - // get internal format - if (baseTexture->isDepth) + // compute internal format + if(texture->overwriteInfo.hasFormatOverwrite) + { + cemu_assert_debug(format == texture->format); + glInternalFormat = texture->glInternalFormat; // for format overwrite no aliasing is allowed and thus we always inherit the internal format of the base texture + } + else if (baseTexture->isDepth) { // depth is handled differently - cemuLog_logDebug(LogType::Force, "Creating depth view"); - cemu_assert(format == texture->format); // todo + cemu_assert(format == texture->format); // is depth alias with different format intended? glInternalFormat = texture->glInternalFormat; } else @@ -73,7 +77,7 @@ void LatteTextureViewGL::InitAliasView() catchOpenGLError(); if (firstMip >= texture->maxPossibleMipLevels) { - cemuLog_logDebug(LogType::Force, "_createNewView: Out of bounds mip level requested"); + cemuLog_logDebug(LogType::Force, "InitAliasView(): Out of bounds mip level requested"); glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, texture->maxPossibleMipLevels - 1, numMip, firstSlice, this->numSlice); } else diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 68d7def..943e39a 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -330,13 +330,14 @@ void OpenGLRenderer::Initialize() lock.unlock(); // create framebuffer for fast clearing (avoid glClearTexSubImage on Nvidia) - if (this->m_vendor == GfxVendor::Nvidia || glClearTexSubImage == nullptr) + if (glCreateFramebuffers) + glCreateFramebuffers(1, &glRendererState.clearFBO); + else { - // generate framebuffer - if (glCreateFramebuffers && false) - glCreateFramebuffers(1, &glRendererState.clearFBO); - else - glGenFramebuffers(1, &glRendererState.clearFBO); + glGenFramebuffers(1, &glRendererState.clearFBO); + // bind to initialize + glBindFramebuffer(GL_FRAMEBUFFER_EXT, glRendererState.clearFBO); + glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); } draw_init(); @@ -425,9 +426,12 @@ void _glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GL return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Dithering is enabled")) return; - + if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Blending is enabled, but is not supported for integer framebuffers")) + return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "does not have a defined base level")) return; + if(LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "has depth comparisons disabled, with a texture object")) + return; cemuLog_log(LogType::Force, "GLDEBUG: {}", message); @@ -670,7 +674,10 @@ void OpenGLRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) { auto cfboGL = (CachedFBOGL*)cfbo; if (prevBoundFBO == cfboGL->glId_fbo) - prevBoundFBO = -1; + { + glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); + prevBoundFBO = 0; + } glDeleteFramebuffers(1, &cfboGL->glId_fbo); } @@ -1013,9 +1020,6 @@ void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneri effectiveBaseHeight = hostTexture->overwriteInfo.height; effectiveBaseDepth = hostTexture->overwriteInfo.depth; } - // get format info - LatteTextureGL::FormatInfoGL glFormatInfo; - LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo); // calculate mip count sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); mipLevels = std::max(mipLevels, 1); @@ -1023,25 +1027,25 @@ void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneri if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) { cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); + glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); } else if (hostTexture->dim == Latte::E_DIM::DIM_1D) { cemu_assert_debug(effectiveBaseHeight == 1); cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth); + glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); } else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) { - glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_3D) { - glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) { - glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); + glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); } else { @@ -1279,7 +1283,6 @@ void OpenGLRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, { auto srcGL = (LatteTextureGL*)src; auto dstGL = (LatteTextureGL*)dst; - if ((srcGL->isAlternativeFormat || dstGL->isAlternativeFormat) && (srcGL->glInternalFormat != dstGL->glInternalFormat)) { if (srcGL->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC4_UNORM) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 8a4b1a1..026264c 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -195,7 +195,7 @@ private: GLuint glStreamoutCacheRingBuffer; // cfbo - GLuint prevBoundFBO = -1; + GLuint prevBoundFBO = 0; GLuint glId_fbo = 0; // renderstate diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp index d87d9ea..aae7e9d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp @@ -57,7 +57,12 @@ uint32 LatteTextureVk_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 co LatteTextureViewVk::LatteTextureViewVk(VkDevice device, LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_device(device) { - if (dim != texture->dim || format != texture->format) + if(texture->overwriteInfo.hasFormatOverwrite) + { + cemu_assert_debug(format == texture->format); // if format overwrite is used, the texture is no longer taking part in aliasing and the format of any view has to match + m_format = texture->GetFormat(); + } + else if (dim != texture->dim || format != texture->format) { VulkanRenderer::FormatInfoVK texFormatInfo; VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, texture->isDepth, dim, 0, 0, &texFormatInfo); From 193767e6cccafd971e9028386fdfe7f8f46b2d21 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:04:05 +0100 Subject: [PATCH 034/130] Latte+Vulkan: Code cleanup Besides a general cleanup: - Remove deprecated resource destruction queues - Move functionality from renderer into Latte base classes to deduplicate code --- src/Cafe/HW/Latte/Core/Latte.h | 2 +- .../HW/Latte/Core/LatteCommandProcessor.cpp | 12 +-- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 43 ++------- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 10 +++ src/Cafe/HW/Latte/Core/LatteTexture.h | 2 + src/Cafe/HW/Latte/Core/LatteTextureCache.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp | 2 +- .../Latte/Renderer/OpenGL/LatteTextureGL.cpp | 73 +++++++++++----- .../HW/Latte/Renderer/OpenGL/LatteTextureGL.h | 12 +-- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 51 ----------- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 3 - src/Cafe/HW/Latte/Renderer/Renderer.h | 3 - .../HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp | 4 +- .../Renderer/Vulkan/LatteTextureViewVk.cpp | 6 +- .../Latte/Renderer/Vulkan/LatteTextureVk.cpp | 14 ++- .../HW/Latte/Renderer/Vulkan/LatteTextureVk.h | 5 +- .../Renderer/Vulkan/RendererShaderVk.cpp | 3 +- .../Latte/Renderer/Vulkan/VKRPipelineInfo.cpp | 2 +- .../Vulkan/VulkanPipelineStableCache.cpp | 2 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 87 +------------------ .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 25 +----- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 8 +- src/imgui/imgui_impl_vulkan.cpp | 13 +-- 23 files changed, 115 insertions(+), 269 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index dc3cbc9..d9419a6 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -98,7 +98,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight); void LatteRenderTarget_itHLESwapScanBuffer(); -void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil); +void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil); void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget); void LatteRenderTarget_unloadAll(); diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 60e5935..c928f89 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -864,8 +864,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) cemu_assert_debug(nWords == 23); uint32 clearMask = LatteReadCMD(); // color (1), depth (2), stencil (4) // color buffer - MPTR colorBufferMPTR = LatteReadCMD(); // MPTR for color buffer (physical address) - MPTR colorBufferFormat = LatteReadCMD(); // format for color buffer + MPTR colorBufferMPTR = LatteReadCMD(); // physical address for color buffer + Latte::E_GX2SURFFMT colorBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 colorBufferWidth = LatteReadCMD(); uint32 colorBufferHeight = LatteReadCMD(); @@ -873,8 +873,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) uint32 colorBufferViewFirstSlice = LatteReadCMD(); uint32 colorBufferViewNumSlice = LatteReadCMD(); // depth buffer - MPTR depthBufferMPTR = LatteReadCMD(); // MPTR for depth buffer (physical address) - MPTR depthBufferFormat = LatteReadCMD(); // format for depth buffer + MPTR depthBufferMPTR = LatteReadCMD(); // physical address for depth buffer + Latte::E_GX2SURFFMT depthBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 depthBufferWidth = LatteReadCMD(); uint32 depthBufferHeight = LatteReadCMD(); @@ -893,8 +893,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) LatteRenderTarget_itHLEClearColorDepthStencil( clearMask, - colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice, - depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice, + colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice, + depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice, r, g, b, a, clearDepth, clearStencil); return cmd; diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index f84bbec..3006971 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -221,35 +221,9 @@ void LatteMRT::BindDepthBufferOnly(LatteTextureView* view) ApplyCurrentState(); } -/***************************************************/ - -LatteTextureView* LatteMRT_FindColorBufferForClearing(MPTR colorBufferPtr, sint32 colorBufferWidth, sint32 colorBufferHeight, sint32 colorBufferPitch, uint32 format, sint32 sliceIndex, sint32* searchIndex) -{ - LatteTextureView* view = LatteTC_LookupTextureByData(colorBufferPtr, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, sliceIndex, 1, searchIndex); - if (view == nullptr) - return nullptr; - return view; -} - -LatteTextureView* LatteMRT_CreateColorBuffer(MPTR colorBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_GX2SURFFMT format, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 viewSlice) -{ - cemu_assert_debug(colorBufferPhysMem != MPTR_NULL); - LatteTextureView* textureView; - if(viewSlice != 0) - textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, false); - else - textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); - return textureView; -} - LatteTextureView* LatteMRT_CreateDepthBuffer(MPTR depthBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_HWTILEMODE tileMode, Latte::E_GX2SURFFMT format, uint32 swizzle, sint32 viewSlice) { - LatteTextureView* textureView; - if(viewSlice == 0) - textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); - else - textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true); - + LatteTextureView* textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, viewSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); LatteMRT::SetDepthAndStencilAttachment(textureView, textureView->baseTexture->hasStencil); return textureView; } @@ -605,7 +579,7 @@ bool LatteMRT::UpdateCurrentFBO() if (depthBufferPhysMem != MPTR_NULL) { LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true); - if (depthBufferView == nullptr) + if (!depthBufferView) { // create new depth buffer view and if it doesn't exist then also create the texture depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); @@ -768,7 +742,10 @@ void LatteRenderTarget_applyTextureDepthClear(LatteTexture* texture, uint32 slic LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter); } -void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil) +void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, + MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, + MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, + float r, float g, float b, float a, float clearDepth, uint32 clearStencil) { uint32 depthBufferMipIndex = 0; // todo uint32 colorBufferMipIndex = 0; // todo @@ -803,13 +780,11 @@ void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorB bool targetFound = false; while (true) { - LatteTextureView* colorView = LatteMRT_FindColorBufferForClearing(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferFormat, colorBufferViewFirstSlice, &searchIndex); + LatteTextureView* colorView = LatteTC_LookupTextureByData(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, colorBufferViewFirstSlice, 1, &searchIndex); if (!colorView) break; - if (Latte::GetFormatBits((Latte::E_GX2SURFFMT)colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format)) - { + if (Latte::GetFormatBits(colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format)) continue; - } if (colorView->baseTexture->pitch == colorBufferPitch && colorView->baseTexture->height == colorBufferHeight) targetFound = true; @@ -821,7 +796,7 @@ void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorB { // create new texture with matching format cemu_assert_debug(colorBufferViewNumSlice <= 1); - LatteTextureView* newColorView = LatteMRT_CreateColorBuffer(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, (Latte::E_GX2SURFFMT)colorBufferFormat, colorBufferTilemode, colorBufferSwizzle, colorBufferViewFirstSlice); + LatteTextureView* newColorView = LatteTexture_CreateMapping(colorBufferMPTR, MPTR_NULL, colorBufferWidth, colorBufferHeight, colorBufferViewFirstSlice+1, colorBufferPitch, colorBufferTilemode, colorBufferSwizzle, 0, 1, colorBufferViewFirstSlice, 1, colorBufferFormat, colorBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); LatteRenderTarget_applyTextureColorClear(newColorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter); } } diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 21b49c9..d6f576d 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1199,6 +1199,15 @@ std::vector& LatteTexture::GetAllTextures() return sAllTextures; } +bool LatteTexture_GX2FormatHasStencil(bool isDepth, Latte::E_GX2SURFFMT format) +{ + if (!isDepth) + return false; + return format == Latte::E_GX2SURFFMT::D24_S8_UNORM || + format == Latte::E_GX2SURFFMT::D24_S8_FLOAT || + format == Latte::E_GX2SURFFMT::D32_S8_FLOAT; +} + LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { @@ -1217,6 +1226,7 @@ LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddre this->mipLevels = mipLevels; this->tileMode = tileMode; this->isDepth = isDepth; + this->hasStencil = LatteTexture_GX2FormatHasStencil(isDepth, format); this->physMipAddress = physMipAddress; this->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); this->lastWriteEventCounter = LatteTexture_getNextUpdateEventCounter(); diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.h b/src/Cafe/HW/Latte/Core/LatteTexture.h index b46c132..6c09e84 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.h +++ b/src/Cafe/HW/Latte/Core/LatteTexture.h @@ -27,6 +27,8 @@ public: LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); virtual ~LatteTexture(); + virtual void AllocateOnHost() = 0; + LatteTextureView* GetOrCreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { for (auto& itr : views) diff --git a/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp b/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp index a71bd6a..3145e90 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp @@ -316,7 +316,7 @@ void LatteTexture_Delete(LatteTexture* texture) delete[] texture->sliceMipInfo; texture->sliceMipInfo = nullptr; } - g_renderer->texture_destroy(texture); + delete texture; } /* diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp index 862fff0..c06a3bf 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp @@ -621,7 +621,7 @@ void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIn if (tex->isDataDefined == false) { - g_renderer->texture_reserveTextureOnGPU(tex); + tex->AllocateOnHost(); tex->isDataDefined = true; // if decoder is not set then clear texture // on Vulkan this is used to make sure the texture is no longer in UNDEFINED layout diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index cd36361..5880592 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -5,20 +5,6 @@ #include "config/LaunchSettings.h" -GLuint texIdPool[64]; -sint32 texIdPoolIndex = 64; - -static GLuint _genTextureHandleGL() -{ - if (texIdPoolIndex == 64) - { - glGenTextures(64, texIdPool); - texIdPoolIndex = 0; - } - texIdPoolIndex++; - return texIdPool[texIdPoolIndex - 1]; -} - LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth) @@ -29,7 +15,6 @@ LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipA GetOpenGLFormatInfo(isDepth, overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)overwriteInfo.format : format, dim, &glFormatInfo); this->glInternalFormat = glFormatInfo.glInternalFormat; this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; - this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format? // set debug name bool useGLDebugNames = false; #ifdef CEMU_DEBUG_ASSERT @@ -88,34 +73,34 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma { if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM) { - formatInfoOut->setDepthFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, true); + formatInfoOut->setFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8); return; } else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT) { - formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true); + formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV); formatInfoOut->markAsAlternativeFormat(); return; } else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) { - formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true); + formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV); return; } else if (format == Latte::E_GX2SURFFMT::D32_FLOAT) { - formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, false); + formatInfoOut->setFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::D16_UNORM) { - formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); + formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT); return; } // unsupported depth format cemuLog_log(LogType::Force, "OpenGL: Unsupported texture depth format 0x{:04x}", (uint32)format); // use placeholder format - formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); + formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT); formatInfoOut->markAsAlternativeFormat(); return; } @@ -496,3 +481,49 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma formatInfoOut->glIsCompressed = glIsCompressed; formatInfoOut->isUsingAlternativeFormat = isUsingAlternativeFormat; } + +void LatteTextureGL::AllocateOnHost() +{ + auto hostTexture = this; + cemu_assert_debug(hostTexture->isDataDefined == false); + sint32 effectiveBaseWidth = hostTexture->width; + sint32 effectiveBaseHeight = hostTexture->height; + sint32 effectiveBaseDepth = hostTexture->depth; + if (hostTexture->overwriteInfo.hasResolutionOverwrite) + { + effectiveBaseWidth = hostTexture->overwriteInfo.width; + effectiveBaseHeight = hostTexture->overwriteInfo.height; + effectiveBaseDepth = hostTexture->overwriteInfo.depth; + } + // calculate mip count + sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); + mipLevels = std::max(mipLevels, 1); + // create immutable storage + if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) + { + cemu_assert_debug(effectiveBaseDepth == 1); + glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_1D) + { + cemu_assert_debug(effectiveBaseHeight == 1); + cemu_assert_debug(effectiveBaseDepth == 1); + glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + { + glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_3D) + { + glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) + { + glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); + } + else + { + cemu_assert_unimplemented(); + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h index 9169bb2..abfb0d4 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h @@ -11,6 +11,8 @@ public: ~LatteTextureGL(); + void AllocateOnHost() override; + static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType); protected: @@ -23,7 +25,6 @@ public: sint32 glSuppliedFormat; sint32 glSuppliedFormatType; bool glIsCompressed; - bool hasStencil{}; bool isUsingAlternativeFormat{}; void setFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) @@ -34,15 +35,6 @@ public: this->glIsCompressed = false; } - void setDepthFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType, bool hasStencil) - { - this->glInternalFormat = glInternalFormat; - this->glSuppliedFormat = glSuppliedFormat; - this->glSuppliedFormatType = glSuppliedFormatType; - this->glIsCompressed = false; - this->hasStencil = hasStencil; - } - void setCompressed(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) { setFormat(glInternalFormat, glSuppliedFormat, glSuppliedFormatType); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 943e39a..604744c 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -1002,57 +1002,6 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT return texDecoder; } -void OpenGLRenderer::texture_destroy(LatteTexture* hostTexture) -{ - delete hostTexture; -} - -void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneric) -{ - auto hostTexture = (LatteTextureGL*)hostTextureGeneric; - cemu_assert_debug(hostTexture->isDataDefined == false); - sint32 effectiveBaseWidth = hostTexture->width; - sint32 effectiveBaseHeight = hostTexture->height; - sint32 effectiveBaseDepth = hostTexture->depth; - if (hostTexture->overwriteInfo.hasResolutionOverwrite) - { - effectiveBaseWidth = hostTexture->overwriteInfo.width; - effectiveBaseHeight = hostTexture->overwriteInfo.height; - effectiveBaseDepth = hostTexture->overwriteInfo.depth; - } - // calculate mip count - sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); - mipLevels = std::max(mipLevels, 1); - // create immutable storage - if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) - { - cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_1D) - { - cemu_assert_debug(effectiveBaseHeight == 1); - cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) - { - glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_3D) - { - glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) - { - glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); - } - else - { - cemu_assert_unimplemented(); - } -} - // use standard API to upload texture data void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 026264c..3a89219 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -66,14 +66,11 @@ public: void renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler); // texture functions - void texture_destroy(LatteTexture* hostTexture) override; - void* texture_acquireTextureUploadBuffer(uint32 size) override; void texture_releaseTextureUploadBuffer(uint8* mem) override; TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; - void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override; void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 93edaf8..2a9a1d1 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -97,14 +97,11 @@ public: virtual void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) = 0; // texture functions - virtual void texture_destroy(LatteTexture* hostTexture) = 0; - virtual void* texture_acquireTextureUploadBuffer(uint32 size) = 0; virtual void texture_releaseTextureUploadBuffer(uint8* mem) = 0; virtual TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) = 0; - virtual void texture_reserveTextureOnGPU(LatteTexture* hostTexture) = 0; virtual void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) = 0; virtual void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) = 0; virtual void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp index 66f7ba9..8a99900 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp @@ -44,9 +44,9 @@ CachedFBOVk::~CachedFBOVk() while (!m_usedByPipelines.empty()) delete m_usedByPipelines[0]; auto vkr = VulkanRenderer::GetInstance(); - vkr->releaseDestructibleObject(m_vkrObjFramebuffer); + vkr->ReleaseDestructibleObject(m_vkrObjFramebuffer); m_vkrObjFramebuffer = nullptr; - vkr->releaseDestructibleObject(m_vkrObjRenderPass); + vkr->ReleaseDestructibleObject(m_vkrObjRenderPass); m_vkrObjRenderPass = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp index aae7e9d..f0e2295 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp @@ -79,14 +79,14 @@ LatteTextureViewVk::~LatteTextureViewVk() delete list_descriptorSets[0]; if (m_smallCacheView0) - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView0); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView0); if (m_smallCacheView1) - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView1); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView1); if (m_fallbackCache) { for (auto& itr : *m_fallbackCache) - VulkanRenderer::GetInstance()->releaseDestructibleObject(itr.second); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(itr.second); delete m_fallbackCache; m_fallbackCache = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index b5f6270..a62741e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -46,7 +46,7 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di VulkanRenderer::FormatInfoVK texFormatInfo; vkRenderer->GetTextureFormatInfoVK(format, isDepth, dim, effectiveBaseWidth, effectiveBaseHeight, &texFormatInfo); - hasStencil = (texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + cemu_assert_debug(hasStencil == ((texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0)); imageInfo.format = texFormatInfo.vkImageFormat; vkObjTex->m_imageAspect = texFormatInfo.vkImageAspect; @@ -117,7 +117,7 @@ LatteTextureVk::~LatteTextureVk() m_vkr->surfaceCopy_notifyTextureRelease(this); - VulkanRenderer::GetInstance()->releaseDestructibleObject(vkObjTex); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(vkObjTex); vkObjTex = nullptr; } @@ -130,12 +130,8 @@ LatteTextureView* LatteTextureVk::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFF return new LatteTextureViewVk(m_vkr->GetLogicalDevice(), this, dim, format, firstMip, mipCount, firstSlice, sliceCount); } -void LatteTextureVk::setAllocation(struct VkImageMemAllocation* memAllocation) +void LatteTextureVk::AllocateOnHost() { - vkObjTex->m_allocation = memAllocation; -} - -struct VkImageMemAllocation* LatteTextureVk::getAllocation() const -{ - return vkObjTex->m_allocation; + auto allocationInfo = VulkanRenderer::GetInstance()->GetMemoryManager()->imageMemoryAllocate(GetImageObj()->m_image); + vkObjTex->m_allocation = allocationInfo; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h index 714c4e1..612e2e7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h @@ -14,14 +14,13 @@ public: ~LatteTextureVk(); + void AllocateOnHost() override; + VKRObjectTexture* GetImageObj() const { return vkObjTex; }; VkFormat GetFormat() const { return vkObjTex->m_format; } VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } - void setAllocation(struct VkImageMemAllocation* memAllocation); - struct VkImageMemAllocation* getAllocation() const; - VkImageLayout GetImageLayout(VkImageSubresource& subresource) { cemu_assert_debug(subresource.mipLevel < m_layoutsMips); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 437ef51..15ea6e8 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -207,7 +207,8 @@ RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxH RendererShaderVk::~RendererShaderVk() { - VulkanRenderer::GetInstance()->destroyShader(this); + while (!list_pipelineInfo.empty()) + delete list_pipelineInfo[0]; } void RendererShaderVk::Init() diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index 72a1be4..fd5a5b7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -84,7 +84,7 @@ PipelineInfo::~PipelineInfo() // queue pipeline for destruction if (m_vkrObjPipeline) { - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkrObjPipeline); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkrObjPipeline); m_vkrObjPipeline = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 0ee9f02..2be9a2f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -300,7 +300,7 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span fileData) delete pipelineInfo; delete lcr; delete cachedPipeline; - VulkanRenderer::GetInstance()->releaseDestructibleObject(renderPass); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(renderPass); s_spinlockSharedInternal.unlock(); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index c7f8c04..d030531 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1803,44 +1803,6 @@ void VulkanRenderer::PreparePresentationFrame(bool mainWindow) AcquireNextSwapchainImage(mainWindow); } -void VulkanRenderer::ProcessDestructionQueues(size_t commandBufferIndex) -{ - auto& current_descriptor_cache = m_destructionQueues.m_cmd_descriptor_set_objects[commandBufferIndex]; - if (!current_descriptor_cache.empty()) - { - assert_dbg(); - //for (const auto& descriptor : current_descriptor_cache) - //{ - // vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, 1, &descriptor); - // performanceMonitor.vk.numDescriptorSets.decrement(); - //} - - current_descriptor_cache.clear(); - } - - // destroy buffers - for (auto& itr : m_destructionQueues.m_buffers[commandBufferIndex]) - vkDestroyBuffer(m_logicalDevice, itr, nullptr); - m_destructionQueues.m_buffers[commandBufferIndex].clear(); - - // destroy device memory objects - for (auto& itr : m_destructionQueues.m_memory[commandBufferIndex]) - vkFreeMemory(m_logicalDevice, itr, nullptr); - m_destructionQueues.m_memory[commandBufferIndex].clear(); - - // destroy image views - for (auto& itr : m_destructionQueues.m_cmd_image_views[commandBufferIndex]) - vkDestroyImageView(m_logicalDevice, itr, nullptr); - m_destructionQueues.m_cmd_image_views[commandBufferIndex].clear(); - - // destroy host textures - for (auto itr : m_destructionQueues.m_host_textures[commandBufferIndex]) - delete itr; - m_destructionQueues.m_host_textures[commandBufferIndex].clear(); - - ProcessDestructionQueue2(); -} - void VulkanRenderer::InitFirstCommandBuffer() { cemu_assert_debug(m_state.currentCommandBuffer == nullptr); @@ -1869,7 +1831,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers() VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]); if (fenceStatus == VK_SUCCESS) { - ProcessDestructionQueues(m_commandBufferSyncIndex); + ProcessDestructionQueue(); m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size(); memoryManager->cleanupBuffers(m_countCommandBufferFinished); m_countCommandBufferFinished++; @@ -3035,48 +2997,7 @@ TextureDecoder* VulkanRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT return texFormatInfo.decoder; } -void VulkanRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture) -{ - LatteTextureVk* vkTexture = (LatteTextureVk*)hostTexture; - auto allocationInfo = memoryManager->imageMemoryAllocate(vkTexture->GetImageObj()->m_image); - vkTexture->setAllocation(allocationInfo); -} - -void VulkanRenderer::texture_destroy(LatteTexture* hostTexture) -{ - LatteTextureVk* texVk = (LatteTextureVk*)hostTexture; - delete texVk; -} - -void VulkanRenderer::destroyViewDepr(VkImageView imageView) -{ - cemu_assert_debug(false); - - m_destructionQueues.m_cmd_image_views[m_commandBufferIndex].emplace_back(imageView); -} - -void VulkanRenderer::destroyBuffer(VkBuffer buffer) -{ - m_destructionQueues.m_buffers[m_commandBufferIndex].emplace_back(buffer); -} - -void VulkanRenderer::destroyDeviceMemory(VkDeviceMemory mem) -{ - m_destructionQueues.m_memory[m_commandBufferIndex].emplace_back(mem); -} - -void VulkanRenderer::destroyPipelineInfo(PipelineInfo* pipelineInfo) -{ - cemu_assert_debug(false); -} - -void VulkanRenderer::destroyShader(RendererShaderVk* shader) -{ - while (!shader->list_pipelineInfo.empty()) - delete shader->list_pipelineInfo[0]; -} - -void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructibleObject) +void VulkanRenderer::ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject) { // destroy immediately if possible if (destructibleObject->canDestroy()) @@ -3090,7 +3011,7 @@ void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructib m_spinlockDestructionQueue.unlock(); } -void VulkanRenderer::ProcessDestructionQueue2() +void VulkanRenderer::ProcessDestructionQueue() { m_spinlockDestructionQueue.lock(); for (auto it = m_destructionQueue.begin(); it != m_destructionQueue.end();) @@ -3139,7 +3060,7 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo() performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers); performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers); - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkObjDescriptorSet); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet); m_vkObjDescriptorSet = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 226edad..e0a4c75 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -231,7 +231,6 @@ public: void DrawEmptyFrame(bool mainWindow) override; void PreparePresentationFrame(bool mainWindow); - void ProcessDestructionQueues(size_t commandBufferIndex); void InitFirstCommandBuffer(); void ProcessFinishedCommandBuffers(); void WaitForNextFinishedCommandBuffer(); @@ -244,15 +243,9 @@ public: bool HasCommandBufferFinished(uint64 commandBufferId) const; void WaitCommandBufferFinished(uint64 commandBufferId); - // clean up (deprecated) - void destroyViewDepr(VkImageView imageView); - void destroyBuffer(VkBuffer buffer); - void destroyDeviceMemory(VkDeviceMemory mem); - void destroyPipelineInfo(PipelineInfo* pipelineInfo); - void destroyShader(RendererShaderVk* shader); - // clean up (new) - void releaseDestructibleObject(VKRDestructibleObject* destructibleObject); - void ProcessDestructionQueue2(); + // resource destruction queue + void ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject); + void ProcessDestructionQueue(); FSpinlock m_spinlockDestructionQueue; std::vector m_destructionQueue; @@ -290,9 +283,6 @@ public: TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; - void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override; - void texture_destroy(LatteTexture* hostTexture) override; - void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; @@ -634,15 +624,6 @@ private: // command buffer, garbage collection, synchronization static constexpr uint32 kCommandBufferPoolSize = 128; - struct - { - std::array, kCommandBufferPoolSize> m_cmd_descriptor_set_objects; - std::array, kCommandBufferPoolSize> m_cmd_image_views; - std::array, kCommandBufferPoolSize> m_host_textures; - std::array, kCommandBufferPoolSize> m_buffers; - std::array, kCommandBufferPoolSize> m_memory; - }m_destructionQueues; - size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) std::array m_cmd_buffer_fences; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index 479b7e6..bf33ed9 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -813,9 +813,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur { if (p) { - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjDescriptorSet); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjDescriptorSet); p->vkObjDescriptorSet = nullptr; - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView); p->vkObjImageView = nullptr; } } @@ -829,9 +829,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur { if (p) { - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjFramebuffer); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjFramebuffer); p->vkObjFramebuffer = nullptr; - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView); p->vkObjImageView = nullptr; } } diff --git a/src/imgui/imgui_impl_vulkan.cpp b/src/imgui/imgui_impl_vulkan.cpp index f0006b4..723f153 100644 --- a/src/imgui/imgui_impl_vulkan.cpp +++ b/src/imgui/imgui_impl_vulkan.cpp @@ -245,18 +245,13 @@ static void check_vk_result(VkResult err) static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) { - VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); - - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + vkDeviceWaitIdle(v->Device); // make sure previously created buffer is not in use anymore VkResult err; if (buffer != VK_NULL_HANDLE) - { - vkRenderer->destroyBuffer(buffer); - } + vkDestroyBuffer(v->Device, buffer, v->Allocator); if (buffer_memory != VK_NULL_HANDLE) - { - vkRenderer->destroyDeviceMemory(buffer_memory); - } + vkFreeMemory(v->Device, buffer_memory, v->Allocator); VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / g_BufferMemoryAlignment + 1) * g_BufferMemoryAlignment; VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; From 731713de3ac97e6cee2504fd6ce3e7ca943fc282 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 14 Mar 2024 03:10:10 +0100 Subject: [PATCH 035/130] OpenGL: Remove "-legacy" flag "Intel legacy mode" was a special mode to workaround various Intel OpenGL driver limitations during the earlier years of Cemu. It's been unmaintained for years and no longer serves a purpose. If we ever bring back compatibility with ancient Intel GPUs it should be done in a more structured way than a blunt yes/no flag. --- src/Cafe/GraphicPack/GraphicPack2.cpp | 3 - src/Cafe/HW/Latte/Core/LatteConst.h | 2 - src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 21 +++---- src/Cafe/HW/Latte/Core/LatteThread.cpp | 8 +-- .../Latte/Renderer/OpenGL/LatteTextureGL.cpp | 62 +++++-------------- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 44 +------------ .../Renderer/OpenGL/OpenGLRendererCore.cpp | 2 +- src/Cafe/HW/Latte/Renderer/Renderer.h | 2 - src/config/LaunchSettings.cpp | 2 - src/config/LaunchSettings.h | 2 - src/gui/guiWrapper.cpp | 4 -- 11 files changed, 26 insertions(+), 126 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 365e6e3..b581316 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -878,9 +878,6 @@ bool GraphicPack2::Activate() if (m_gfx_vendor.has_value()) { auto vendor = g_renderer->GetVendor(); - if (vendor == GfxVendor::IntelLegacy || vendor == GfxVendor::IntelNoLegacy) - vendor = GfxVendor::Intel; - if (m_gfx_vendor.value() != vendor) return false; } diff --git a/src/Cafe/HW/Latte/Core/LatteConst.h b/src/Cafe/HW/Latte/Core/LatteConst.h index 04c7b88..ebe741e 100644 --- a/src/Cafe/HW/Latte/Core/LatteConst.h +++ b/src/Cafe/HW/Latte/Core/LatteConst.h @@ -82,8 +82,6 @@ #define GLVENDOR_UNKNOWN (0) #define GLVENDOR_AMD (1) // AMD/ATI #define GLVENDOR_NVIDIA (2) -#define GLVENDOR_INTEL_LEGACY (3) -#define GLVENDOR_INTEL_NOLEGACY (4) #define GLVENDOR_INTEL (5) #define GLVENDOR_APPLE (6) diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index b9ccbac..50aa4d8 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -229,21 +229,16 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u // if this texture is bound multiple times then use alternative views if (textureView->lastTextureBindIndex == LatteGPUState.textureBindCounter) { - // Intel driver has issues with textures that have multiple views bound and used by a shader, causes a softlock in BotW - // therefore we disable this on Intel - if (LatteGPUState.glVendor != GLVENDOR_INTEL_NOLEGACY) + LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView; + // get next unused alternative texture view + while (true) { - LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView; - // get next unused alternative texture view - while (true) - { - textureViewGL = textureViewGL->GetAlternativeView(); - if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter) - break; - } - textureView = textureViewGL; + textureViewGL = textureViewGL->GetAlternativeView(); + if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter) + break; } - } + textureView = textureViewGL; + } textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter; rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler); } diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 60b32ec..bd312d9 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -140,13 +140,7 @@ int Latte_ThreadEntry() case GfxVendor::AMD: LatteGPUState.glVendor = GLVENDOR_AMD; break; - case GfxVendor::IntelLegacy: - LatteGPUState.glVendor = GLVENDOR_INTEL_LEGACY; - break; - case GfxVendor::IntelNoLegacy: - LatteGPUState.glVendor = GLVENDOR_INTEL_NOLEGACY; - break; - case GfxVendor::Intel: + case GfxVendor::Intel: LatteGPUState.glVendor = GLVENDOR_INTEL; break; case GfxVendor::Nvidia: diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index 5880592..fb025eb 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -110,10 +110,6 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma sint32 glInternalFormat; sint32 glSuppliedFormat; sint32 glSuppliedFormatType; - // check if compressed textures should be used - bool allowCompressedGLFormat = true; - if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) - allowCompressedGLFormat = false; // compressed formats seem to cause more harm than good on Intel // get format information if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) { @@ -149,20 +145,11 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma else if (format == Latte::E_GX2SURFFMT::BC1_UNORM || format == Latte::E_GX2SURFFMT::BC1_SRGB) { - if (allowCompressedGLFormat) - { - if (format == Latte::E_GX2SURFFMT::BC1_SRGB) - formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1); - else - formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1); - return; - } + if (format == Latte::E_GX2SURFFMT::BC1_SRGB) + formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1); else - { - formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); - formatInfoOut->markAsAlternativeFormat(); - return; - } + formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1); + return; } else if (format == Latte::E_GX2SURFFMT::BC2_UNORM || format == Latte::E_GX2SURFFMT::BC2_SRGB) { @@ -173,28 +160,18 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma } else if (format == Latte::E_GX2SURFFMT::BC3_UNORM || format == Latte::E_GX2SURFFMT::BC3_SRGB) { - if (allowCompressedGLFormat) - { - if (format == Latte::E_GX2SURFFMT::BC3_SRGB) - formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1); - else - formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1); - return; - } + if (format == Latte::E_GX2SURFFMT::BC3_SRGB) + formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1); else - { - // todo: SRGB support - formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); - formatInfoOut->markAsAlternativeFormat(); - return; - } + formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1); + return; } else if (format == Latte::E_GX2SURFFMT::BC4_UNORM || format == Latte::E_GX2SURFFMT::BC4_SNORM) { + bool allowCompressed = true; if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) - allowCompressedGLFormat = false; // RGTC1 does not support non-2D textures - - if (allowCompressedGLFormat) + allowCompressed = false; // RGTC1 does not support non-2D textures + if (allowCompressed) { if (format == Latte::E_GX2SURFFMT::BC4_UNORM) formatInfoOut->setCompressed(GL_COMPRESSED_RED_RGTC1, -1, -1); @@ -211,20 +188,11 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma } else if (format == Latte::E_GX2SURFFMT::BC5_UNORM || format == Latte::E_GX2SURFFMT::BC5_SNORM) { - if (allowCompressedGLFormat) - { - if (format == Latte::E_GX2SURFFMT::BC5_SNORM) - formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1); - else - formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1); - return; - } + if (format == Latte::E_GX2SURFFMT::BC5_SNORM) + formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1); else - { - formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_FLOAT); - formatInfoOut->markAsAlternativeFormat(); - return; - } + formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1); + return; } else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 604744c..28e91b8 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -407,10 +407,7 @@ void OpenGLRenderer::GetVendorInformation() } else if (memcmp(glVendorString, "Intel", 5) == 0) { - if (LaunchSettings::ForceIntelLegacyEnabled()) - m_vendor = GfxVendor::IntelLegacy; - else - m_vendor = GfxVendor::IntelNoLegacy; + m_vendor = GfxVendor::Intel; return; } } @@ -849,45 +846,6 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT } return nullptr; } - - if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) - { - if (format == Latte::E_GX2SURFFMT::BC1_UNORM) - { - texDecoder = TextureDecoder_BC1_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC1_SRGB) - { - texDecoder = TextureDecoder_BC1_SRGB_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC3_UNORM) - { - texDecoder = TextureDecoder_BC3_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC3_SRGB) - { - texDecoder = TextureDecoder_BC3_SRGB_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC4_UNORM) - { - texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC4_SNORM) - { - cemu_assert_debug(false); // todo - } - else if (format == Latte::E_GX2SURFFMT::BC5_UNORM) - { - texDecoder = TextureDecoder_BC5_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC5_SNORM) - { - texDecoder = TextureDecoder_BC5_SNORM_uncompress::getInstance(); - } - if (texDecoder) - return texDecoder; - } - if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) texDecoder = TextureDecoder_R4_G4_UNORM_To_RGBA4::getInstance(); else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index 51d0d20..571961f 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -950,7 +950,7 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; if (streamoutEnable) { - if (glBeginTransformFeedback == nullptr || LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY) + if (glBeginTransformFeedback == nullptr) { cemu_assert_debug(false); return; // transform feedback not supported diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 2a9a1d1..0b694bb 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -21,8 +21,6 @@ enum class GfxVendor Generic, AMD, - IntelLegacy, - IntelNoLegacy, Intel, Nvidia, Apple, diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index fdd4cc6..b7a79a1 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -174,8 +174,6 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) if (vm.count("nsight")) s_nsight_mode = vm["nsight"].as(); - if (vm.count("legacy")) - s_force_intel_legacy = vm["legacy"].as(); if(vm.count("force-interpreter")) s_force_interpreter = vm["force-interpreter"].as(); diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index f87dc60..be989e6 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -24,7 +24,6 @@ public: static bool GDBStubEnabled() { return s_enable_gdbstub; } static bool NSightModeEnabled() { return s_nsight_mode; } - static bool ForceIntelLegacyEnabled() { return s_force_intel_legacy; } static bool ForceInterpreter() { return s_force_interpreter; }; @@ -44,7 +43,6 @@ private: inline static bool s_enable_gdbstub = false; inline static bool s_nsight_mode = false; - inline static bool s_force_intel_legacy = false; inline static bool s_force_interpreter = false; diff --git a/src/gui/guiWrapper.cpp b/src/gui/guiWrapper.cpp index 68f9759..ce043ba 100644 --- a/src/gui/guiWrapper.cpp +++ b/src/gui/guiWrapper.cpp @@ -93,10 +93,6 @@ void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps) const char* graphicMode = "[Generic]"; if (LatteGPUState.glVendor == GLVENDOR_AMD) graphicMode = "[AMD GPU]"; - else if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) - graphicMode = "[Intel GPU - Legacy]"; - else if (LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY) - graphicMode = "[Intel GPU]"; else if (LatteGPUState.glVendor == GLVENDOR_INTEL) graphicMode = "[Intel GPU]"; else if (LatteGPUState.glVendor == GLVENDOR_NVIDIA) From eaa82817dd235b5067002df76b06e66a550ac1d3 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:06:48 +0100 Subject: [PATCH 036/130] Update thread names (#1120) --- src/Cafe/HW/Espresso/Debugger/GDBStub.cpp | 2 +- src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp | 2 +- src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp | 2 ++ .../HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp | 2 ++ src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 1 + src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp | 1 + src/Cafe/IOSU/ODM/iosu_odm.cpp | 2 ++ src/Cafe/IOSU/PDM/iosu_pdm.cpp | 2 ++ src/Cafe/IOSU/nn/iosu_nn_service.cpp | 1 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 2 +- src/Cafe/TitleList/SaveList.cpp | 2 ++ src/Cafe/TitleList/TitleList.cpp | 1 + src/Cemu/FileCache/FileCache.cpp | 2 +- src/gui/components/wxGameList.cpp | 1 + src/gui/guiWrapper.cpp | 2 +- src/input/InputManager.cpp | 2 +- src/input/api/DSU/DSUControllerProvider.cpp | 4 ++-- src/input/api/SDL/SDLControllerProvider.cpp | 2 +- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 4 ++-- src/util/helpers/helpers.cpp | 4 +++- 20 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp index 6cddae0..e54fae1 100644 --- a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp +++ b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp @@ -297,7 +297,7 @@ bool GDBServer::Initialize() void GDBServer::ThreadFunc() { - SetThreadName("GDBServer::ThreadFunc"); + SetThreadName("GDBServer"); while (!m_stopRequested) { diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index f4d063f..24e87bd 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -294,7 +294,7 @@ std::atomic_bool s_recompilerThreadStopSignal{false}; void PPCRecompiler_thread() { - SetThreadName("PPCRecompiler_thread"); + SetThreadName("PPCRecompiler"); while (true) { if(s_recompilerThreadStopSignal) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 15ea6e8..50f2c2d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -8,6 +8,7 @@ #include #include +#include bool s_isLoadingShadersVk{ false }; class FileCache* s_spirvCache{nullptr}; @@ -155,6 +156,7 @@ public: void CompilerThreadFunc() { + SetThreadName("vkShaderComp"); while (m_threadsActive.load(std::memory_order::relaxed)) { s_compilationQueueCount.decrementWithWait(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 2be9a2f..123120d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -408,6 +408,7 @@ bool VulkanPipelineStableCache::DeserializePipeline(MemStreamReader& memReader, int VulkanPipelineStableCache::CompilerThread() { + SetThreadName("plCacheCompiler"); while (m_numCompilationThreads != 0) { std::vector pipelineData = m_compilationQueue.pop(); @@ -421,6 +422,7 @@ int VulkanPipelineStableCache::CompilerThread() void VulkanPipelineStableCache::WorkerThread() { + SetThreadName("plCacheWriter"); while (true) { CachedPipeline* job; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index d030531..d62b61a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1986,6 +1986,7 @@ void VulkanRenderer::WaitCommandBufferFinished(uint64 commandBufferId) void VulkanRenderer::PipelineCacheSaveThread(size_t cache_size) { + SetThreadName("vkDriverPlCache"); const auto dir = ActiveSettings::GetCachePath("shaderCache/driver/vk"); if (!fs::exists(dir)) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 320357f..d510140 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -190,6 +190,7 @@ std::queue g_compilePipelineRequests; void compilePipeline_thread(sint32 threadIndex) { + SetThreadName("compilePl"); #ifdef _WIN32 // one thread runs at normal priority while the others run at lower priority if(threadIndex != 0) diff --git a/src/Cafe/IOSU/ODM/iosu_odm.cpp b/src/Cafe/IOSU/ODM/iosu_odm.cpp index 3dc8e43..aae3c50 100644 --- a/src/Cafe/IOSU/ODM/iosu_odm.cpp +++ b/src/Cafe/IOSU/ODM/iosu_odm.cpp @@ -1,3 +1,4 @@ +#include #include "iosu_odm.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" @@ -79,6 +80,7 @@ namespace iosu void ODMServiceThread() { + SetThreadName("ODMService"); 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); diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.cpp b/src/Cafe/IOSU/PDM/iosu_pdm.cpp index e54529a..d94b1db 100644 --- a/src/Cafe/IOSU/PDM/iosu_pdm.cpp +++ b/src/Cafe/IOSU/PDM/iosu_pdm.cpp @@ -1,3 +1,4 @@ +#include #include "iosu_pdm.h" #include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" @@ -387,6 +388,7 @@ namespace iosu void TimeTrackingThread(uint64 titleId) { + SetThreadName("PlayDiaryThread"); PlayStatsEntry* playStatsEntry = PlayStats_BeginNewTracking(titleId); auto startTime = std::chrono::steady_clock::now(); diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index b3b2d4c..1fb5c77 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -155,6 +155,7 @@ namespace iosu void IPCService::ServiceThread() { + SetThreadName("IPCService"); m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 3701a4d..8ce5de0 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1168,7 +1168,7 @@ namespace coreinit void OSSchedulerCoreEmulationThread(void* _assignedCoreIndex) { - SetThreadName(fmt::format("OSSchedulerThread[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); + SetThreadName(fmt::format("OSSched[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); t_assignedCoreIndex = (sint32)(uintptr_t)_assignedCoreIndex; #if defined(ARCH_X86_64) _mm_setcsr(_mm_getcsr() | 0x8000); // flush denormals to zero diff --git a/src/Cafe/TitleList/SaveList.cpp b/src/Cafe/TitleList/SaveList.cpp index a86e849..d0c0e3e 100644 --- a/src/Cafe/TitleList/SaveList.cpp +++ b/src/Cafe/TitleList/SaveList.cpp @@ -1,5 +1,6 @@ #include "SaveList.h" #include +#include std::mutex sSLMutex; fs::path sSLMLCPath; @@ -44,6 +45,7 @@ void CafeSaveList::Refresh() void CafeSaveList::RefreshThreadWorker() { + SetThreadName("SaveListWorker"); // clear save list for (auto& itSaveInfo : sSLList) { diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 1cc084b..c288dd1 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -258,6 +258,7 @@ void CafeTitleList::AddTitleFromPath(fs::path path) bool CafeTitleList::RefreshWorkerThread() { + SetThreadName("TitleListWorker"); while (sTLRefreshRequests.load()) { sTLRefreshRequests.store(0); diff --git a/src/Cemu/FileCache/FileCache.cpp b/src/Cemu/FileCache/FileCache.cpp index aa7770a..b284b66 100644 --- a/src/Cemu/FileCache/FileCache.cpp +++ b/src/Cemu/FileCache/FileCache.cpp @@ -50,7 +50,7 @@ struct _FileCacheAsyncWriter private: void FileCacheThread() { - SetThreadName("fileCache_thread"); + SetThreadName("fileCache"); while (true) { std::unique_lock lock(m_fileCacheMutex); diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 88934cd..8e8f3c4 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1194,6 +1194,7 @@ void wxGameList::RemoveCache(const std::list& cachePaths, const std::s void wxGameList::AsyncWorkerThread() { + SetThreadName("GameListWorker"); while (m_async_worker_active) { m_async_task_count.decrementWithWait(); diff --git a/src/gui/guiWrapper.cpp b/src/gui/guiWrapper.cpp index ce043ba..d887e89 100644 --- a/src/gui/guiWrapper.cpp +++ b/src/gui/guiWrapper.cpp @@ -37,7 +37,7 @@ void _wxLaunch() void gui_create() { - SetThreadName("MainThread"); + SetThreadName("cemu"); #if BOOST_OS_WINDOWS // on Windows wxWidgets there is a bug where wxDirDialog->ShowModal will deadlock in Windows internals somehow // moving the UI thread off the main thread fixes this diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index 4e7848c..d928e46 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -934,7 +934,7 @@ std::optional InputManager::get_right_down_mouse_info(bool* is_pad) void InputManager::update_thread() { - SetThreadName("InputManager::update_thread"); + SetThreadName("Input_update"); while (!m_update_thread_shutdown.load(std::memory_order::relaxed)) { std::shared_lock lock(m_mutex); diff --git a/src/input/api/DSU/DSUControllerProvider.cpp b/src/input/api/DSU/DSUControllerProvider.cpp index 0fa93e2..37f9277 100644 --- a/src/input/api/DSU/DSUControllerProvider.cpp +++ b/src/input/api/DSU/DSUControllerProvider.cpp @@ -250,7 +250,7 @@ MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const void DSUControllerProvider::reader_thread() { - SetThreadName("DSUControllerProvider::reader_thread"); + SetThreadName("DSU-reader"); bool first_read = true; while (m_running.load(std::memory_order_relaxed)) { @@ -383,7 +383,7 @@ void DSUControllerProvider::reader_thread() void DSUControllerProvider::writer_thread() { - SetThreadName("DSUControllerProvider::writer_thread"); + SetThreadName("DSU-writer"); while (m_running.load(std::memory_order_relaxed)) { std::unique_lock lock(m_writer_mutex); diff --git a/src/input/api/SDL/SDLControllerProvider.cpp b/src/input/api/SDL/SDLControllerProvider.cpp index 9e0c09b..9b21b30 100644 --- a/src/input/api/SDL/SDLControllerProvider.cpp +++ b/src/input/api/SDL/SDLControllerProvider.cpp @@ -124,7 +124,7 @@ MotionSample SDLControllerProvider::motion_sample(int diid) void SDLControllerProvider::event_thread() { - SetThreadName("SDLControllerProvider::event_thread"); + SetThreadName("SDL_events"); while (m_running.load(std::memory_order_relaxed)) { SDL_Event event{}; diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 55f28c0..5aac3fe 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -143,7 +143,7 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz void WiimoteControllerProvider::reader_thread() { - SetThreadName("WiimoteControllerProvider::reader_thread"); + SetThreadName("Wiimote-reader"); std::chrono::steady_clock::time_point lastCheck = {}; while (m_running.load(std::memory_order_relaxed)) { @@ -878,7 +878,7 @@ void WiimoteControllerProvider::set_motion_plus(size_t index, bool state) void WiimoteControllerProvider::writer_thread() { - SetThreadName("WiimoteControllerProvider::writer_thread"); + SetThreadName("Wiimote-writer"); while (m_running.load(std::memory_order_relaxed)) { std::unique_lock writer_lock(m_writer_mutex); diff --git a/src/util/helpers/helpers.cpp b/src/util/helpers/helpers.cpp index b556db3..7e22e9f 100644 --- a/src/util/helpers/helpers.cpp +++ b/src/util/helpers/helpers.cpp @@ -155,7 +155,9 @@ void SetThreadName(const char* name) #elif BOOST_OS_MACOS pthread_setname_np(name); #else - pthread_setname_np(pthread_self(), name); + if(std::strlen(name) > 15) + cemuLog_log(LogType::Force, "Truncating thread name {} because it was longer than 15 characters", name); + pthread_setname_np(pthread_self(), std::string{name}.substr(0,15).c_str()); #endif } From 42d14eec96c6e63ea93cfb7daa49655b7139c997 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 18 Mar 2024 09:18:02 +0100 Subject: [PATCH 037/130] Minor code improvements (#1124) --- .../LatteDecompilerEmitGLSL.cpp | 2 +- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 2 -- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 10 +++++----- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 3 +-- src/input/motion/MotionSample.h | 20 +++++++++---------- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index f3d2c7a..e19535b 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -973,7 +973,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec } else { - cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel 0x%x\n", aluInstruction->sourceOperand[operandIndex].sel); + cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel {:#x}\n", aluInstruction->sourceOperand[operandIndex].sel); debugBreakpoint(); } diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 3a89219..313ea3c 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -30,8 +30,6 @@ public: void Flush(bool waitIdle = false) override; void NotifyLatteCommandProcessorIdle() override; - void UpdateVSyncState(); - void EnableDebugMode() override; void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index d62b61a..02bb1e7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -706,8 +706,8 @@ SwapchainInfoVk& VulkanRenderer::GetChainInfo(bool mainWindow) const void VulkanRenderer::StopUsingPadAndWait() { - m_destroyPadSwapchainNextAcquire = true; - m_padCloseReadySemaphore.wait(); + m_destroyPadSwapchainNextAcquire.test_and_set(); + m_destroyPadSwapchainNextAcquire.wait(true); } bool VulkanRenderer::IsPadWindowActive() @@ -2557,11 +2557,11 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow) if(!IsSwapchainInfoValid(mainWindow)) return false; - if(!mainWindow && m_destroyPadSwapchainNextAcquire) + if(!mainWindow && m_destroyPadSwapchainNextAcquire.test()) { RecreateSwapchain(mainWindow, true); - m_destroyPadSwapchainNextAcquire = false; - m_padCloseReadySemaphore.notify(); + m_destroyPadSwapchainNextAcquire.clear(); + m_destroyPadSwapchainNextAcquire.notify_all(); return false; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index e0a4c75..47097df 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -414,8 +414,7 @@ private: }m_state; std::unique_ptr m_mainSwapchainInfo{}, m_padSwapchainInfo{}; - Semaphore m_padCloseReadySemaphore; - bool m_destroyPadSwapchainNextAcquire = false; + std::atomic_flag m_destroyPadSwapchainNextAcquire{}; bool IsSwapchainInfoValid(bool mainWindow) const; VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE; diff --git a/src/input/motion/MotionSample.h b/src/input/motion/MotionSample.h index bb47f78..0697711 100644 --- a/src/input/motion/MotionSample.h +++ b/src/input/motion/MotionSample.h @@ -258,48 +258,48 @@ DRC flat on table, screen facing up. Top pointing away (away from person, pointi 0.03 0.99 -0.13 0.01 0.13 0.99 -Turned 45° to the right: +Turned 45° to the right: 0.71 -0.03 0.71 0.12 0.99 -0.08 -0.70 0.14 0.70 -Turned 45° to the right (top of GamePad pointing right now): +Turned 45° to the right (top of GamePad pointing right now): 0.08 -0.03 1.00 -> Z points towards person 0.15 0.99 0.01 -0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus -Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): +Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): -1.00 -0.01 0.06 0.00 0.99 0.15 -0.06 0.15 -0.99 -Turned 90° to the right (pointing left): +Turned 90° to the right (pointing left): -0.17 -0.01 -0.99 -0.13 0.99 0.02 0.98 0.13 -0.17 -After another 90° we end up in the initial position: +After another 90° we end up in the initial position: 0.99 -0.03 -0.11 0.01 0.99 -0.13 0.12 0.12 0.99 ------ -From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left +From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left 0.66 -0.75 -0.03 0.74 0.66 -0.11 0.10 0.05 0.99 -Further 45°, GamePad now on its left, screen pointing left: +Further 45°, GamePad now on its left, screen pointing left: -0.03 -1.00 -0.00 0.99 -0.03 -0.15 0.15 -0.01 0.99 -From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right +From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right 0.75 0.65 -0.11 -0.65 0.76 0.07 0.12 0.02 0.99 -From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): +From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): 0.99 -0.05 -0.10 -0.10 0.01 -0.99 0.05 1.00 0.01 @@ -309,7 +309,7 @@ From initial position, stand the GamePad on its top side: 0.09 -0.01 1.00 -0.01 -1.00 -0.01 -Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): +Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): 0.99 -0.03 -0.15 -0.04 -1.00 -0.08 -0.15 0.09 -0.99 From 4d609f06b810a6bc686aa783a2b716e3cd280f0e Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:22:48 +0100 Subject: [PATCH 038/130] InputSettings: Fix controller type counter to restore WPAD limit (#1118) --- src/gui/input/InputSettings2.cpp | 32 ++++---------------------------- src/gui/input/InputSettings2.h | 3 --- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/gui/input/InputSettings2.cpp b/src/gui/input/InputSettings2.cpp index 58c168a..72bf4f7 100644 --- a/src/gui/input/InputSettings2.cpp +++ b/src/gui/input/InputSettings2.cpp @@ -295,32 +295,6 @@ wxWindow* InputSettings2::initialize_page(size_t index) return page; } -std::pair InputSettings2::get_emulated_controller_types() const -{ - size_t vpad = 0, wpad = 0; - for(size_t i = 0; i < m_notebook->GetPageCount(); ++i) - { - auto* page = m_notebook->GetPage(i); - auto* page_data = (wxControllerPageData*)page->GetClientObject(); - if (!page_data) - continue; - - if (!page_data->ref().m_controller) // = disabled - continue; - - const auto api_type = page_data->ref().m_controller->type(); - if (api_type) - continue; - - if (api_type == EmulatedController::VPAD) - ++vpad; - else - ++wpad; - } - - return std::make_pair(vpad, wpad); -} - std::shared_ptr InputSettings2::get_active_controller() const { auto& page_data = get_current_page_data(); @@ -771,14 +745,16 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) wxWindowUpdateLocker lock(emulated_controllers); bool is_gamepad_selected = false; + bool is_wpad_selected = false; const auto selected = emulated_controllers->GetSelection(); const auto selected_value = emulated_controllers->GetStringSelection(); if(selected != wxNOT_FOUND) { is_gamepad_selected = selected_value == to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD)); + is_wpad_selected = !is_gamepad_selected && selected != 0; } - const auto [vpad_count, wpad_count] = get_emulated_controller_types(); + const auto [vpad_count, wpad_count] = InputManager::instance().get_controller_count(); emulated_controllers->Clear(); emulated_controllers->AppendString(_("Disabled")); @@ -786,7 +762,7 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected) emulated_controllers->Append(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD))); - if (wpad_count < InputManager::kMaxWPADControllers || !is_gamepad_selected) + if (wpad_count < InputManager::kMaxWPADControllers || is_wpad_selected) { emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Pro))); emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Classic))); diff --git a/src/gui/input/InputSettings2.h b/src/gui/input/InputSettings2.h index 0131365..1a3c8bb 100644 --- a/src/gui/input/InputSettings2.h +++ b/src/gui/input/InputSettings2.h @@ -27,9 +27,6 @@ private: wxWindow* initialize_page(size_t index); - // count active controllers - std::pair get_emulated_controller_types() const; - // currently selected controller from active tab std::shared_ptr get_active_controller() const; From 17060752b6d5bc952446e26c4cd4b0d259653ced Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 24 Mar 2024 10:57:08 +0100 Subject: [PATCH 039/130] Vulkan: Several swapchain fixes and refactors (#1132) --- .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 91 +++++++++---------- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 25 ++--- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 54 ++++++++--- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 13 ++- 4 files changed, 99 insertions(+), 84 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 14b7a17..b00f549 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -1,15 +1,34 @@ #include "SwapchainInfoVk.h" #include "config/CemuConfig.h" +#include "gui/guiWrapper.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteTiming.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" -void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice) +SwapchainInfoVk::SwapchainInfoVk(bool mainWindow, Vector2i size) : mainWindow(mainWindow), m_desiredExtent(size) { - m_physicalDevice = physicalDevice; - m_logicalDevice = logicalDevice; - const auto details = QuerySwapchainSupport(surface, physicalDevice); + auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; + auto renderer = VulkanRenderer::GetInstance(); + m_instance = renderer->GetVkInstance(); + m_logicalDevice = renderer->GetLogicalDevice(); + m_physicalDevice = renderer->GetPhysicalDevice(); + + m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo); +} + + +SwapchainInfoVk::~SwapchainInfoVk() +{ + Cleanup(); + if(m_surface != VK_NULL_HANDLE) + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); +} + +void SwapchainInfoVk::Create() +{ + const auto details = QuerySwapchainSupport(m_surface, m_physicalDevice); m_surfaceFormat = ChooseSurfaceFormat(details.formats); m_actualExtent = ChooseSwapExtent(details.capabilities); @@ -20,28 +39,28 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe if(image_count < 2) cemuLog_log(LogType::Force, "Vulkan: Swapchain image count less than 2 may cause problems"); - VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(surface, details, m_surfaceFormat, image_count, m_actualExtent); + VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(m_surface, details, m_surfaceFormat, image_count, m_actualExtent); create_info.oldSwapchain = nullptr; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - VkResult result = vkCreateSwapchainKHR(logicalDevice, &create_info, nullptr, &swapchain); + VkResult result = vkCreateSwapchainKHR(m_logicalDevice, &create_info, nullptr, &m_swapchain); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to create a swapchain"); - result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, nullptr); + result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, nullptr); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve the count of swapchain images"); m_swapchainImages.resize(image_count); - result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, m_swapchainImages.data()); + result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, m_swapchainImages.data()); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve swapchain images"); // create default renderpass VkAttachmentDescription colorAttachment = {}; colorAttachment.format = m_surfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -62,7 +81,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - result = vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); + result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); if (result != VK_SUCCESS) UnrecoverableError("Failed to create renderpass for swapchain"); @@ -84,7 +103,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - result = vkCreateImageView(logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); + result = vkCreateImageView(m_logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create imageviews for swapchain"); } @@ -104,7 +123,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe framebufferInfo.width = m_actualExtent.width; framebufferInfo.height = m_actualExtent.height; framebufferInfo.layers = 1; - result = vkCreateFramebuffer(logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); + result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create framebuffer for swapchain"); } @@ -114,7 +133,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_presentSemaphores){ - if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain present"); } @@ -123,14 +142,14 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_acquireSemaphores){ - if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain acquire"); } VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - result = vkCreateFence(logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); + result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); if (result != VK_SUCCESS) UnrecoverableError("Failed to create fence for swapchain"); @@ -167,19 +186,20 @@ void SwapchainInfoVk::Cleanup() if (m_imageAvailableFence) { + WaitAvailableFence(); vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); m_imageAvailableFence = nullptr; } - if (swapchain) + if (m_swapchain) { - vkDestroySwapchainKHR(m_logicalDevice, swapchain, nullptr); - swapchain = VK_NULL_HANDLE; + vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); + m_swapchain = VK_NULL_HANDLE; } } bool SwapchainInfoVk::IsValid() const { - return swapchain && !m_acquireSemaphores.empty(); + return m_swapchain && !m_acquireSemaphores.empty(); } void SwapchainInfoVk::WaitAvailableFence() @@ -207,7 +227,7 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout) ResetAvailableFence(); VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; if (result < 0) @@ -231,35 +251,6 @@ void SwapchainInfoVk::UnrecoverableError(const char* errMsg) throw std::runtime_error(errMsg); } -SwapchainInfoVk::QueueFamilyIndices SwapchainInfoVk::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) -{ - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - QueueFamilyIndices indices; - for (int i = 0; i < (int)queueFamilies.size(); ++i) - { - const auto& queueFamily = queueFamilies[i]; - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) - indices.graphicsFamily = i; - - VkBool32 presentSupport = false; - const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (result != VK_SUCCESS) - throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); - - if (queueFamily.queueCount > 0 && presentSupport) - indices.presentFamily = i; - - if (indices.IsComplete()) - break; - } - - return indices; -} SwapchainInfoVk::SwapchainSupportDetails SwapchainInfoVk::QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device) { @@ -391,7 +382,7 @@ VkSwapchainCreateInfoKHR SwapchainInfoVk::CreateSwapchainCreateInfo(VkSurfaceKHR createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - const QueueFamilyIndices indices = FindQueueFamilies(surface, m_physicalDevice); + const VulkanRenderer::QueueFamilyIndices indices = VulkanRenderer::GetInstance()->FindQueueFamilies(surface, m_physicalDevice); m_swapchainQueueFamilyIndices = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily }; if (indices.graphicsFamily != indices.presentFamily) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index 26dbc7d..0e8c2ad 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -14,14 +14,6 @@ struct SwapchainInfoVk SYNC_AND_LIMIT = 3, // synchronize emulated vsync events to monitor vsync. But skip events if rate higher than virtual vsync period }; - struct QueueFamilyIndices - { - int32_t graphicsFamily = -1; - int32_t presentFamily = -1; - - bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } - }; - struct SwapchainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; @@ -30,7 +22,7 @@ struct SwapchainInfoVk }; void Cleanup(); - void Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice); + void Create(); bool IsValid() const; @@ -45,8 +37,6 @@ struct SwapchainInfoVk static void UnrecoverableError(const char* errMsg); - // todo: move this function somewhere more sensible. Not directly swapchain related - static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); static SwapchainSupportDetails QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device); VkPresentModeKHR ChoosePresentMode(const std::vector& modes); @@ -61,14 +51,10 @@ struct SwapchainInfoVk return m_actualExtent; } - SwapchainInfoVk(VkSurfaceKHR surface, bool mainWindow) - : surface(surface), mainWindow(mainWindow) {} + SwapchainInfoVk(bool mainWindow, Vector2i size); SwapchainInfoVk(const SwapchainInfoVk&) = delete; SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default; - ~SwapchainInfoVk() - { - Cleanup(); - } + ~SwapchainInfoVk(); bool mainWindow{}; @@ -77,11 +63,12 @@ struct SwapchainInfoVk VSync m_vsyncState = VSync::Immediate; bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state + VkInstance m_instance{}; VkPhysicalDevice m_physicalDevice{}; VkDevice m_logicalDevice{}; - VkSurfaceKHR surface{}; + VkSurfaceKHR m_surface{}; VkSurfaceFormatKHR m_surfaceFormat{}; - VkSwapchainKHR swapchain{}; + VkSwapchainKHR m_swapchain{}; Vector2i m_desiredExtent{}; uint32 swapchainImageIndex = (uint32)-1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 02bb1e7..e0ebda2 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -167,6 +167,7 @@ std::vector VulkanRenderer::GetDevices() result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID); } } + vkDestroySurfaceKHR(instance, surface, nullptr); } catch (...) { @@ -441,7 +442,7 @@ VulkanRenderer::VulkanRenderer() } // create logical device - m_indices = SwapchainInfoVk::FindQueueFamilies(surface, m_physicalDevice); + m_indices = FindQueueFamilies(surface, m_physicalDevice); std::set uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily }; std::vector queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies); VkPhysicalDeviceFeatures deviceFeatures = {}; @@ -510,7 +511,7 @@ VulkanRenderer::VulkanRenderer() PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT")); VkDebugUtilsMessengerCreateInfoEXT debugCallback{}; - debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; + debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debugCallback.pNext = nullptr; debugCallback.flags = 0; debugCallback.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; @@ -673,24 +674,19 @@ VulkanRenderer* VulkanRenderer::GetInstance() void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow) { - auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; - - const auto surface = CreateFramebufferSurface(m_instance, windowHandleInfo); if (mainWindow) { - m_mainSwapchainInfo = std::make_unique(surface, mainWindow); - m_mainSwapchainInfo->m_desiredExtent = size; - m_mainSwapchainInfo->Create(m_physicalDevice, m_logicalDevice); + m_mainSwapchainInfo = std::make_unique(mainWindow, size); + m_mainSwapchainInfo->Create(); // aquire first command buffer InitFirstCommandBuffer(); } else { - m_padSwapchainInfo = std::make_unique(surface, mainWindow); - m_padSwapchainInfo->m_desiredExtent = size; + m_padSwapchainInfo = std::make_unique(mainWindow, size); // todo: figure out a way to exclusively create swapchain on main LatteThread - m_padSwapchainInfo->Create(m_physicalDevice, m_logicalDevice); + m_padSwapchainInfo->Create(); } } @@ -1074,6 +1070,36 @@ RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, u return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } +VulkanRenderer::QueueFamilyIndices VulkanRenderer::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) +{ + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + QueueFamilyIndices indices; + for (int i = 0; i < (int)queueFamilies.size(); ++i) + { + const auto& queueFamily = queueFamilies[i]; + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + indices.graphicsFamily = i; + + VkBool32 presentSupport = false; + const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); + + if (queueFamily.queueCount > 0 && presentSupport) + indices.presentFamily = i; + + if (indices.IsComplete()) + break; + } + + return indices; +} + bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info) { std::vector availableDeviceExtensions; @@ -1215,7 +1241,7 @@ std::vector VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device) { - if (!SwapchainInfoVk::FindQueueFamilies(surface, device).IsComplete()) + if (!FindQueueFamilies(surface, device).IsComplete()) return false; // check API version (using Vulkan 1.0 way of querying properties) @@ -2605,7 +2631,7 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate) chainInfo.m_desiredExtent = size; if(!skipCreate) { - chainInfo.Create(m_physicalDevice, m_logicalDevice); + chainInfo.Create(); } if (mainWindow) @@ -2675,7 +2701,7 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &chainInfo.swapchain; + presentInfo.pSwapchains = &chainInfo.m_swapchain; presentInfo.pImageIndices = &chainInfo.swapchainImageIndex; // wait on command buffer semaphore presentInfo.waitSemaphoreCount = 1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 47097df..2491d05 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -127,7 +127,6 @@ class VulkanRenderer : public Renderer friend class PipelineCompiler; using VSync = SwapchainInfoVk::VSync; - using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices; static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB @@ -421,6 +420,18 @@ private: VkDescriptorPool m_descriptorPool; + public: + struct QueueFamilyIndices + { + int32_t graphicsFamily = -1; + int32_t presentFamily = -1; + + bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } + }; + static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); + + private: + struct FeatureControl { struct From 241915e1a6bfd92e4ffd0d6961a178335300e83f Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 24 Mar 2024 10:11:18 +0000 Subject: [PATCH 040/130] Gamelist: Display title long names + improvements for shortcuts (#1126) - Windows icons are stored as .ico files to %LOCALAPPDATA%/Cemu/icons/ - Long title names chosen as some games (NSMBU + NSLU) add trailing dots for their shortnames - Long title names have their newlines replaced with spaces at parsing - Linux shortcut paths are saved with UTF-8 encoding - Game titles are copied and saved with UTF-8 encoding --- src/Cafe/TitleList/ParsedMetaXml.h | 7 +- src/Cafe/TitleList/TitleInfo.cpp | 4 +- src/gui/CemuApp.cpp | 7 +- src/gui/components/wxGameList.cpp | 272 ++++++++++++++++------------- 4 files changed, 168 insertions(+), 122 deletions(-) diff --git a/src/Cafe/TitleList/ParsedMetaXml.h b/src/Cafe/TitleList/ParsedMetaXml.h index 7537d60..f1aee37 100644 --- a/src/Cafe/TitleList/ParsedMetaXml.h +++ b/src/Cafe/TitleList/ParsedMetaXml.h @@ -90,8 +90,11 @@ struct ParsedMetaXml else if (boost::starts_with(name, "longname_")) { const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1)); - if (index != -1) - parsedMetaXml->m_long_name[index] = child.text().as_string(); + if (index != -1){ + std::string longname = child.text().as_string(); + std::replace_if(longname.begin(), longname.end(), [](char c) { return c == '\r' || c == '\n';}, ' '); + parsedMetaXml->m_long_name[index] = longname; + } } else if (boost::starts_with(name, L"shortname_")) { diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index ff45757..d23e1d0 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const if (m_parsedMetaXml) { std::string titleNameCfgLanguage; - titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language); + titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language); if (titleNameCfgLanguage.empty()) //Get English Title - titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN); + titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN); if (titleNameCfgLanguage.empty()) //Unknown Title titleNameCfgLanguage = "Unknown Title"; return titleNameCfgLanguage; diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index fde4bcc..7f11d4c 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -59,7 +59,12 @@ bool CemuApp::OnInit() fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); - +#if BOOST_OS_LINUX + // GetExecutablePath returns the AppImage's temporary mount location + wxString appImagePath; + if (wxGetEnv(("APPIMAGE"), &appImagePath)) + exePath = wxHelper::MakeFSPath(appImagePath); +#endif // Try a portable path first, if it exists. user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; #if BOOST_OS_MACOS diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 8e8f3c4..73bcd98 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -19,7 +19,6 @@ #include #include - #include #include @@ -526,7 +525,6 @@ void wxGameList::OnKeyDown(wxListEvent& event) } } - enum ContextMenuEntries { kContextMenuRefreshGames = wxID_HIGHEST + 1, @@ -732,7 +730,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) { if (wxTheClipboard->Open()) { - wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName())); + wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName()))); wxTheClipboard->Close(); } break; @@ -1276,129 +1274,169 @@ 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(); - const char *flatpak_id = getenv("FLATPAK_ID"); - - // 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(); +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + const char* flatpakId = getenv("FLATPAK_ID"); -#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(); + const wxString desktopEntryName = wxString::Format("%s.desktop", titleName); + wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName, + "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + const auto result = entryDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto output_path = entryDialog.GetPath(); - // 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"); + std::optional iconPath; + // Obtain and convert icon + [&]() + { + int iconIndex, smallIconIndex; - 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()); + if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons"); - auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage(); + if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir)) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } - 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); - } - } - } - } + iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); - std::string desktop_exec_entry; - if (flatpak_id) - desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id); - else - desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id); + auto image = m_image_list->GetIcon(iconIndex).ConvertToImage(); + wxPNGHandler pngHandler; + if (!pngHandler.SaveFile(&image, pngFileStream, false)) + { + iconPath = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); - // 'Icon' accepts spaces in file name, does not accept quoted file paths - // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths - auto desktop_entry_string = - fmt::format("[Desktop Entry]\n" - "Name={0}\n" - "Comment=Play {0} on Cemu\n" - "Exec={1}\n" - "Icon={2}\n" - "Terminal=false\n" - "Type=Application\n" - "Categories=Game;\n", - title_name, - desktop_exec_entry, - _pathToUtf8(icon_path.value_or(""))); + std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId) + : fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId); - if (flatpak_id) - desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id); + // 'Icon' accepts spaces in file name, does not accept quoted file paths + // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths + auto desktopEntryString = fmt::format( + "[Desktop Entry]\n" + "Name={0}\n" + "Comment=Play {0} on Cemu\n" + "Exec={1}\n" + "Icon={2}\n" + "Terminal=false\n" + "Type=Application\n" + "Categories=Game;\n", + titleName.utf8_string(), + desktopExecEntry, + _pathToUtf8(iconPath.value_or(""))); - std::ofstream output_stream(output_path); - if (!output_stream.good()) - { - auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); - wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - return; - } - output_stream << desktop_entry_string; + if (flatpakId) + desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId); -#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 + std::ofstream outputStream(output_path.utf8_string()); + if (!outputStream.good()) + { + auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return; + } + outputStream << desktopEntryString; } -#endif +#elif BOOST_OS_WINDOWS +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + + // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path + PWSTR userShortcutFolder; + SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder); + const wxString shortcutName = wxString::Format("%s.lnk", titleName); + wxFileDialog shortcutDialog(this, _("Choose shortcut location"), _pathToUtf8(userShortcutFolder), shortcutName, + "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + + const auto result = shortcutDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto outputPath = shortcutDialog.GetPath(); + + std::optional icon_path = std::nullopt; + [&]() + { + int iconIdx; + int smallIconIdx; + if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const auto icon = m_image_list->GetIcon(iconIdx); + PWSTR localAppData; + const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData); + wxBitmap bitmap{}; + auto folder = fs::path(localAppData) / "Cemu" / "icons"; + if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder))) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } + if (!bitmap.CopyFromIcon(icon)) + { + cemuLog_log(LogType::Force, "Failed to copy icon"); + return; + } + + icon_path = folder / fmt::format("{:016x}.ico", titleId); + auto stream = wxFileOutputStream(_pathToUtf8(*icon_path)); + auto image = bitmap.ConvertToImage(); + wxICOHandler icohandler{}; + if (!icohandler.SaveFile(&image, stream, false)) + { + icon_path = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); + + IShellLinkW* shellLink; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&shellLink)); + if (SUCCEEDED(hres)) + { + const auto description = wxString::Format("Play %s on Cemu", titleName); + const auto args = wxString::Format("-t %016llx", titleId); + + shellLink->SetPath(exePath.wstring().c_str()); + shellLink->SetDescription(description.wc_str()); + shellLink->SetArguments(args.wc_str()); + shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str()); + + if (icon_path) + shellLink->SetIconLocation(icon_path->wstring().c_str(), 0); + else + shellLink->SetIconLocation(exePath.wstring().c_str(), 0); + + IPersistFile* shellLinkFile; + // save the shortcut + hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast(&shellLinkFile)); + if (SUCCEEDED(hres)) + { + hres = shellLinkFile->Save(outputPath.wc_str(), TRUE); + shellLinkFile->Release(); + } + shellLink->Release(); + } + if (!SUCCEEDED(hres)) { + auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + } +} +#endif \ No newline at end of file From 4d148b369660db629c225bf1ff0e354d025e7902 Mon Sep 17 00:00:00 2001 From: Francesco Saltori Date: Mon, 25 Mar 2024 21:34:40 +0100 Subject: [PATCH 041/130] Add supported locales to macOS plist (#1133) --- src/resource/MacOSXBundleInfo.plist.in | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/resource/MacOSXBundleInfo.plist.in b/src/resource/MacOSXBundleInfo.plist.in index 9806473..ccd1c92 100644 --- a/src/resource/MacOSXBundleInfo.plist.in +++ b/src/resource/MacOSXBundleInfo.plist.in @@ -30,6 +30,28 @@ ${MACOSX_BUNDLE_CATEGORY} LSMinimumSystemVersion ${MACOSX_MINIMUM_SYSTEM_VERSION} + CFBundleLocalizations + + ca + de + en + es + fr + he + hu + it + ja + ko + nb + nl + pl + pt + ru + sv + tr + uk + zh + CFBundleDocumentTypes From 4b7d2f88ae044a590dfa7faeadee1e03047492f0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:02:37 +0100 Subject: [PATCH 042/130] Latte: Enable colorbuffer optimization if gfx packs are aware The optimization for colorbuffer resolution introduced in PR #706 is now enabled. This optimization changes the resolution of certain framebuffer textures, which may conflict with the texture resolution rules set by some graphic packs. As a result, if a graphic pack that specifies texture resolution rules is in use, the optimization will automatically be turned off to prevent any issues. To circumvent this, graphic packs can now include the setting "colorbufferOptimizationAware = true" in their rules.txt. This setting indicates that the pack has been updated to handle the resolution changes introduced by the optimization. Cemu will allow the optimization to remain enabled if resolution packs have this flag set. --- src/Cafe/GraphicPack/GraphicPack2.cpp | 4 ++++ src/Cafe/GraphicPack/GraphicPack2.h | 3 +++ src/Cafe/HW/Latte/Core/Latte.h | 2 ++ src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 21 ++++++++++---------- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 7 ++++--- src/Cafe/HW/Latte/Core/LatteThread.cpp | 17 ++++++++++++++++ 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index b581316..27d423b 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -280,6 +280,10 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) m_enabled = m_default_enabled; } + auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware"); + if (option_allowRendertargetSizeOptimization) + m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1"); + auto option_vendorFilter = rules.FindOption("vendorFilter"); if (option_vendorFilter) { diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index 6b07cce..9b6a86d 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -113,6 +113,7 @@ public: const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy const std::string& GetDescription() const { return m_description; } bool IsDefaultEnabled() const { return m_default_enabled; } + bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; } void SetEnabled(bool state) { m_enabled = state; } @@ -217,6 +218,8 @@ private: bool m_default_enabled = false; + bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74 + // filter std::optional m_renderer_api; std::optional m_gfx_vendor; diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index d9419a6..e8cb2be 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -25,6 +25,8 @@ struct LatteGPUState_t // context control uint32 contextControl0; uint32 contextControl1; + // optional features + bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size // draw context struct { diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 3006971..f165e25 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -267,14 +267,15 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN // colorbuffer width/height has to be padded to 8/32 alignment but the actual resolution might be smaller // use the scissor box as a clue to figure out the original resolution if possible -#if 0 - uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); - uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); - if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) - colorBufferWidth = scissorBoxWidth; - if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) - colorBufferHeight = scissorBoxHeight; -#endif + if(LatteGPUState.allowFramebufferSizeOptimization) + { + uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); + uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); + if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) + colorBufferWidth = scissorBoxWidth; + if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) + colorBufferHeight = scissorBoxHeight; + } // log resolution changes if the above heuristic takes effect // this is useful to find resolutions which need to be updated in gfx pack texture rules @@ -303,7 +304,7 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN if (colorBufferView == nullptr) { // create color buffer view - colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); + colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, true); LatteGPUState.repeatTextureInitialization = true; checkForTextureChanges = false; } @@ -582,7 +583,7 @@ bool LatteMRT::UpdateCurrentFBO() if (!depthBufferView) { // create new depth buffer view and if it doesn't exist then also create the texture - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true, true); LatteGPUState.repeatTextureInitialization = true; } else diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index d6f576d..3754fb1 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1,5 +1,4 @@ #include "Cafe/HW/Latte/Core/Latte.h" -#include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" @@ -9,6 +8,8 @@ #include "Cafe/GraphicPack/GraphicPack2.h" +#include + struct TexMemOccupancyEntry { uint32 addrStart; @@ -963,7 +964,7 @@ void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* textur } // create new texture representation -// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible +// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible (todo - we should differentiate between Latte compatible views and renderer compatible) // the returned view will map to the provided mip and slice range within the created texture, this is to match the behavior of lookupSliceEx LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture) { @@ -980,7 +981,7 @@ LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, si // todo, depth and numSlice are redundant sint32 sliceCount = firstSlice + numSlice; - std::vector list_overlappingTextures; + boost::container::small_vector list_overlappingTextures; for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) { sint32 mipIndex = 0; diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index bd312d9..a23bd5b 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -175,6 +175,23 @@ int Latte_ThreadEntry() // before doing anything with game specific shaders, we need to wait for graphic packs to finish loading GraphicPack2::WaitUntilReady(); + // if legacy packs are enabled we cannot use the colorbuffer resolution optimization + LatteGPUState.allowFramebufferSizeOptimization = true; + for(auto& pack : GraphicPack2::GetActiveGraphicPacks()) + { + if(pack->AllowRendertargetSizeOptimization()) + continue; + for(auto& rule : pack->GetTextureRules()) + { + if(rule.filter_settings.width >= 0 || rule.filter_settings.height >= 0 || rule.filter_settings.depth >= 0 || + rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0) + { + LatteGPUState.allowFramebufferSizeOptimization = false; + cemuLog_log(LogType::Force, "Graphic pack {} prevents rendertarget size optimization.", pack->GetName()); + break; + } + } + } // load disk shader cache LatteShaderCache_Load(); // init registers From fa4ad9b8c196c0821888c0d882154d10feee673b Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Mon, 25 Mar 2024 23:30:39 +0200 Subject: [PATCH 043/130] Gamelist: Add option to hide the icon column (#604) --- src/config/CemuConfig.cpp | 3 +++ src/config/CemuConfig.h | 2 ++ src/gui/components/wxGameList.cpp | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 1801759..e4be97a 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -84,6 +84,8 @@ void CemuConfig::Load(XMLConfigParser& parser) game_list_style = gamelist.get("style", 0); game_list_column_order = gamelist.get("order", ""); + show_icon_column = parser.get("show_icon_column", true); + // return default width if value in config file out of range auto loadColumnSize = [&gamelist] (const char *name, uint32 defaultWidth) { @@ -385,6 +387,7 @@ void CemuConfig::Save(XMLConfigParser& parser) psize.set("x", pad_size.x); psize.set("y", pad_size.y); config.set("pad_maximized", pad_maximized); + config.set("show_icon_column" , show_icon_column); auto gamelist = config.set("GameList"); gamelist.set("style", game_list_style); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index eb552fc..bcaf846 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -418,6 +418,8 @@ struct CemuConfig ConfigValue did_show_graphic_pack_download{false}; ConfigValue did_show_macos_disclaimer{false}; + ConfigValue show_icon_column{ false }; + int game_list_style = 0; std::string game_list_column_order; struct diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 73bcd98..d7c9a4f 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -88,7 +88,10 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id) const auto& config = GetConfig(); InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0); - InsertColumn(ColumnIcon, "", wxLIST_FORMAT_LEFT, kListIconWidth); + if(config.show_icon_column) + InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, kListIconWidth); + else + InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, 0); InsertColumn(ColumnName, _("Game"), wxLIST_FORMAT_LEFT, config.column_width.name); InsertColumn(ColumnVersion, _("Version"), wxLIST_FORMAT_RIGHT, config.column_width.version); InsertColumn(ColumnDLC, _("DLC"), wxLIST_FORMAT_RIGHT, config.column_width.dlc); @@ -794,6 +797,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) ResetWidth = wxID_HIGHEST + 1, ResetOrder, + ShowIcon, ShowName, ShowVersion, ShowDlc, @@ -810,6 +814,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) menu.Append(ResetOrder, _("Reset &order")) ; menu.AppendSeparator(); + menu.AppendCheckItem(ShowIcon, _("Show &icon"))->Check(GetColumnWidth(ColumnIcon) > 0); menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0); menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0); menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0); @@ -828,6 +833,9 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) switch (event.GetId()) { + case ShowIcon: + config.show_icon_column = menu->IsChecked(ShowIcon); + break; case ShowName: config.column_width.name = menu->IsChecked(ShowName) ? DefaultColumnSize::name : 0; break; @@ -907,7 +915,10 @@ void wxGameList::ApplyGameListColumnWidths() { const auto& config = GetConfig(); wxWindowUpdateLocker lock(this); - SetColumnWidth(ColumnIcon, kListIconWidth); + if(config.show_icon_column) + SetColumnWidth(ColumnIcon, kListIconWidth); + else + SetColumnWidth(ColumnIcon, 0); SetColumnWidth(ColumnName, config.column_width.name); SetColumnWidth(ColumnVersion, config.column_width.version); SetColumnWidth(ColumnDLC, config.column_width.dlc); From 111e383d1b0c2a27001433781e2cba123f991048 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:07:08 +0100 Subject: [PATCH 044/130] coreinit: Fix race condition that causes crash (#1138) --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 8ce5de0..809d7be 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1159,9 +1159,11 @@ namespace coreinit #include std::vector g_schedulerThreadIds; + std::mutex g_schedulerThreadIdsLock; std::vector& OSGetSchedulerThreadIds() { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); return g_schedulerThreadIds; } #endif @@ -1183,7 +1185,10 @@ namespace coreinit } pid_t tid = gettid(); - g_schedulerThreadIds.emplace_back(tid); + { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); + g_schedulerThreadIds.emplace_back(tid); + } #endif t_schedulerFiber = Fiber::PrepareCurrentThread(); @@ -1238,7 +1243,10 @@ namespace coreinit sSchedulerThreads.clear(); g_schedulerThreadHandles.clear(); #if BOOST_OS_LINUX - g_schedulerThreadIds.clear(); + { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); + g_schedulerThreadIds.clear(); + } #endif // clean up all fibers for (auto& it : g_idleLoopFiber) From 4f3d4624f5c73d5f2634b09142f4fb7ea7fba623 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:09:24 +0100 Subject: [PATCH 045/130] GraphicPacksWindow: Disable update button when a game is running (#1137) --- src/gui/DownloadGraphicPacksWindow.cpp | 16 ++++++---------- src/gui/GraphicPacksWindow2.cpp | 10 ++++++++++ src/gui/GraphicPacksWindow2.h | 1 + src/gui/MainWindow.cpp | 10 ++++++++++ src/gui/MainWindow.h | 4 +++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp index 8189b50..03f102d 100644 --- a/src/gui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -110,14 +110,6 @@ void deleteDownloadedGraphicPacks() void DownloadGraphicPacksWindow::UpdateThread() { - if (CafeSystem::IsTitleRunning()) - { - wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); - // cancel update - m_threadState = ThreadFinished; - return; - } - // get github url std::string githubAPIUrl; curlDownloadFileState_t tempDownloadState; @@ -326,8 +318,6 @@ DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent) m_downloadState = std::make_unique(); - - m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); } DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow() @@ -344,6 +334,12 @@ const std::string& DownloadGraphicPacksWindow::GetException() const int DownloadGraphicPacksWindow::ShowModal() { + if(CafeSystem::IsTitleRunning()) + { + wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); + return wxID_CANCEL; + } + m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); wxDialog::ShowModal(); return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK; } diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index 78b344d..29f4b86 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -319,6 +319,7 @@ GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_fil SetSizer(main_sizer); + UpdateTitleRunning(CafeSystem::IsTitleRunning()); FillGraphicPackList(); } @@ -676,6 +677,15 @@ void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event) event.Skip(); } +void GraphicPacksWindow2::UpdateTitleRunning(bool running) +{ + m_update_graphicPacks->Enable(!running); + if(running) + m_update_graphicPacks->SetToolTip(_("Graphic packs cannot be updated while a game is running.")); + else + m_update_graphicPacks->SetToolTip(nullptr); +} + void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const { if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency()) diff --git a/src/gui/GraphicPacksWindow2.h b/src/gui/GraphicPacksWindow2.h index a068f2b..a79c62c 100644 --- a/src/gui/GraphicPacksWindow2.h +++ b/src/gui/GraphicPacksWindow2.h @@ -21,6 +21,7 @@ public: ~GraphicPacksWindow2(); static void RefreshGraphicPacks(); + void UpdateTitleRunning(bool running); private: std::string m_filter; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 311ddfb..023918b 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -625,6 +625,7 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE CreateCanvas(); CafeSystem::LaunchForegroundTitle(); RecreateMenu(); + UpdateChildWindowTitleRunningState(); return true; } @@ -683,6 +684,7 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) RecreateMenu(); CreateGameListAndStatusBar(); DoLayout(); + UpdateChildWindowTitleRunningState(); } } @@ -2320,6 +2322,14 @@ void MainWindow::RecreateMenu() SetMenuVisible(false); } +void MainWindow::UpdateChildWindowTitleRunningState() +{ + const bool running = CafeSystem::IsTitleRunning(); + + if(m_graphic_pack_window) + m_graphic_pack_window->UpdateTitleRunning(running); +} + void MainWindow::RestoreSettingsAfterGameExited() { RecreateMenu(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 88d2a1d..7191df1 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -21,6 +21,7 @@ class DebuggerWindow2; struct GameEntry; class DiscordPresence; class TitleManager; +class GraphicPacksWindow2; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); @@ -146,6 +147,7 @@ public: private: void RecreateMenu(); + void UpdateChildWindowTitleRunningState(); static wxString GetInitialWindowTitle(); void ShowGettingStartedDialog(); @@ -163,7 +165,7 @@ private: MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; PadViewFrame* m_padView = nullptr; - wxWindow* m_graphic_pack_window = nullptr; + GraphicPacksWindow2* m_graphic_pack_window = nullptr; wxTimer* m_timer; wxPoint m_mouse_position{}; From 5230fcab374b6180043b0c1397c9d51e0c99b84a Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:14:01 +0100 Subject: [PATCH 046/130] Debugger: Fix infinite loop in symbol storage (#1134) --- src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h index aba6a9b..0a46951 100644 --- a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h +++ b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h @@ -45,12 +45,17 @@ public: static void ClearRange(MPTR address, uint32 length) { + if (length == 0) + return; s_lock.lock(); - while (length > 0) + for (;;) { auto itr = s_typeStorage.find(address); if (itr != s_typeStorage.end()) s_typeStorage.erase(itr); + + if (length <= 4) + break; address += 4; length -= 4; } @@ -60,4 +65,4 @@ public: private: static FSpinlock s_lock; static std::unordered_map s_typeStorage; -}; \ No newline at end of file +}; From b0b2c257626852f749109aba053c2efcb037b0b8 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:44:51 +0100 Subject: [PATCH 047/130] coreinit: Improve accuracy of OSSwitchCoroutine Fixes Injustice: Gods Among Us crashing during boot. --- .../OS/libs/coreinit/coreinit_Coroutine.cpp | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp index b1f4abb..49c4683 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp @@ -3,9 +3,13 @@ #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/MMU/MMU.h" +#include "Cafe/OS/RPL/rpl.h" namespace coreinit { + static_assert(sizeof(OSCoroutine) == 0x180); + + static uint32 s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine = 0; void coreinitExport_OSInitCoroutine(PPCInterpreter_t* hCPU) { @@ -57,14 +61,30 @@ namespace coreinit void coreinitExport_OSSwitchCoroutine(PPCInterpreter_t* hCPU) { - OSCoroutine* coroutineCurrent = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - OSCoroutine* coroutineNext = (OSCoroutine*)memory_getPointerFromVirtualOffsetAllowNull(hCPU->gpr[4]); + // OSSwitchCoroutine is a wrapper for OSSaveCoroutine + OSLoadCoroutine but it has side effects that we need to care about: + // r31 is saved and restored via the stack in OSSwitchCoroutine + // r4 is stored in the r31 field of coroutineCurrent. Injustice: Gods Among Us reads the r31 field and expects it to match coroutineCurrent (0x027183D4 @ EU v16) + OSCoroutine* coroutineCurrent = MEMPTR(hCPU->gpr[3]); + OSCoroutine* coroutineNext = MEMPTR(hCPU->gpr[4]); + hCPU->gpr[1] -= 0x10; + memory_writeU32(hCPU->gpr[1]+0xC, hCPU->gpr[31]); + memory_writeU32(hCPU->gpr[1]+0x14, hCPU->spr.LR); + hCPU->spr.LR = s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine; + hCPU->gpr[31] = hCPU->gpr[4]; coreinitCoroutine_OSSaveCoroutine(coroutineCurrent, hCPU); - if (coroutineNext != NULL) - { - coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU); - } - osLib_returnFromFunction(hCPU, 0); + hCPU->gpr[3] = hCPU->gpr[31]; + hCPU->gpr[4] = 1; + coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU); + hCPU->instructionPointer = hCPU->spr.LR; + } + + void coreinitExport_OSSwitchCoroutineAfterOSLoadCoroutine(PPCInterpreter_t* hCPU) + { + // resuming after OSSaveCoroutine + hCPU->gpr[31] = memory_readU32(hCPU->gpr[1]+0xC); + hCPU->spr.LR = memory_readU32(hCPU->gpr[1]+0x14); + hCPU->gpr[1] += 0x10; + hCPU->instructionPointer = hCPU->spr.LR; } void coreinitExport_OSSwitchFiberEx(PPCInterpreter_t* hCPU) @@ -96,5 +116,7 @@ namespace coreinit osLib_addFunction("coreinit", "OSInitCoroutine", coreinitExport_OSInitCoroutine); osLib_addFunction("coreinit", "OSSwitchCoroutine", coreinitExport_OSSwitchCoroutine); osLib_addFunction("coreinit", "OSSwitchFiberEx", coreinitExport_OSSwitchFiberEx); + + s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine = RPLLoader_MakePPCCallable(coreinitExport_OSSwitchCoroutineAfterOSLoadCoroutine); } } \ No newline at end of file From 60adc382056d4272d38673e8045e11f2bca2338d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:59:13 +0100 Subject: [PATCH 048/130] Latte: Add support for more fence conditions MEM_OP_GREATER is required by Injustice: Gods Among Us --- .../HW/Latte/Core/LatteCommandProcessor.cpp | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index c928f89..167911b 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -475,18 +475,45 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords) { uint32 fenceMemValue = _swapEndianU32(*fencePtr); fenceMemValue &= fenceMask; - if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL) + if (compareOp == GPU7_WAIT_MEM_OP_LESS) { - // greater or equal - if (fenceMemValue >= fenceValue) + if (fenceMemValue < fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_LEQUAL) + { + if (fenceMemValue <= fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_EQUAL) { - // equal if (fenceMemValue == fenceValue) break; } + else if (compareOp == GPU7_WAIT_MEM_OP_NOTEQUAL) + { + if (fenceMemValue != fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL) + { + if (fenceMemValue >= fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_GREATER) + { + if (fenceMemValue > fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_ALWAYS) + { + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_NEVER) + { + cemuLog_logOnce(LogType::Force, "Latte: WAIT_MEM_OP_NEVER encountered"); + break; + } else assert_dbg(); if (!stalls) From fa8bab2f3978d1fedeffa4c578a876e4c624206b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:01:44 +0100 Subject: [PATCH 049/130] Latte: Add support for LOOP_START_NO_AL shader instruction This instruction is used by Injustice: Gods Among Us and Project Zero Also improved robustness of rendering to be less prone to crashing when a game tries to draw with broken shaders --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteShaderAssembly.h | 2 +- .../HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp | 6 ++++-- .../LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp | 9 ++++++--- .../LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp | 3 ++- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp | 4 ++-- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index f165e25..5b9fc34 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -340,7 +340,7 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade return 0; cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported // combine color buffer mask with pixel output mask from pixel shader - colorBufferMask &= pixelShader->pixelColorOutputMask; + colorBufferMask &= (pixelShader ? pixelShader->pixelColorOutputMask : 0); // combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked) uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK(); for (uint32 i = 0; i < 8; i++) diff --git a/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h b/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h index df63668..d2314a5 100644 --- a/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h +++ b/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h @@ -12,7 +12,7 @@ #define GPU7_CF_INST_VTX (0x02) // used only in GS copy program? #define GPU7_CF_INST_LOOP_END (0x05) #define GPU7_CF_INST_LOOP_START_DX10 (0x06) -#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero) +#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero, Injustice: Gods Among Us) #define GPU7_CF_INST_LOOP_BREAK (0x09) #define GPU7_CF_INST_JUMP (0x0A) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp index cf88b90..c3f7c19 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp @@ -101,7 +101,8 @@ bool LatteDecompiler_ParseCFInstruction(LatteDecompilerShaderContext* shaderCont // ignored (we use ALU/IF/ELSE/PUSH/POP clauses to determine code flow) return true; } - else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END) + else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END || + cf_inst23_7 == GPU7_CF_INST_LOOP_START_NO_AL) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address @@ -966,7 +967,8 @@ void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* decompilerContex { // no sub-instructions } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { // no sub-instructions } diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index cf22f05..19604e0 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -441,7 +441,8 @@ void LatteDecompiler_analyzeSubroutine(LatteDecompilerShaderContext* shaderConte { shaderContext->analyzer.modifiesPixelActiveState = true; } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { shaderContext->analyzer.modifiesPixelActiveState = true; } @@ -685,7 +686,8 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD { shaderContext->analyzer.modifiesPixelActiveState = true; } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { shaderContext->analyzer.modifiesPixelActiveState = true; shaderContext->analyzer.hasLoops = true; @@ -929,7 +931,8 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD if (cfCurrentStackDepth < 0) debugBreakpoint(); } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index e19535b..7a6605f 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -3662,7 +3662,8 @@ void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext, { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - cfInstruction->popCount), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount)); } - else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 ) + else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 || + cfInstruction->type == GPU7_CF_INST_LOOP_START_NO_AL) { // start of loop // if pixel is disabled, then skip loop diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index d510140..6500f7d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1285,9 +1285,9 @@ void VulkanRenderer::draw_beginSequence() // update shader state LatteSHRC_UpdateActiveShaders(); - if (m_state.drawSequenceSkip) + if (LatteGPUState.activeShaderHasError) { - debug_printf("Skipping drawcalls due to shader error\n"); + cemuLog_logDebugOnce(LogType::Force, "Skipping drawcalls due to shader error"); m_state.drawSequenceSkip = true; cemu_assert_debug(false); return; From 3e467e220e4e407752381f264f405c50eaec1036 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 01:45:50 +0200 Subject: [PATCH 050/130] Logging: Prevent crash for nullptr strings --- src/Cafe/OS/common/OSUtil.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/common/OSUtil.h b/src/Cafe/OS/common/OSUtil.h index 6801f6a..84e38a6 100644 --- a/src/Cafe/OS/common/OSUtil.h +++ b/src/Cafe/OS/common/OSUtil.h @@ -140,6 +140,17 @@ static std::tuple cafeExportBuildArgTuple(PPCInterpreter_t* hCPU, R(fn) return std::tuple{ cafeExportGetParamWrapper(hCPU, gprIndex, fprIndex)... }; } +template +T cafeExportGetFormatParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex) +{ + T v; + cafeExportParamWrapper::getParamWrapper(hCPU, gprIndex, fprIndex, v); + // if T is char* or const char*, return "null" instead of nullptr since newer fmtlib would throw otherwise + if constexpr (std::is_same_v || std::is_same_v) + return v ? v : (T)"null"; + return v; +} + template using _CAFE_FORMAT_ARG = std::conditional_t, std::conditional_t || std::is_same_v, T, MEMPTR>, T>; @@ -150,7 +161,7 @@ static auto cafeExportBuildFormatTuple(PPCInterpreter_t* hCPU, R(fn)(Args...)) int gprIndex = 0; int fprIndex = 0; return std::tuple<_CAFE_FORMAT_ARG...>{ - cafeExportGetParamWrapper<_CAFE_FORMAT_ARG>(hCPU, gprIndex, fprIndex)... + cafeExportGetFormatParamWrapper<_CAFE_FORMAT_ARG>(hCPU, gprIndex, fprIndex)... }; } From 51072b510c74e078350f70f31386802f2e78159f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 01:45:05 +0200 Subject: [PATCH 051/130] nn_boss: Large rework with various improvements Lots of internal changes. On the surface this only fixes a crash in Mario & Sonic Rio 2016 (at least what I saw from my testing) but it may affect more games. Summary of changes: - Rewrite code to use newer cafeExportRegisterFunc - Simplify code by merging namespaces and structs of the same types - Correctly set ppc vtables for the virtual boss classes - Fix some wrong function definitions and implement a little bit more of the boss API (mainly constructors and destructors) --- src/Cafe/OS/libs/nn_boss/nn_boss.cpp | 2710 +++++++++++++------------- src/Cemu/Logging/CemuLogging.cpp | 1 + src/Cemu/Logging/CemuLogging.h | 1 + src/gui/MainWindow.cpp | 1 + 4 files changed, 1325 insertions(+), 1388 deletions(-) diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index c2d65a5..f53a6d7 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -27,13 +27,244 @@ bossBufferVector->buffer = (uint8*)bossRequest; sint32 g_initCounter = 0; bool g_isInitialized = false; - void freeMem(void* mem) + struct VTableEntry { - if(mem) - coreinit::default_MEMFreeToDefaultHeap((uint8*)mem - 8); + uint16be offsetA{0}; + uint16be offsetB{0}; + MEMPTR ptr; + }; + static_assert(sizeof(VTableEntry) == 8); + + #define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) + + constexpr uint32 BOSS_MEM_MAGIC = 0xCAFE4321; + + template + MEMPTR boss_new() + { + uint32 objSize = sizeof(T); + uint32be* basePtr = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(objSize + 8, 0x8); + basePtr[0] = BOSS_MEM_MAGIC; + basePtr[1] = objSize; + return (T*)(basePtr+2); } - struct TaskSetting_t + void boss_delete(MEMPTR mem) + { + if(!mem) + return; + uint32be* basePtr = (uint32be*)mem.GetPtr() - 2; + if(basePtr[0] != BOSS_MEM_MAGIC) + { + cemuLog_log(LogType::Force, "nn_boss: Detected memory corruption"); + cemu_assert_suspicious(); + } + coreinit::_weak_MEMFreeToDefaultHeap(basePtr); + } + + Result Initialize() // Initialize__Q2_2nn4bossFv + { + coreinit::OSLockMutex(&g_mutex); + Result result = 0; + if(g_initCounter == 0) + { + g_isInitialized = true; + // IPC init here etc. + result = 0x200080; // init result + } + g_initCounter++; + coreinit::OSUnlockMutex(&g_mutex); + return NN_RESULT_IS_SUCCESS(result) ? 0 : result; + } + + uint32 IsInitialized() // IsInitialized__Q2_2nn4bossFv + { + return g_isInitialized; + } + + void Finalize() // Finalize__Q2_2nn4bossFv + { + coreinit::OSLockMutex(&g_mutex); + if(g_initCounter == 0) + cemuLog_log(LogType::Force, "nn_boss: Finalize() called without corresponding Initialize()"); + if(g_initCounter == 1) + { + g_isInitialized = false; + // IPC deinit here etc. + } + g_initCounter--; + coreinit::OSUnlockMutex(&g_mutex); + } + + uint32 GetBossState(PPCInterpreter_t* hCPU) + { + cemuLog_logDebug(LogType::Force, "nn_boss.GetBossState() - stub"); + return 7; + } + + struct TitleId + { + uint64be u64{}; + + static TitleId* ctor(TitleId* _thisptr, uint64 titleId) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId; + return _thisptr; + } + + static TitleId* ctor(TitleId* _thisptr) + { + return ctor(_thisptr, 0); + } + + static bool IsValid(TitleId* _thisptr) + { + return _thisptr->u64 != 0; + } + + static TitleId* ctor1(TitleId* _thisptr, uint32 filler, uint64 titleId) + { + return ctor(_thisptr); + } + + static TitleId* ctor2(TitleId* _thisptr, uint32 filler, uint64 titleId) + { + cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_ctor2(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + { + // _thisptr = new Task_t + assert_dbg(); + } + + _thisptr->u64 = titleId; + return _thisptr; + } + + static TitleId* ctor3(TitleId* _thisptr, TitleId* titleId) + { + cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_cctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId->u64; + return _thisptr; + } + + static bool operator_ne(TitleId* _thisptr, TitleId* titleId) + { + cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_operator_ne(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + return _thisptr->u64 != titleId->u64; + } + }; + static_assert(sizeof(TitleId) == 8); + + struct TaskId + { + char id[0x8]{}; + + static TaskId* ctor(TaskId* _thisptr) + { + if(!_thisptr) + _thisptr = boss_new(); + _thisptr->id[0] = '\0'; + return _thisptr; + } + }; + static_assert(sizeof(TaskId) == 8); + + struct Title + { + uint32be accountId{}; // 0x00 + TitleId titleId{}; // 0x8 + MEMPTR vTablePtr{}; // 0x10 + + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator s_titleVTable; + + static Title* ctor(Title* _this) + { + if (!_this) + _this = boss_new(); + *_this = {}; + _this->vTablePtr = s_titleVTable; + return _this; + } + + static void dtor(Title* _this, uint32 options) + { + if (_this && (options & 1)) + boss_delete(_this); + } + + static void InitVTable() + { + s_titleVTable->rtti.ptr = nullptr; // todo + s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); + } + }; + static_assert(sizeof(Title) == 0x18); + + struct DirectoryName + { + char name[0x8]{}; + + static DirectoryName* ctor(DirectoryName* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<DirectoryName>(); + memset(_thisptr->name, 0x00, 0x8); + return _thisptr; + } + + static const char* operator_const_char(DirectoryName* _thisptr) + { + return _thisptr->name; + } + }; + static_assert(sizeof(DirectoryName) == 8); + + struct BossAccount // the actual class name is "Account" and while the boss namespace helps us separate this from Account(.h) we use an alternative name to avoid confusion + { + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator<VTable> s_VTable; + + uint32be accountId; + MEMPTR<void> vTablePtr; + + static BossAccount* ctor(BossAccount* _this, uint32 accountId) + { + if (!_this) + _this = boss_new<BossAccount>(); + _this->accountId = accountId; + _this->vTablePtr = s_VTable; + return _this; + } + + static void dtor(BossAccount* _this, uint32 options) + { + if(_this && options & 1) + boss_delete(_this); + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); + } + + }; + static_assert(sizeof(BossAccount) == 8); + + struct TaskSetting { static const uint32 kBossCode = 0x7C0; static const uint32 kBossCodeLen = 0x20; @@ -46,8 +277,6 @@ bossBufferVector->buffer = (uint8*)bossRequest; static const uint32 kNbdlFileName = 0x7F8; static const uint32 kFileNameLen = 0x20; - - static const uint32 kURL = 0x48; static const uint32 kURLLen = 0x100; @@ -57,245 +286,75 @@ bossBufferVector->buffer = (uint8*)bossRequest; static const uint32 kServiceToken = 0x590; static const uint32 kServiceTokenLen = 0x200; - uint8 settings[0x1000]; - uint32be uknExt_vTableProbably; // +0x1000 - }; - static_assert(sizeof(TaskSetting_t) == 0x1004); - static_assert(offsetof(TaskSetting_t, uknExt_vTableProbably) == 0x1000, "offsetof(TaskSetting_t, uknExt)"); + MEMPTR<void> vTablePtr; // +0x1000 - struct NetTaskSetting_t : TaskSetting_t + struct VTableTaskSetting + { + VTableEntry rtti; + VTableEntry dtor; + VTableEntry RegisterPreprocess; + VTableEntry unk1; + }; + static inline SysAllocator<VTableTaskSetting> s_VTable; + + static TaskSetting* ctor(TaskSetting* _thisptr) + { + if(!_thisptr) + _thisptr = boss_new<TaskSetting>(); + _thisptr->vTablePtr = s_VTable; + InitializeSetting(_thisptr); + return _thisptr; + } + + static void dtor(TaskSetting* _this, uint32 options) + { + cemuLog_logDebug(LogType::Force, "nn::boss::TaskSetting::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); + if(options & 1) + boss_delete(_this); + } + + static bool IsPrivileged(TaskSetting* _thisptr) + { + const uint16 value = *(uint16be*)&_thisptr->settings[0x28]; + return value == 1 || value == 9 || value == 5; + } + + static void InitializeSetting(TaskSetting* _thisptr) + { + memset(_thisptr, 0x00, sizeof(TaskSetting::settings)); + *(uint32*)&_thisptr->settings[0x0C] = 0; + *(uint8*)&_thisptr->settings[0x2A] = 0x7D; // timeout? + *(uint32*)&_thisptr->settings[0x30] = 0x7080; + *(uint32*)&_thisptr->settings[0x8] = 0; + *(uint32*)&_thisptr->settings[0x38] = 0; + *(uint32*)&_thisptr->settings[0x3C] = 0x76A700; + *(uint32*)&_thisptr->settings[0] = 0x76A700; + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } + }; + static_assert(sizeof(TaskSetting) == 0x1004); + static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000); + + struct NetTaskSetting : TaskSetting { // 0x188 cert1 + 0x188 cert2 + 0x188 cert3 // 0x190 AddCaCert (3times) char cert[0x80]; // SetConnectionSetting // SetFirstLastModifiedTime - }; - static_assert(sizeof(NetTaskSetting_t) == 0x1004); - struct NbdlTaskSetting_t : NetTaskSetting_t - { - //char fileName[0x20]; // @0x7F8 - }; - static_assert(sizeof(NbdlTaskSetting_t) == 0x1004); + struct VTableNetTaskSetting : public VTableTaskSetting + { }; + static inline SysAllocator<VTableNetTaskSetting> s_VTable; - struct RawUlTaskSetting_t : NetTaskSetting_t - { - static const uint32 kType = 0x12340000; - uint32be ukRaw1; // 0x1004 - uint32be ukRaw2; // 0x1008 - uint32be ukRaw3; // 0x100C - uint8 rawSpace[0x200]; // 0x1010 - }; - static_assert(sizeof(RawUlTaskSetting_t) == 0x1210); - - struct PlayReportSetting_t : RawUlTaskSetting_t - { - static const uint32 kType = 0x12340001; - MEMPTR<void*> ukPlay1; // 0x1210 - uint32be ukPlay2; // 0x1214 - uint32be ukPlay3; // 0x1218 - uint32be ukPlay4; // 0x121C - }; - static_assert(sizeof(PlayReportSetting_t) == 0x1220); - - struct RawDlTaskSetting_t : NetTaskSetting_t - { - static const uint32 kType = 0x12340002; - //char fileName[0x20]; // 0x7F8 - }; - static_assert(sizeof(RawDlTaskSetting_t) == 0x1004); - - struct TitleId_t - { - uint64be u64{}; - }; - static_assert(sizeof(TitleId_t) == 8); - - struct TaskId_t - { - char id[0x8]{}; - }; - static_assert(sizeof(TaskId_t) == 8); - - struct Title_t - { - uint32be accountId{}; // 0x00 - TitleId_t titleId{}; // 0x8 - uint32be someValue = 0x12341000; // 0x10 - }; - static_assert(sizeof(Title_t) == 0x18); - - struct DirectoryName_t - { - char name[0x8]{}; - }; - static_assert(sizeof(DirectoryName_t) == 8); - - struct Account_t - { - uint32be accountId; - uint32be uk1; // global struct - }; - static_assert(sizeof(Account_t) == 8); - - struct Task_t - { - uint32be accountId; // 0x00 - uint32be uk2; // 0x04 - TaskId_t taskId; // 0x08 - TitleId_t titleId; // 0x10 - uint32be ext; // 0x18 - uint32be padding; // 0x1C - }; - static_assert(sizeof(Task_t) == 0x20, "sizeof(Task_t)"); - - namespace TaskId - { - TaskId_t* ctor(TaskId_t* thisptr) - { - if(!thisptr) - { - // thisptr = new TaskId_t - assert_dbg(); - } - - if(thisptr) - { - thisptr->id[0] = 0; - } - - return thisptr; - } - } - - namespace Account - { - Account_t* ctor(Account_t* thisptr, uint32 accountId) - { - if (!thisptr) - { - // thisptr = new TaskId_t - assert_dbg(); - } - - thisptr->accountId = accountId; - thisptr->uk1 = 0x12340010; - return thisptr; - } - } - - namespace TitleId - { - TitleId_t* ctor(TitleId_t* thisptr, uint64 titleId) - { - if (!thisptr) - { - // thisptr = new TaskId_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->u64 = titleId; - } - - return thisptr; - } - - TitleId_t* ctor(TitleId_t* thisptr) - { - return ctor(thisptr, 0); - } - - bool IsValid(TitleId_t* thisptr) - { - return thisptr->u64 != 0; - } - - TitleId_t* ctor1(TitleId_t* thisptr, uint32 filler, uint64 titleId) - { - return ctor(thisptr); - } - - TitleId_t* ctor2(TitleId_t* thisptr, uint32 filler, uint64 titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_ctor2(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - thisptr->u64 = titleId; - return thisptr; - } - - - TitleId_t* cctor(TitleId_t* thisptr, TitleId_t* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_cctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - thisptr->u64 = titleId->u64; - - return thisptr; - } - - bool operator_ne(TitleId_t* thisptr, TitleId_t* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_operator_ne(0x{:x})", MEMPTR(thisptr).GetMPTR()); - return thisptr->u64 != titleId->u64; - } - } - - namespace TaskSetting - { - bool IsPrivilegedTaskSetting(TaskSetting_t* thisptr) - { - const uint16 value = *(uint16*)&thisptr->settings[0x28]; - return value == 1 || value == 9 || value == 5; - } - - void InitializeSetting(TaskSetting_t* thisptr) - { - memset(thisptr, 0x00, sizeof(TaskSetting_t::settings)); - *(uint32*)&thisptr->settings[0x0C] = 0; - *(uint8*)&thisptr->settings[0x2A] = 0x7D; // timeout? - *(uint32*)&thisptr->settings[0x30] = 0x7080; - *(uint32*)&thisptr->settings[0x8] = 0; - *(uint32*)&thisptr->settings[0x38] = 0; - *(uint32*)&thisptr->settings[0x3C] = 0x76A700; - *(uint32*)&thisptr->settings[0] = 0x76A700; - } - - TaskSetting_t* ctor(TaskSetting_t* thisptr) - { - if(!thisptr) - { - // thisptr = new TaskSetting_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->uknExt_vTableProbably = 0; - InitializeSetting(thisptr); - } - - return thisptr; - } - - - } - - namespace NetTaskSetting - { - Result AddCaCert(NetTaskSetting_t* thisptr, const char* name) + static Result AddCaCert(NetTaskSetting* _thisptr, const char* name) { if(name == nullptr || strnlen(name, 0x80) == 0x80) { @@ -308,1505 +367,1380 @@ bossBufferVector->buffer = (uint8*)bossRequest; return 0xA0220D00; } - NetTaskSetting_t* ctor(NetTaskSetting_t* thisptr) + static NetTaskSetting* ctor(NetTaskSetting* _thisptr) { - if (!thisptr) - { - // thisptr = new NetTaskSetting_t - assert_dbg(); - } - - if (thisptr) - { - TaskSetting::ctor(thisptr); - *(uint32*)&thisptr->settings[0x18C] = 0x78; - thisptr->uknExt_vTableProbably = 0; - } - - return thisptr; + if (!_thisptr) + _thisptr = boss_new<NetTaskSetting>(); + TaskSetting::ctor(_thisptr); + *(uint32*)&_thisptr->settings[0x18C] = 0x78; + _thisptr->vTablePtr = s_VTable; + return _thisptr; } - Result SetServiceToken(NetTaskSetting_t* thisptr, const uint8* serviceToken) + static Result SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetServiceToken(0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetServiceToken(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); cemuLog_logDebug(LogType::Force, "\t->{}", fmt::ptr(serviceToken)); - memcpy(&thisptr->settings[TaskSetting_t::kServiceToken], serviceToken, TaskSetting_t::kServiceTokenLen); + memcpy(&_thisptr->settings[TaskSetting::kServiceToken], serviceToken, TaskSetting::kServiceTokenLen); return 0x200080; } - Result AddInternalCaCert(NetTaskSetting_t* thisptr, char certId) + static Result AddInternalCaCert(NetTaskSetting* _thisptr, char certId) { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert(0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), (int)certId); + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); - uint32 location = TaskSetting_t::kCACert; + uint32 location = TaskSetting::kCACert; for(int i = 0; i < 3; ++i) { - if(thisptr->settings[location] == 0) + if(_thisptr->settings[location] == 0) { - thisptr->settings[location] = (uint8)certId; + _thisptr->settings[location] = (uint8)certId; return 0x200080; } - location += TaskSetting_t::kCACert; + location += TaskSetting::kCACert; } - + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: can't store certificate"); return 0xA0220D00; } - void SetInternalClientCert(NetTaskSetting_t* thisptr, char certId) + static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId) { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetInternalClientCert(0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), (int)certId); - thisptr->settings[TaskSetting_t::kClientCert] = (uint8)certId; + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetInternalClientCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); + _thisptr->settings[TaskSetting::kClientCert] = (uint8)certId; } - } - namespace NbdlTaskSetting // : NetTaskSetting + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } + }; + static_assert(sizeof(NetTaskSetting) == 0x1004); + + struct NbdlTaskSetting : NetTaskSetting { - NbdlTaskSetting_t* ctor(NbdlTaskSetting_t* thisptr) + struct VTableNbdlTaskSetting : public VTableNetTaskSetting { - if (!thisptr) - { - // thisptr = new NbdlTaskSetting_t - assert_dbg(); - } + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableNbdlTaskSetting) == 8*5); + static inline SysAllocator<VTableNbdlTaskSetting> s_VTable; - if (thisptr) - { - NetTaskSetting::ctor(thisptr); - thisptr->uknExt_vTableProbably = 0; - } - - return thisptr; + static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<NbdlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; } - void export_ctor(PPCInterpreter_t* hCPU) + static Result Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* directoryName) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 { - ppcDefineParamMEMPTR(thisptr, NbdlTaskSetting_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_ctor"); - ctor(thisptr.GetPtr()); - osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); - } - - void export_Initialize(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, NbdlTaskSetting_t, 0); - ppcDefineParamMEMPTR(bossCode, const char, 1); - ppcDefineParamU64(directorySizeLimit, 2); - ppcDefineParamMEMPTR(directoryName, const char, 4); - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_Initialize(0x{:08x}, {}, 0x{:x}, 0x{:08x})", thisptr.GetMPTR(), bossCode.GetPtr(), directorySizeLimit, directoryName.GetMPTR()); - - if(!bossCode || strnlen(bossCode.GetPtr(), 0x20) == 0x20) - { - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780)); - return; - } - - if (directoryName && strnlen(directoryName.GetPtr(), 0x8) == 0x8) - { - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780)); - return; - } - - strncpy((char*)&thisptr->settings[TaskSetting_t::kBossCode], bossCode.GetPtr(), TaskSetting_t::kBossCodeLen); - - *(uint64be*)&thisptr->settings[TaskSetting_t::kDirectorySizeLimit] = directorySizeLimit; // uint64be - if(directoryName) - strncpy((char*)&thisptr->settings[TaskSetting_t::kDirectoryName], directoryName.GetPtr(), TaskSetting_t::kDirectoryNameLen); - - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80)); - } - - Result SetFileName(NbdlTaskSetting_t* thisptr, const char* fileName) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_t_SetFileName(0x{:08x}, {})", MEMPTR(thisptr).GetMPTR(), fileName ? fileName : "\"\""); - - if (!fileName || strnlen(fileName, TaskSetting_t::kFileNameLen) == TaskSetting_t::kFileNameLen) - { + if(!bossCode || strnlen(bossCode, TaskSetting::kBossCodeLen) == TaskSetting::kBossCodeLen) return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - } - - strncpy((char*)&thisptr->settings[TaskSetting_t::kNbdlFileName], fileName, TaskSetting_t::kFileNameLen); + + if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) + return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); + + strncpy((char*)&_thisptr->settings[TaskSetting::kBossCode], bossCode, TaskSetting::kBossCodeLen); + + *(uint64be*)&_thisptr->settings[TaskSetting::kDirectorySizeLimit] = directorySizeLimit; // uint64be + if(directoryName) + strncpy((char*)&_thisptr->settings[TaskSetting::kDirectoryName], directoryName, TaskSetting::kDirectoryNameLen); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - } - - namespace RawUlTaskSetting - { - RawUlTaskSetting_t* ctor(RawUlTaskSetting_t* thisptr) + static Result SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) { - cemuLog_logDebug(LogType::Force, "nn_boss_RawUlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new RawUlTaskSetting_t - assert_dbg(); - } + cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_t_SetFileName(0x{:08x}, {})", MEMPTR(_thisptr).GetMPTR(), fileName ? fileName : "\"\""); + if (!fileName || strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) + return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - if (thisptr) - { - NetTaskSetting::ctor(thisptr); - thisptr->uknExt_vTableProbably = RawUlTaskSetting_t::kType; - thisptr->ukRaw1 = 0; - thisptr->ukRaw2 = 0; - thisptr->ukRaw3 = 0; - memset(thisptr->rawSpace, 0x00, 0x200); - } - - return thisptr; - } - } - - namespace RawDlTaskSetting - { - RawDlTaskSetting_t* ctor(RawDlTaskSetting_t* thisptr) - { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new RawDlTaskSetting_t - assert_dbg(); - } - - if (thisptr) - { - NetTaskSetting::ctor(thisptr); - thisptr->uknExt_vTableProbably = RawDlTaskSetting_t::kType; - } - - return thisptr; + strncpy((char*)&_thisptr->settings[TaskSetting::kNbdlFileName], fileName, TaskSetting::kFileNameLen); + // also sets byte at +0x817 to zero? + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - Result Initialize(RawDlTaskSetting_t* thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) + static void InitVTable() { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_Initialize(0x{:x}, 0x{:x}, {}, {}, 0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + }; + static_assert(sizeof(NbdlTaskSetting) == 0x1004); + + struct RawUlTaskSetting : NetTaskSetting + { + uint32be ukRaw1; // 0x1004 + uint32be ukRaw2; // 0x1008 + uint32be ukRaw3; // 0x100C + uint8 rawSpace[0x200]; // 0x1010 + + struct VTableRawUlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableRawUlTaskSetting) == 8*5); + static inline SysAllocator<VTableRawUlTaskSetting> s_VTable; + + static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<RawUlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + _thisptr->ukRaw1 = 0; + _thisptr->ukRaw2 = 0; + _thisptr->ukRaw3 = 0; + memset(_thisptr->rawSpace, 0x00, 0x200); + return _thisptr; + } + + static void dtor(RawUlTaskSetting* _this, uint32 options) + { + cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + }; + static_assert(sizeof(RawUlTaskSetting) == 0x1210); + + struct RawDlTaskSetting : NetTaskSetting + { + struct VTableRawDlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableRawDlTaskSetting) == 8*5); + static inline SysAllocator<VTableRawDlTaskSetting> s_VTable; + + static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr) + { + cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + _thisptr = boss_new<RawDlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + static Result Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) + { + cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_Initialize(0x{:x}, 0x{:x}, {}, {}, 0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); if (!url) { return 0xC0203780; } - if (strnlen(url, TaskSetting_t::kURLLen) == TaskSetting_t::kURLLen) + if (strnlen(url, TaskSetting::kURLLen) == TaskSetting::kURLLen) { return 0xC0203780; } cemuLog_logDebug(LogType::Force, "\t-> url: {}", url); - if (fileName && strnlen(fileName, TaskSetting_t::kFileNameLen) == TaskSetting_t::kFileNameLen) + if (fileName && strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) { return 0xC0203780; } - if (directoryName && strnlen(directoryName, TaskSetting_t::kDirectoryNameLen) == TaskSetting_t::kDirectoryNameLen) + if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) { return 0xC0203780; } - strncpy((char*)thisptr + TaskSetting_t::kURL, url, TaskSetting_t::kURLLen); - thisptr->settings[0x147] = '\0'; + strncpy((char*)_thisptr + TaskSetting::kURL, url, TaskSetting::kURLLen); + _thisptr->settings[0x147] = '\0'; if (fileName) - strncpy((char*)thisptr + 0x7D0, fileName, TaskSetting_t::kFileNameLen); + strncpy((char*)_thisptr + 0x7D0, fileName, TaskSetting::kFileNameLen); else - strncpy((char*)thisptr + 0x7D0, "rawcontent.dat", TaskSetting_t::kFileNameLen); - thisptr->settings[0x7EF] = '\0'; + strncpy((char*)_thisptr + 0x7D0, "rawcontent.dat", TaskSetting::kFileNameLen); + _thisptr->settings[0x7EF] = '\0'; - cemuLog_logDebug(LogType::Force, "\t-> filename: {}", (char*)thisptr + 0x7D0); + cemuLog_logDebug(LogType::Force, "\t-> filename: {}", (char*)_thisptr + 0x7D0); if (directoryName) { - strncpy((char*)thisptr + 0x7C8, directoryName, TaskSetting_t::kDirectoryNameLen); - thisptr->settings[0x7CF] = '\0'; - cemuLog_logDebug(LogType::Force, "\t-> directoryName: {}", (char*)thisptr + 0x7C8); + strncpy((char*)_thisptr + 0x7C8, directoryName, TaskSetting::kDirectoryNameLen); + _thisptr->settings[0x7CF] = '\0'; + cemuLog_logDebug(LogType::Force, "\t-> directoryName: {}", (char*)_thisptr + 0x7C8); } - thisptr->settings[0x7C0] = newArrival; - thisptr->settings[0x7C1] = led; - *(uint16be*)&thisptr->settings[0x28] = 0x3; + _thisptr->settings[0x7C0] = newArrival; + _thisptr->settings[0x7C1] = led; + *(uint16be*)&_thisptr->settings[0x28] = 0x3; return 0x200080; } - } - namespace PlayReportSetting // : NetTaskSetting - { - void export_ctor(PPCInterpreter_t* hCPU) + static void InitVTable() { - ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_ctor TODO"); - if (!thisptr) - { - assert_dbg(); - } + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + }; + static_assert(sizeof(RawDlTaskSetting) == 0x1004); - if (thisptr) - { - RawUlTaskSetting::ctor(thisptr.GetPtr()); - thisptr->uknExt_vTableProbably = PlayReportSetting_t::kType; - thisptr->ukPlay1 = nullptr; - thisptr->ukPlay2 = 0; - thisptr->ukPlay3 = 0; - thisptr->ukPlay4 = 0; - } + struct PlayReportSetting : RawUlTaskSetting + { + MEMPTR<uint8> ukn1210_ptr; // 0x1210 + uint32be ukn1214_size; // 0x1214 + uint32be ukPlay3; // 0x1218 + uint32be ukPlay4; // 0x121C - osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); + struct VTablePlayReportSetting : public VTableRawUlTaskSetting + {}; + static_assert(sizeof(VTablePlayReportSetting) == 8*5); + static inline SysAllocator<VTablePlayReportSetting> s_VTable; + + static PlayReportSetting* ctor(PlayReportSetting* _this) + { + if(!_this) + _this = boss_new<PlayReportSetting>(); + RawUlTaskSetting::ctor(_this); + _this->vTablePtr = s_VTable; + _this->ukn1210_ptr = nullptr; + _this->ukn1214_size = 0; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; + return _this; } - void export_Initialize(PPCInterpreter_t* hCPU) + static void dtor(PlayReportSetting* _this, uint32 options) { - ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); - ppcDefineParamMEMPTR(ptr, void*, 1); - ppcDefineParamU32(value, 2); - ppcDefineParamMEMPTR(directoryName, const char, 4); - //cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_Initialize(0x{:08x}, {}, 0x{:x}, 0x{:08x})", thisptr.GetMPTR(), ptr.GetPtr(), directorySizeLimit, directoryName.GetMPTR()); + RawUlTaskSetting::dtor(_this, 0); + if(options&1) + boss_delete(_this->ukn1210_ptr.GetPtr()); + } - if(!ptr || value == 0 || value > 0x19000) + static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) + { + if(!ptr || size == 0 || size > 0x19000) { - cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_Initialize: invalid parameter"); - osLib_returnFromFunction(hCPU, 0); + cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); + return; } - *ptr.GetPtr<uint8>() = 0; + *ptr = 0; - *(uint16be*)&thisptr->settings[0x28] = 6; - *(uint16be*)&thisptr->settings[0x2B] |= 0x3; - *(uint16be*)&thisptr->settings[0x2C] |= 0xA; - *(uint32be*)&thisptr->settings[0x7C0] |= 2; - - thisptr->ukPlay1 = ptr; - thisptr->ukPlay2 = value; - thisptr->ukPlay3 = 0; - thisptr->ukPlay4 = 0; + *(uint16be*)&_this->settings[0x28] = 6; + *(uint16be*)&_this->settings[0x2B] |= 0x3; + *(uint16be*)&_this->settings[0x2C] |= 0xA; + *(uint32be*)&_this->settings[0x7C0] |= 2; + + _this->ukn1210_ptr = ptr; + _this->ukn1214_size = size; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; // TODO - osLib_returnFromFunction(hCPU, 0); } - void export_Set(PPCInterpreter_t* hCPU) + static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value) { - ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); - ppcDefineParamMEMPTR(key, const char, 1); - ppcDefineParamU32(value, 2); - // TODO - cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_Set(0x{:08x}, {}, 0x{:x}) TODO", thisptr.GetMPTR(), key.GetPtr(), value); - - osLib_returnFromFunction(hCPU, 1); + return true; } - - } - - namespace Title - { - Title_t* ctor(Title_t* thisptr) + static void InitVTable() { - cemuLog_logDebug(LogType::Force, "nn_boss_Title_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - *thisptr = {}; - - return thisptr; + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo } - } + }; + static_assert(sizeof(PlayReportSetting) == 0x1220); - namespace DirectoryName + struct Task { - DirectoryName_t* ctor(DirectoryName_t* thisptr) + struct VTableTask { - cemuLog_logDebug(LogType::Force, "nn_boss_DirectoryName_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator<VTableTask> s_vTable; - memset(thisptr->name, 0x00, 0x8); + uint32be accountId; // 0x00 + uint32be uk2; // 0x04 + TaskId taskId; // 0x08 + TitleId titleId; // 0x10 + MEMPTR<VTableTask> vTablePtr; // 0x18 + uint32be padding; // 0x1C - return thisptr; - } - - const char* operator_const_char(DirectoryName_t* thisptr) - { - cemuLog_logDebug(LogType::Force, "nn_boss_DirectoryName_operator_const_char(0x{:x})", MEMPTR(thisptr).GetMPTR()); - return thisptr->name; - } - } - - namespace Task - { - - Result Initialize(Task_t* thisptr, const char* taskId, uint32 accountId) + static Result Initialize1(Task* _thisptr, const char* taskId, uint32 accountId) // Initialize__Q3_2nn4boss4TaskFPCcUi { if(!taskId || strnlen(taskId, 0x8) == 8) { return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); } - - thisptr->accountId = accountId; - strncpy(thisptr->taskId.id, taskId, 0x08); + _thisptr->accountId = accountId; + strncpy(_thisptr->taskId.id, taskId, 0x08); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - Result Initialize(Task_t* thisptr, uint8 slot, const char* taskId) + static Result Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc { const uint32 accountId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); - return Initialize(thisptr, taskId, accountId); + return Initialize1(_thisptr, taskId, accountId); } - Result Initialize(Task_t* thisptr, const char* taskId) + static Result Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc { - return Initialize(thisptr, taskId, 0); + return Initialize1(_thisptr, taskId, 0); } - void export_Initialize3(PPCInterpreter_t* hCPU) + static Task* ctor2(Task* _thisptr, const char* taskId, uint32 accountId) // __ct__Q3_2nn4boss4TaskFPCcUi { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(taskId, const char, 1); - ppcDefineParamU32(accountId, 2); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Initialize3(0x{:08x}, {}, 0x{:x})", thisptr.GetMPTR(), taskId.GetPtr(), accountId); - const Result result = Initialize(thisptr.GetPtr(), taskId.GetPtr(), accountId); - osLib_returnFromFunction(hCPU, result); + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + auto r = Initialize1(_thisptr, taskId, accountId); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); + return _thisptr; } - void export_Initialize2(PPCInterpreter_t* hCPU) + static Task* ctor1(Task* _thisptr, uint8 slot, const char* taskId) // __ct__Q3_2nn4boss4TaskFUcPCc { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU8(slotId, 1); - ppcDefineParamMEMPTR(taskId, const char, 2); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Initialize2(0x{:08x}, {}, {})", thisptr.GetMPTR(), slotId, taskId.GetPtr()); - const Result result = Initialize(thisptr.GetPtr(), slotId, taskId.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - void export_Initialize1(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(taskId, const char, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Initialize1(0x{:08x}, {})", thisptr.GetMPTR(), taskId.GetPtr()); - const Result result = Initialize(thisptr.GetPtr(), taskId.GetPtr()); - osLib_returnFromFunction(hCPU, result); + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + auto r = Initialize2(_thisptr, slot, taskId); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); + return _thisptr; } - - Task_t* ctor(Task_t* thisptr, const char* taskId, uint32 accountId) + static Task* ctor3(Task* _thisptr, const char* taskId) // __ct__Q3_2nn4boss4TaskFPCc { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, taskId, accountId))); - } - - return thisptr; + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + auto r = Initialize3(_thisptr, taskId); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); + return _thisptr; } - Task_t* ctor(Task_t* thisptr, uint8 slot, const char* taskId) + static Task* ctor4(Task* _thisptr) // __ct__Q3_2nn4boss4TaskFv { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, slot, taskId))); - } - - return thisptr; + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + memset(&_thisptr->taskId, 0x00, sizeof(TaskId)); + return _thisptr; } - Task_t* ctor(Task_t* thisptr, const char* taskId) + static void dtor(Task* _this, uint32 options) // __dt__Q3_2nn4boss4TaskFv { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, taskId))); - } - - return thisptr; + cemuLog_logDebug(LogType::Force, "nn::boss::Task::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); + // todo - Task::Finalize + if(options & 1) + boss_delete(_this); } - Task_t* ctor(Task_t* thisptr) + static Result Run(Task* _thisptr, bool isForegroundRun) { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - memset(&thisptr->taskId, 0x00, sizeof(TaskId_t)); - } - - return thisptr; - } - - void export_ctor(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_ctor(0x{:08x})", thisptr.GetMPTR()); - ctor(thisptr.GetPtr()); - osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); - } - - void export_Run(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU8(isForegroundRun, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Run(0x{:08x}, {})", thisptr.GetMPTR(), isForegroundRun); if (isForegroundRun != 0) { - //peterBreak(); cemuLog_logDebug(LogType::Force, "export_Run foreground run"); } bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_RUN; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = isForegroundRun != 0; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_StartScheduling(PPCInterpreter_t* hCPU) + static Result StartScheduling(Task* _thisptr, uint8 executeImmediately) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU8(executeImmediately, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_StartScheduling(0x{:08x}, {})", thisptr.GetMPTR(), executeImmediately); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_START_SCHEDULING; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = executeImmediately != 0; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_StopScheduling(PPCInterpreter_t* hCPU) + static Result StopScheduling(Task* _thisptr) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_StopScheduling(0x{:08x})", thisptr.GetMPTR()); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_STOP_SCHEDULING; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_IsRegistered(PPCInterpreter_t* hCPU) + static Result IsRegistered(Task* _thisptr) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_IS_REGISTERED; - bossRequest->accountId = thisptr->accountId; - bossRequest->titleId = thisptr->titleId.u64; - bossRequest->taskId = thisptr->taskId.id; + bossRequest->accountId = _thisptr->accountId; + bossRequest->titleId = _thisptr->titleId.u64; + bossRequest->taskId = _thisptr->taskId.id; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_IsRegistered(0x{:08x}) -> {}", thisptr.GetMPTR(), bossRequest->returnCode); - - osLib_returnFromFunction(hCPU, bossRequest->returnCode); + return bossRequest->returnCode; } - void export_Wait(PPCInterpreter_t* hCPU) + static Result Wait(Task* _thisptr, uint32 timeout, uint32 waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState { - // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU32(timeout, 1); - ppcDefineParamU32(waitState, 2); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Wait(0x{:08x}, 0x{:x}, {})", thisptr.GetMPTR(), timeout, waitState); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_WAIT; - bossRequest->titleId = thisptr->titleId.u64; - bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; + bossRequest->taskId = _thisptr->taskId.id; bossRequest->timeout = timeout; bossRequest->waitState = waitState; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, bossRequest->returnCode); - - //osLib_returnFromFunction(hCPU, 1); // 0 -> timeout, 1 -> wait condition met + return bossRequest->returnCode; } - void export_RegisterForImmediateRun(PPCInterpreter_t* hCPU) + static Result RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting { - // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(settings, TaskSetting_t, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_RegisterForImmediateRun(0x{:08x}, 0x{:08x})", thisptr.GetMPTR(), settings.GetMPTR()); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->settings = settings.GetPtr(); + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->settings = settings; bossRequest->uk1 = 0xC00; - if (TaskSetting::IsPrivilegedTaskSetting(settings.GetPtr())) - bossRequest->titleId = thisptr->titleId.u64; + if (TaskSetting::IsPrivileged(settings)) + bossRequest->titleId = _thisptr->titleId.u64; - const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, result); + Result result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + return result; } - void export_Unregister(PPCInterpreter_t* hCPU) + static Result Unregister(Task* _thisptr) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Unregister(0x{:08x})", thisptr.GetMPTR()); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_UNREGISTER; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, result); + return result; } - void export_Register(PPCInterpreter_t* hCPU) + static Result Register(Task* _thisptr, TaskSetting* settings) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(settings, TaskSetting_t, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register(0x{:08x}, 0x{:08x})", thisptr.GetMPTR(), settings.GetMPTR()); - - if (hCPU->gpr[4] == 0) + if (!settings) { - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); - osLib_returnFromFunction(hCPU, 0); - return; + cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); // settings should never be zero + return 0; } bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->settings = settings.GetPtr(); + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->settings = settings; bossRequest->uk1 = 0xC00; - if(TaskSetting::IsPrivilegedTaskSetting(settings.GetPtr())) - bossRequest->titleId = thisptr->titleId.u64; + if(TaskSetting::IsPrivileged(settings)) + bossRequest->titleId = _thisptr->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, bossRequest->returnCode); + return bossRequest->returnCode; } - - - void export_GetTurnState(PPCInterpreter_t* hCPU) + + static uint32 GetTurnState(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_TURN_STATE; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u32.exec_count; + if (executionCountOut) + *executionCountOut = bossRequest->u32.exec_count; - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetTurnState(0x{:08x}, 0x{:08x}) -> {}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u32.result); - - osLib_returnFromFunction(hCPU, bossRequest->u32.result); - //osLib_returnFromFunction(hCPU, 7); // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) RETURN FINISHED + return bossRequest->u32.result; + // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) } - void export_GetContentLength(PPCInterpreter_t* hCPU) + static uint64 GetContentLength(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u64.exec_count; + if (executionCountOut) + *executionCountOut = bossRequest->u64.exec_count; - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetContentLength(0x{:08x}, 0x{:08x}) -> 0x{:x}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u64.result); - - osLib_returnFromFunction64(hCPU, bossRequest->u64.result); + return bossRequest->u64.result; } - void export_GetProcessedLength(PPCInterpreter_t* hCPU) + static uint64 GetProcessedLength(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u64.exec_count; - - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetProcessedLength(0x{:08x}, 0x{:08x}) -> 0x{:x}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u64.result); - - osLib_returnFromFunction64(hCPU, bossRequest->u64.result); + if (executionCountOut) + *executionCountOut = bossRequest->u64.exec_count; + return bossRequest->u64.result; } - void export_GetHttpStatusCode(PPCInterpreter_t* hCPU) + static uint32 GetHttpStatusCode(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u32.exec_count; + if (executionCountOut) + *executionCountOut = bossRequest->u32.exec_count; - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetHttpStatusCode(0x{:08x}, 0x{:08x}) -> {}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u32.result); - - osLib_returnFromFunction(hCPU, bossRequest->u32.result); + return bossRequest->u32.result; } - } - struct PrivilegedTask_t : Task_t - { - + static void InitVTable() + { + s_vTable->rtti.ptr = nullptr; // todo + s_vTable->dtor.ptr = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { Task::dtor(MEMPTR<Task>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }); + } }; - static_assert(sizeof(PrivilegedTask_t) == 0x20); - - struct AlmightyTask_t : PrivilegedTask_t + + static_assert(sizeof(Task) == 0x20); + + struct PrivilegedTask : Task { - + struct VTablePrivilegedTask : public VTableTask + { + VTableEntry rttiTask; + }; + static_assert(sizeof(VTablePrivilegedTask) == 8*3); + static inline SysAllocator<VTablePrivilegedTask> s_VTable; + + static PrivilegedTask* ctor(PrivilegedTask* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<PrivilegedTask>(); + Task::ctor4(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + static void dtor(PrivilegedTask* _this, uint32 options) + { + if(!_this) + return; + Task::dtor(_this, 0); + if(options & 1) + boss_delete(_this); + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(PrivilegedTask); + s_VTable->rttiTask.ptr = nullptr; // todo + } }; - static_assert(sizeof(AlmightyTask_t) == 0x20); - - namespace PrivilegedTask - { - PrivilegedTask_t* ctor(PrivilegedTask_t*thisptr) - { - if (!thisptr) - assert_dbg(); // new - - Task::ctor(thisptr); - thisptr->ext = 0x10003a50; - return thisptr; - } - } + static_assert(sizeof(PrivilegedTask) == 0x20); - namespace AlmightyTask + struct AlmightyTask : PrivilegedTask { - AlmightyTask_t* ctor(AlmightyTask_t* thisptr) - { - if (!thisptr) - assert_dbg(); // new + struct VTableAlmightyTask : public VTablePrivilegedTask + {}; + static_assert(sizeof(VTableAlmightyTask) == 8*3); + static inline SysAllocator<VTableAlmightyTask> s_VTable; - PrivilegedTask::ctor(thisptr); - thisptr->ext = 0x10002a0c; - return thisptr; - } - void dtor(AlmightyTask_t* thisptr) + static AlmightyTask* ctor(AlmightyTask* _thisptr) { - if (thisptr) - freeMem(thisptr); + if (!_thisptr) + _thisptr = boss_new<AlmightyTask>(); + PrivilegedTask::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; } - - uint32 Initialize(AlmightyTask_t* thisptr, TitleId_t* titleId, const char* taskId, uint32 accountId) + + static void dtor(AlmightyTask* _thisptr, uint32 options) { - if (!thisptr) + if (!_thisptr) + return; + PrivilegedTask::dtor(_thisptr, 0); + if(options&1) + boss_delete(_thisptr); + } + + static uint32 Initialize(AlmightyTask* _thisptr, TitleId* titleId, const char* taskId, uint32 accountId) + { + if (!_thisptr) return 0xc0203780; - thisptr->accountId = accountId; - thisptr->titleId.u64 = titleId->u64; - strncpy(thisptr->taskId.id, taskId, 8); - thisptr->taskId.id[7] = 0x00; - + _thisptr->accountId = accountId; + _thisptr->titleId.u64 = titleId->u64; + strncpy(_thisptr->taskId.id, taskId, 8); + _thisptr->taskId.id[7] = 0x00; + return 0x200080; } - } - Result InitializeImpl() - { - // if( Initialize(IpcClientCafe*) ) ... - g_isInitialized = true; - return 0; - } - - void export_IsInitialized(PPCInterpreter_t* hCPU) - { - osLib_returnFromFunction(hCPU, (uint32)g_isInitialized); - } - - Result Initialize() - { - Result result; - coreinit::OSLockMutex(&g_mutex); - - if(g_initCounter != 0 || NN_RESULT_IS_SUCCESS((result=InitializeImpl()))) + static void InitVTable() { - g_initCounter++; - result = 0; + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(AlmightyTask); + s_VTable->rttiTask.ptr = nullptr; // todo + } + }; + static_assert(sizeof(AlmightyTask) == 0x20); + + struct DataName + { + char name[32]; + + static DataName* ctor(DataName* _this) // __ct__Q3_2nn4boss8DataNameFv + { + if(!_this) + _this = boss_new<DataName>(); + memset(_this->name, 0, sizeof(name)); + return _this; } - coreinit::OSUnlockMutex(&g_mutex); - return result; - } + static const char* operator_const_char(DataName* _this) // __opPCc__Q3_2nn4boss8DataNameCFv + { + return _this->name; + } + }; + static_assert(sizeof(DataName) == 0x20); - void export_Initialize(PPCInterpreter_t* hCPU) + struct BossStorageFadEntry { - cemuLog_logDebug(LogType::Force, "nn_boss_Initialize()"); - osLib_returnFromFunction(hCPU, Initialize()); - } - - void export_GetBossState(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "nn_boss.GetBossState() - stub"); - osLib_returnFromFunction(hCPU, 7); - } - - enum StorageKind - { - kStorageKind_NBDL, - kStorageKind_RawDl, + char name[32]; + uint32be fileNameId; + uint32 ukn24; + uint32 ukn28; + uint32 ukn2C; + uint32 ukn30; + uint32be timestampRelated; // guessed }; - namespace Storage +#define FAD_ENTRY_MAX_COUNT 512 + + struct Storage { - struct bossStorage_t + struct VTableStorage { - /* +0x00 */ uint32be accountId; - /* +0x04 */ uint32be storageKind; - /* +0x08 */ uint8 ukn08Array[3]; - /* +0x0B */ char storageName[8]; - uint8 ukn13; - uint8 ukn14; - uint8 ukn15; - uint8 ukn16; - uint8 ukn17; - /* +0x18 */ - nn::boss::TitleId_t titleId; - uint32be ukn20; // pointer to some global struct - uint32be ukn24; + VTableEntry rtti; + VTableEntry dtor; }; - - static_assert(sizeof(bossStorage_t) == 0x28); - static_assert(offsetof(bossStorage_t, storageKind) == 0x04); - static_assert(offsetof(bossStorage_t, ukn08Array) == 0x08); - static_assert(offsetof(bossStorage_t, storageName) == 0x0B); - static_assert(offsetof(bossStorage_t, titleId) == 0x18); + static inline SysAllocator<VTableStorage> s_vTable; - bossStorage_t* ctor(bossStorage_t* thisptr) + enum StorageKind { - cemuLog_logDebug(LogType::Force, "nn_boss_Storage_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new RawDlTaskSetting_t - assert_dbg(); - } + kStorageKind_NBDL, + kStorageKind_RawDl, + }; - if (thisptr) - { - thisptr->titleId.u64 = 0; - thisptr->ukn20 = 0x10000a64; - } + /* +0x00 */ uint32be accountId; + /* +0x04 */ uint32be storageKind; + /* +0x08 */ uint8 ukn08Array[3]; + /* +0x0B */ char storageName[8]; + uint8 ukn13; + uint8 ukn14; + uint8 ukn15; + uint8 ukn16; + uint8 ukn17; + /* +0x18 */ nn::boss::TitleId titleId; + /* +0x20 */ MEMPTR<VTableStorage> vTablePtr; + /* +0x24 */ uint32be ukn24; - return thisptr; + static nn::boss::Storage* ctor1(nn::boss::Storage* _this) // __ct__Q3_2nn4boss7StorageFv + { + if(!_this) + _this = boss_new<nn::boss::Storage>(); + _this->vTablePtr = s_vTable; + _this->titleId.u64 = 0; + return _this; } - void nnBossStorage_prepareTitleId(bossStorage_t* storage) + static void dtor(nn::boss::Storage* _this, uint32 options) // __dt__Q3_2nn4boss7StorageFv + { + cemuLog_logDebug(LogType::Force, "nn::boss::Storage::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); + Finalize(_this); + if(options & 1) + boss_delete(_this); + } + + static void nnBossStorage_prepareTitleId(Storage* storage) { if (storage->titleId.u64 != 0) return; storage->titleId.u64 = CafeSystem::GetForegroundTitleId(); } - Result Initialize(bossStorage_t* thisptr, const char* dirName, uint32 accountId, StorageKind type) + static Result Initialize(Storage* _thisptr, const char* dirName, uint32 accountId, StorageKind type) { if (!dirName) return 0xC0203780; cemuLog_logDebug(LogType::Force, "boss::Storage::Initialize({}, 0x{:08x}, {})", dirName, accountId, type); - thisptr->storageKind = type; - thisptr->titleId.u64 = 0; + _thisptr->storageKind = type; + _thisptr->titleId.u64 = 0; - memset(thisptr->storageName, 0, 0x8); - strncpy(thisptr->storageName, dirName, 0x8); - thisptr->storageName[7] = '\0'; + memset(_thisptr->storageName, 0, 0x8); + strncpy(_thisptr->storageName, dirName, 0x8); + _thisptr->storageName[7] = '\0'; - thisptr->accountId = accountId; + _thisptr->accountId = accountId; - nnBossStorage_prepareTitleId(thisptr); // usually not done like this + nnBossStorage_prepareTitleId(_thisptr); // usually not done like this return 0x200080; } - Result Initialize2(bossStorage_t* thisptr, const char* dirName, StorageKind type) + static Result Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) { - return Initialize(thisptr, dirName, 0, type); + return Initialize(_thisptr, dirName, 0, type); } - } - using Storage_t = Storage::bossStorage_t; - struct AlmightyStorage_t : Storage_t - { - }; - static_assert(sizeof(AlmightyStorage_t) == 0x28); - - namespace AlmightyStorage - { - AlmightyStorage_t* ctor(AlmightyStorage_t* thisptr) + static void Finalize(Storage* _this) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) + memset(_this, 0, sizeof(Storage)); // todo - not all fields might be cleared + } + + static Result GetDataList(nn::boss::Storage* storage, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 + { + // initialize titleId of storage if not already done + nnBossStorage_prepareTitleId(storage); + + cemu_assert_debug(startIndex == 0); // non-zero index is todo + + // load fad.db + BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); + if (fadTable) { - // thisptr = new RawDlTaskSetting_t - assert_dbg(); + sint32 validEntryCount = 0; + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if( fadTable[i].name[0] == '\0' ) + continue; + memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); + validEntryCount++; + if (validEntryCount >= maxEntries) + break; + } + *outputEntryCount = validEntryCount; + free(fadTable); } - - if (thisptr) + else { - Storage::ctor(thisptr); - thisptr->ukn20 = 0x100028a4; + // could not load fad table + *outputEntryCount = 0; } - - return thisptr; + return 0; // todo } - uint32 Initialize(AlmightyStorage_t* thisptr, TitleId_t* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) + static bool Exist(nn::boss::Storage* storage) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_Initialize(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - return 0xc0203780; - - thisptr->accountId = accountId; - thisptr->storageKind = storageKind; - thisptr->titleId.u64 = titleId->u64; - - strncpy(thisptr->storageName, storageName, 8); - thisptr->storageName[0x7] = 0x00; - - return 0x200080; - } - } -} -} - -// Storage - -struct bossDataName_t -{ - char name[32]; -}; - -static_assert(sizeof(bossDataName_t) == 0x20); - -struct bossStorageFadEntry_t -{ - char name[32]; - uint32be fileNameId; - uint32 ukn24; - uint32 ukn28; - uint32 ukn2C; - uint32 ukn30; - uint32be timestampRelated; // guessed -}; - -// __ct__Q3_2nn4boss8DataNameFv -void nnBossDataNameExport_ct(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(dataName, bossDataName_t, 0); - memset(dataName, 0, sizeof(bossDataName_t)); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(dataName)); -} - -// __opPCc__Q3_2nn4boss8DataNameCFv -void nnBossDataNameExport_opPCc(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(dataName, bossDataName_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(dataName->name)); -} - -void nnBossStorageExport_ct(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); - cemuLog_logDebug(LogType::Force, "Constructor for boss storage called"); - // todo - memset(storage, 0, sizeof(nn::boss::Storage::bossStorage_t)); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(storage)); -} - -void nnBossStorageExport_exist(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss.Storage_Exist(...) TODO"); - - // todo - osLib_returnFromFunction(hCPU, 1); -} - -#define FAD_ENTRY_MAX_COUNT 512 - -FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage::bossStorage_t* storage, uint32 fileNameId) -{ - char storageFilePath[1024]; - sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); - sint32 fscStatus; - FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); - return fscStorageFile; -} - -bossStorageFadEntry_t* nnBossStorageFad_getTable(nn::boss::Storage::bossStorage_t* storage) -{ - const auto accountId = ActiveSettings::GetPersistentId(); - char fadPath[1024]; - sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); - - sint32 fscStatus; - FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (!fscFadFile) - { - return nullptr; - } - // skip first 8 bytes - fsc_setFileSeek(fscFadFile, 8); - // read entries - bossStorageFadEntry_t* fadTable = (bossStorageFadEntry_t*)malloc(sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); - memset(fadTable, 0, sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); - fsc_readFile(fscFadFile, fadTable, sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); - fsc_close(fscFadFile); - return fadTable; -} - -// Find index of entry by name. Returns -1 if not found -sint32 nnBossStorageFad_getIndexByName(bossStorageFadEntry_t* fadTable, char* name) -{ - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) - { - if (fadTable[i].name[0] == '\0') - continue; - if (strncmp(name, fadTable[i].name, 0x20) == 0) - { - return i; - } - } - return -1; -} - -bool nnBossStorageFad_getEntryByName(nn::boss::Storage::bossStorage_t* storage, char* name, bossStorageFadEntry_t* fadEntry) -{ - bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); - if (entryIndex >= 0) - { - memcpy(fadEntry, fadTable + entryIndex, sizeof(bossStorageFadEntry_t)); - free(fadTable); + cemuLog_logDebug(LogType::Force, "nn_boss::Storage::Exist() TODO"); return true; } - free(fadTable); - } - return false; -} -void nnBossStorageExport_getDataList(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); - ppcDefineParamStructPtr(dataList, bossDataName_t, 1); - ppcDefineParamS32(maxEntries, 2); - ppcDefineParamU32BEPtr(outputEntryCount, 3); - cemuLog_logDebug(LogType::Force, "boss storage getDataList()"); + /* FAD access */ - // initialize titleId of storage if not already done - nnBossStorage_prepareTitleId(storage); - - // load fad.db - bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 validEntryCount = 0; - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + static FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage* storage, uint32 fileNameId) { - if( fadTable[i].name[0] == '\0' ) - continue; - memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); - validEntryCount++; - if (validEntryCount >= maxEntries) - break; + char storageFilePath[1024]; + sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); + sint32 fscStatus; + FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); + return fscStorageFile; } - *outputEntryCount = validEntryCount; - free(fadTable); - } - else - { - // could not load fad table - *outputEntryCount = 0; - } - osLib_returnFromFunction(hCPU, 0); // error code -} -// NsData - -typedef struct -{ - /* +0x00 */ char name[0x20]; - /* +0x20 */ nn::boss::Storage::bossStorage_t storage; - /* +0x48 */ uint64 readIndex; - /* +0x50 */ uint32 ukn50; // some pointer to a global struct - /* +0x54 */ uint32 ukn54; -}nsData_t; - -void nnBossNsDataExport_ct(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nnBossNsDataExport_ct"); - ppcDefineParamStructPtr(nsData, nsData_t, 0); - if (!nsData) - assert_dbg(); - - - nsData->ukn50 = 0x10000530; - - memset(nsData->name, 0, 0x20); - - nsData->storage.ukn20 = 0x10000798; - nsData->storage.titleId.u64 = 0; - - nsData->readIndex = 0; - - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(nsData)); -} - -void nnBossNsDataExport_initialize(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 1); - ppcDefineParamStr(dataName, 2); - - if(dataName == nullptr) - { - if (storage->storageKind != 1) + static BossStorageFadEntry* nnBossStorageFad_getTable(nn::boss::Storage* storage) { - osLib_returnFromFunction(hCPU, 0xC0203780); - return; + const auto accountId = ActiveSettings::GetPersistentId(); + char fadPath[1024]; + sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); + + sint32 fscStatus; + FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (!fscFadFile) + { + return nullptr; + } + // skip first 8 bytes + fsc_setFileSeek(fscFadFile, 8); + // read entries + BossStorageFadEntry* fadTable = (BossStorageFadEntry*)malloc(sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); + memset(fadTable, 0, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); + fsc_readFile(fscFadFile, fadTable, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); + fsc_close(fscFadFile); + return fadTable; } - } - - nsData->storage.accountId = storage->accountId; - nsData->storage.storageKind = storage->storageKind; - memcpy(nsData->storage.ukn08Array, storage->ukn08Array, 3); - memcpy(nsData->storage.storageName, storage->storageName, 8); - - nsData->storage.titleId.u64 = storage->titleId.u64; - - nsData->storage = *storage; - - if (dataName != nullptr || storage->storageKind != 1) - strncpy(nsData->name, dataName, 0x20); - else - strncpy(nsData->name, "rawcontent.dat", 0x20); - nsData->name[0x1F] = '\0'; - - nsData->readIndex = 0; - - cemuLog_logDebug(LogType::Force, "nnBossNsDataExport_initialize: {}", nsData->name); - - osLib_returnFromFunction(hCPU, 0x200080); -} - -std::string nnBossNsDataExport_GetPath(nsData_t* nsData) -{ - uint32 accountId = nsData->storage.accountId; - if (accountId == 0) - accountId = iosuAct_getAccountIdOfCurrentAccount(); - - uint64 title_id = nsData->storage.titleId.u64; - if (title_id == 0) - title_id = CafeSystem::GetForegroundTitleId(); - - 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(); -} - -void nnBossNsDataExport_DeleteRealFileWithHistory(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss.NsData_DeleteRealFileWithHistory(...)"); - - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - // todo - cemuLog_log(LogType::Force, "BOSS NBDL: Unsupported delete"); - } - else - { - sint32 fscStatus = FSC_STATUS_OK; - std::string filePath = nnBossNsDataExport_GetPath(nsData).c_str(); - fsc_remove((char*)filePath.c_str(), &fscStatus); - if (fscStatus != 0) - cemuLog_log(LogType::Force, "Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); - } - osLib_returnFromFunction(hCPU, 0); -} - -void nnBossNsDataExport_Exist(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nn_boss.NsData_Exist(...)"); - ppcDefineParamStructPtr(nsData, nsData_t, 0); - - bool fileExists = false; - if(nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - // check if name is present in fad table - bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(&nsData->storage); - if (fadTable) + // Find index of entry by name. Returns -1 if not found + static sint32 nnBossStorageFad_getIndexByName(BossStorageFadEntry* fadTable, char* name) { - fileExists = nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; - cemuLog_logDebug(LogType::Force, "\t({}) -> {}", nsData->name, fileExists); - free(fadTable); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (fadTable[i].name[0] == '\0') + continue; + if (strncmp(name, fadTable[i].name, 0x20) == 0) + { + return i; + } + } + return -1; } - } - else - { - sint32 fscStatus; - auto fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); - if (fscStorageFile != nullptr) + + static bool nnBossStorageFad_getEntryByName(nn::boss::Storage* storage, char* name, BossStorageFadEntry* fadEntry) { - fileExists = true; + BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); + if (fadTable) + { + sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); + if (entryIndex >= 0) + { + memcpy(fadEntry, fadTable + entryIndex, sizeof(BossStorageFadEntry)); + free(fadTable); + return true; + } + free(fadTable); + } + return false; + } + + static void InitVTable() + { + s_vTable->rtti.ptr = nullptr; // todo + s_vTable->dtor.ptr = DTOR_WRAPPER(Storage); + } + }; + + static_assert(sizeof(Storage) == 0x28); + static_assert(offsetof(Storage, storageKind) == 0x04); + static_assert(offsetof(Storage, ukn08Array) == 0x08); + static_assert(offsetof(Storage, storageName) == 0x0B); + static_assert(offsetof(Storage, titleId) == 0x18); + + struct AlmightyStorage : Storage + { + struct VTableAlmightyStorage : public VTableStorage + { + VTableEntry rttiStorage; + }; + static_assert(sizeof(VTableAlmightyStorage) == 8*3); + static inline SysAllocator<VTableAlmightyStorage> s_VTable; + + static AlmightyStorage* ctor(AlmightyStorage* _thisptr) + { + cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_ctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + _thisptr = boss_new<AlmightyStorage>(); + Storage::ctor1(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + static uint32 Initialize(AlmightyStorage* _thisptr, TitleId* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) + { + cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_Initialize(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + return 0xc0203780; + + _thisptr->accountId = accountId; + _thisptr->storageKind = storageKind; + _thisptr->titleId.u64 = titleId->u64; + + strncpy(_thisptr->storageName, storageName, 8); + _thisptr->storageName[0x7] = 0x00; + + return 0x200080; + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(AlmightyStorage); + s_VTable->rttiStorage.ptr = nullptr; // todo + } + }; + static_assert(sizeof(AlmightyStorage) == 0x28); + + // NsData + + struct NsData + { + struct VTableNsData + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator<VTableNsData> s_vTable; + + /* +0x00 */ char name[0x20]; // DataName ? + /* +0x20 */ nn::boss::Storage storage; + /* +0x48 */ uint64 readIndex; + /* +0x50 */ MEMPTR<void> vTablePtr; + /* +0x54 */ uint32 ukn54; + + static NsData* ctor(NsData* _this) + { + if (!_this) + _this = boss_new<NsData>(); + _this->vTablePtr = s_vTable; + memset(_this->name, 0, sizeof(_this->name)); + _this->storage.ctor1(&_this->storage); + _this->readIndex = 0; + return _this; + } + + static void dtor(NsData* _this, uint32 options) // __dt__Q3_2nn4boss6NsDataFv + { + _this->storage.dtor(&_this->storage, 0); + // todo + if(options & 1) + boss_delete(_this); + } + + static Result Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) + { + if(dataName == nullptr) + { + if (storage->storageKind != 1) + { + return 0xC0203780; + } + } + + _this->storage.accountId = storage->accountId; + _this->storage.storageKind = storage->storageKind; + + memcpy(_this->storage.ukn08Array, storage->ukn08Array, 3); + memcpy(_this->storage.storageName, storage->storageName, 8); + + _this->storage.titleId.u64 = storage->titleId.u64; + + _this->storage = *storage; + + if (dataName != nullptr || storage->storageKind != 1) + strncpy(_this->name, dataName, 0x20); + else + strncpy(_this->name, "rawcontent.dat", 0x20); + _this->name[0x1F] = '\0'; + + _this->readIndex = 0; + + cemuLog_logDebug(LogType::Force, "initialize: {}", _this->name); + + return 0x200080; + } + + static std::string _GetPath(NsData* nsData) + { + uint32 accountId = nsData->storage.accountId; + if (accountId == 0) + accountId = iosuAct_getAccountIdOfCurrentAccount(); + + uint64 title_id = nsData->storage.titleId.u64; + if (title_id == 0) + title_id = CafeSystem::GetForegroundTitleId(); + + 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(); + } + + static Result DeleteRealFileWithHistory(NsData* nsData) + { + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + // todo + cemuLog_log(LogType::Force, "BOSS NBDL: Unsupported delete"); + } + else + { + sint32 fscStatus = FSC_STATUS_OK; + std::string filePath = _GetPath(nsData).c_str(); + fsc_remove((char*)filePath.c_str(), &fscStatus); + if (fscStatus != 0) + cemuLog_log(LogType::Force, "Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); + } + return 0; + } + + static uint32 Exist(NsData* nsData) + { + bool fileExists = false; + if(nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + // check if name is present in fad table + BossStorageFadEntry* fadTable = nn::boss::Storage::nnBossStorageFad_getTable(&nsData->storage); + if (fadTable) + { + fileExists = nn::boss::Storage::nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; + cemuLog_logDebug(LogType::Force, "\t({}) -> {}", nsData->name, fileExists); + free(fadTable); + } + } + else + { + sint32 fscStatus; + auto fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); + if (fscStorageFile != nullptr) + { + fileExists = true; + fsc_close(fscStorageFile); + } + } + return fileExists?1:0; + } + + static uint64 GetSize(NsData* nsData) + { + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + BossStorageFadEntry fadEntry; + if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + cemuLog_log(LogType::Force, "BOSS storage cant find file {}", nsData->name); + return 0; + } + // open file + fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (fscStorageFile == nullptr) + { + cemuLog_log(LogType::Force, "BOSS storage cant open file alias {}", nsData->name); + return 0; + } + + // get size + const sint32 fileSize = fsc_getFileSize(fscStorageFile); + // close file fsc_close(fscStorageFile); + return fileSize; } - } - osLib_returnFromFunction(hCPU, fileExists?1:0); -} - -void nnBossNsDataExport_getSize(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - bossStorageFadEntry_t fadEntry; - if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + static uint64 GetCreatedTime(NsData* nsData) { - cemuLog_log(LogType::Force, "BOSS storage cant find file {}", nsData->name); - osLib_returnFromFunction(hCPU, 0); - return; + cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0"); + uint64 createdTime = 0; + return createdTime; } - // open file - fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - if (fscStorageFile == nullptr) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {}", nsData->name); - osLib_returnFromFunction(hCPU, 0); - return; - } - - // get size - const sint32 fileSize = fsc_getFileSize(fscStorageFile); - // close file - fsc_close(fscStorageFile); - 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; - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - bossStorageFadEntry_t fadEntry; - if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + static uint32 nnBossNsData_read(NsData* nsData, uint64be* sizeOutBE, void* buffer, sint32 length) { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + BossStorageFadEntry fadEntry; + if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // open file + fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (!fscStorageFile) + { + cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // get size + sint32 fileSize = fsc_getFileSize(fscStorageFile); + // verify read is within bounds + sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; + sint32 readBytes = length; + if (readEndOffset > fileSize) + { + readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); + cemu_assert_debug(readBytes != 0); + } + // read + fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); + fsc_readFile(fscStorageFile, buffer, readBytes); + nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); + + // close file + fsc_close(fscStorageFile); + if (sizeOutBE) + *sizeOutBE = readBytes; + return 0; } - // open file - fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - - if (!fscStorageFile) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // verify read is within bounds - sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; - sint32 readBytes = length; - if (readEndOffset > fileSize) - { - readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); - cemu_assert_debug(readBytes != 0); - } - // read - fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); - fsc_readFile(fscStorageFile, buffer, readBytes); - nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); - - // close file - fsc_close(fscStorageFile); - if (sizeOutBE) - *sizeOutBE = _swapEndianU64(readBytes); - return 0; -} #define NSDATA_SEEK_MODE_BEGINNING (0) -uint32 nnBossNsData_seek(nsData_t* nsData, uint64 seek, uint32 mode) -{ - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - bossStorageFadEntry_t fadEntry; - if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + static uint32 nnBossNsData_seek(NsData* nsData, uint64 seek, uint32 mode) { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + BossStorageFadEntry fadEntry; + if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // open file + fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (fscStorageFile == nullptr) + { + cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // get size + sint32 fileSize = fsc_getFileSize(fscStorageFile); + // handle seek + if (mode == NSDATA_SEEK_MODE_BEGINNING) + { + seek = std::min(seek, (uint64)fileSize); + nsData->readIndex = _swapEndianU64((uint64)seek); + } + else + { + cemu_assert_unimplemented(); + } + fsc_close(fscStorageFile); + return 0; } - // open file - fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - if (fscStorageFile == nullptr) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // handle seek - if (mode == NSDATA_SEEK_MODE_BEGINNING) - { - seek = std::min(seek, (uint64)fileSize); - nsData->readIndex = _swapEndianU64((uint64)seek); - } - else - { - cemu_assert_unimplemented(); - } - fsc_close(fscStorageFile); - return 0; + static sint32 Read(NsData* nsData, uint8* buffer, sint32 length) + { + cemuLog_logDebug(LogType::Force, "nsData read (filename {})", nsData->name); + return nnBossNsData_read(nsData, nullptr, buffer, length); + } + + static sint32 ReadWithSizeOut(NsData* nsData, uint64be* sizeOut, uint8* buffer, sint32 length) + { + uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); + cemuLog_logDebug(LogType::Force, "nsData readWithSizeOut (filename {} length 0x{:x}) Result: {} Sizeout: {:x}", nsData->name, length, r, _swapEndianU64(*sizeOut)); + return r; + } + + static Result Seek(NsData* nsData, uint64 seekPos, uint32 mode) + { + uint32 r = nnBossNsData_seek(nsData, seekPos, mode); + cemuLog_logDebug(LogType::Force, "nsData seek (filename {} seek 0x{:x}) Result: {}", nsData->name, (uint32)seekPos, r); + return r; + } + + static void InitVTable() + { + s_vTable->rtti.ptr = nullptr; // todo + s_vTable->dtor.ptr = DTOR_WRAPPER(NsData); + } + }; + static_assert(sizeof(NsData) == 0x58); + +} } - -void nnBossNsDataExport_read(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamStr(buffer, 1); - ppcDefineParamS32(length, 2); - - cemuLog_logDebug(LogType::Force, "nsData read (filename {})", nsData->name); - - uint32 r = nnBossNsData_read(nsData, nullptr, buffer, length); - - osLib_returnFromFunction(hCPU, r); -} - -void nnBossNsDataExport_readWithSizeOut(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamTypePtr(sizeOut, uint64, 1); - ppcDefineParamStr(buffer, 2); - ppcDefineParamS32(length, 3); - - uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); - cemuLog_logDebug(LogType::Force, "nsData readWithSizeOut (filename {} length 0x{:x}) Result: {} Sizeout: {:x}", nsData->name, length, r, _swapEndianU64(*sizeOut)); - - osLib_returnFromFunction(hCPU, r); -} - -void nnBossNsDataExport_seek(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamU64(seekPos, 2); - ppcDefineParamU32(mode, 4); - - uint32 r = nnBossNsData_seek(nsData, seekPos, mode); - - cemuLog_logDebug(LogType::Force, "nsData seek (filename {} seek 0x{:x}) Result: {}", nsData->name, (uint32)seekPos, r); - - osLib_returnFromFunction(hCPU, r); -} - void nnBoss_load() { OSInitMutexEx(&nn::boss::g_mutex, nullptr); - osLib_addFunction("nn_boss", "Initialize__Q2_2nn4bossFv", nn::boss::export_Initialize); - osLib_addFunction("nn_boss", "GetBossState__Q2_2nn4bossFv", nn::boss::export_GetBossState); - + nn::boss::g_initCounter = 0; + nn::boss::g_isInitialized = false; + + cafeExportRegisterFunc(nn::boss::GetBossState, "nn_boss", "GetBossState__Q2_2nn4bossFv", LogType::NN_BOSS); + + // boss lib + cafeExportRegisterFunc(nn::boss::Initialize, "nn_boss", "Initialize__Q2_2nn4bossFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::IsInitialized, "nn_boss", "IsInitialized__Q2_2nn4bossFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Finalize, "nn_boss", "Finalize__Q2_2nn4bossFv", LogType::NN_BOSS); + // task - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss4TaskFv", nn::boss::Task::export_ctor); - osLib_addFunction("nn_boss", "Run__Q3_2nn4boss4TaskFb", nn::boss::Task::export_Run); - osLib_addFunction("nn_boss", "Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState", nn::boss::Task::export_Wait); - osLib_addFunction("nn_boss", "GetTurnState__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetTurnState); - osLib_addFunction("nn_boss", "GetHttpStatusCode__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetHttpStatusCode); - osLib_addFunction("nn_boss", "GetContentLength__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetContentLength); - osLib_addFunction("nn_boss", "GetProcessedLength__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetProcessedLength); - osLib_addFunction("nn_boss", "Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", nn::boss::Task::export_Register); - osLib_addFunction("nn_boss", "Unregister__Q3_2nn4boss4TaskFv", nn::boss::Task::export_Unregister); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFPCc", nn::boss::Task::export_Initialize1); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFUcPCc", nn::boss::Task::export_Initialize2); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFPCcUi", nn::boss::Task::export_Initialize3); - osLib_addFunction("nn_boss", "IsRegistered__Q3_2nn4boss4TaskCFv", nn::boss::Task::export_IsRegistered); - osLib_addFunction("nn_boss", "RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting", nn::boss::Task::export_RegisterForImmediateRun); - osLib_addFunction("nn_boss", "StartScheduling__Q3_2nn4boss4TaskFb", nn::boss::Task::export_StartScheduling); - osLib_addFunction("nn_boss", "StopScheduling__Q3_2nn4boss4TaskFv", nn::boss::Task::export_StopScheduling); + nn::boss::Task::InitVTable(); + cafeExportRegisterFunc(nn::boss::Task::ctor1, "nn_boss", "__ct__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::ctor2, "nn_boss", "__ct__Q3_2nn4boss4TaskFPCcUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::ctor3, "nn_boss", "__ct__Q3_2nn4boss4TaskFPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::ctor4, "nn_boss", "__ct__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::dtor, "nn_boss", "__dt__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Initialize1, "nn_boss", "Initialize__Q3_2nn4boss4TaskFPCcUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Initialize3, "nn_boss", "Initialize__Q3_2nn4boss4TaskFPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Run, "nn_boss", "Run__Q3_2nn4boss4TaskFb", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Wait, "nn_boss", "Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetTurnState, "nn_boss", "GetTurnState__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetHttpStatusCode, "nn_boss", "GetHttpStatusCode__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetContentLength, "nn_boss", "GetContentLength__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetProcessedLength, "nn_boss", "GetProcessedLength__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Register, "nn_boss", "Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Unregister, "nn_boss", "Unregister__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::IsRegistered, "nn_boss", "IsRegistered__Q3_2nn4boss4TaskCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::RegisterForImmediateRun, "nn_boss", "RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::StartScheduling, "nn_boss", "StartScheduling__Q3_2nn4boss4TaskFb", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::StopScheduling, "nn_boss", "StopScheduling__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); - // Nbdl task setting - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss15NbdlTaskSettingFv", nn::boss::NbdlTaskSetting::export_ctor); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", nn::boss::NbdlTaskSetting::export_Initialize); - //osLib_addFunction("nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", nn::boss::NbdlTaskSetting::export_SetFileName); - cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::Placeholder); + // TaskSetting + nn::boss::TaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::TaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss11TaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss11TaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TaskSetting::IsPrivileged, "nn_boss", "Initialize__Q3_2nn4boss11TaskSettingFPCcUi", LogType::NN_BOSS); + + // NbdlTaskSetting + nn::boss::NbdlTaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::NN_BOSS); - // play task setting - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", nn::boss::PlayReportSetting::export_ctor); - osLib_addFunction("nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFPCcUi", nn::boss::PlayReportSetting::export_Set); - //osLib_addFunction("nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFUiT1", nn::boss::PlayReportSetting::export_Set); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", nn::boss::PlayReportSetting::export_Initialize); + // PlayReportSetting + nn::boss::PlayReportSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::Set, "nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFPCcUi", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss16RawDlTaskSettingFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss16RawDlTaskSettingFPCcbT2N21", LogType::Placeholder); + // RawDlTaskSetting + nn::boss::RawDlTaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss16RawDlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss16RawDlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss16RawDlTaskSettingFPCcbT2N21", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetServiceToken, "nn_boss", "SetServiceToken__Q3_2nn4boss14NetTaskSettingFPCUc", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::NetTaskSetting::AddInternalCaCert, "nn_boss", "AddInternalCaCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetInternalClientCert, "nn_boss", "SetInternalClientCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::Placeholder); + // NetTaskSetting + nn::boss::NetTaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss14NetTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss14NetTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetServiceToken, "nn_boss", "SetServiceToken__Q3_2nn4boss14NetTaskSettingFPCUc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::AddInternalCaCert, "nn_boss", "AddInternalCaCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetInternalClientCert, "nn_boss", "SetInternalClientCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::NN_BOSS); // Title - cafeExportRegisterFunc(nn::boss::Title::ctor, "nn_boss", "__ct__Q3_2nn4boss5TitleFv", LogType::Placeholder); + nn::boss::Title::InitVTable(); + cafeExportRegisterFunc(nn::boss::Title::ctor, "nn_boss", "__ct__Q3_2nn4boss5TitleFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Title::dtor, "nn_boss", "__dt__Q3_2nn4boss5TitleFv", LogType::NN_BOSS); // cafeExportMakeWrapper<nn::boss::Title::SetNewArrivalFlagOff>("nn_boss", "SetNewArrivalFlagOff__Q3_2nn4boss5TitleFv"); SMM bookmarks // TitleId - cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::TitleId::cctor, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctor3, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); // DataName - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss8DataNameFv", nnBossDataNameExport_ct); - osLib_addFunction("nn_boss", "__opPCc__Q3_2nn4boss8DataNameCFv", nnBossDataNameExport_opPCc); + cafeExportRegisterFunc(nn::boss::DataName::ctor, "nn_boss", "__ct__Q3_2nn4boss8DataNameFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::DataName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss8DataNameCFv", LogType::NN_BOSS); // DirectoryName - cafeExportRegisterFunc(nn::boss::DirectoryName::ctor, "nn_boss", "__ct__Q3_2nn4boss13DirectoryNameFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::DirectoryName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss13DirectoryNameCFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::DirectoryName::ctor, "nn_boss", "__ct__Q3_2nn4boss13DirectoryNameFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::DirectoryName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss13DirectoryNameCFv", LogType::NN_BOSS); // Account - cafeExportRegisterFunc(nn::boss::Account::ctor, "nn_boss", "__ct__Q3_2nn4boss7AccountFUi", LogType::Placeholder); - - - // storage - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss7StorageFv", nnBossStorageExport_ct); - //osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", nnBossStorageExport_initialize); - osLib_addFunction("nn_boss", "Exist__Q3_2nn4boss7StorageCFv", nnBossStorageExport_exist); - osLib_addFunction("nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", nnBossStorageExport_getDataList); - osLib_addFunction("nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", nnBossStorageExport_getDataList); - cafeExportRegisterFunc(nn::boss::Storage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcUiQ3_2nn4boss11StorageKind", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::Storage::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", LogType::Placeholder); - - // AlmightyStorage - cafeExportRegisterFunc(nn::boss::AlmightyStorage::ctor, "nn_boss", "__ct__Q3_2nn4boss15AlmightyStorageFv", LogType::Placeholder ); - cafeExportRegisterFunc(nn::boss::AlmightyStorage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", LogType::Placeholder ); + nn::boss::BossAccount::InitVTable(); + cafeExportRegisterFunc(nn::boss::BossAccount::ctor, "nn_boss", "__ct__Q3_2nn4boss7AccountFUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::BossAccount::dtor, "nn_boss", "__dt__Q3_2nn4boss7AccountFv", LogType::NN_BOSS); // AlmightyTask - cafeExportRegisterFunc(nn::boss::AlmightyTask::ctor, "nn_boss", "__ct__Q3_2nn4boss12AlmightyTaskFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::AlmightyTask::Initialize, "nn_boss", "Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", LogType::Placeholder); - // cafeExportRegisterFunc(nn::boss::AlmightyTask::dtor, "nn_boss", "__dt__Q3_2nn4boss12AlmightyTaskFv", LogType::Placeholder); + nn::boss::AlmightyTask::InitVTable(); + cafeExportRegisterFunc(nn::boss::AlmightyTask::ctor, "nn_boss", "__ct__Q3_2nn4boss12AlmightyTaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::AlmightyTask::Initialize, "nn_boss", "Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::AlmightyTask::dtor, "nn_boss", "__dt__Q3_2nn4boss12AlmightyTaskFv", LogType::NN_BOSS); + + // Storage + nn::boss::Storage::InitVTable(); + cafeExportRegisterFunc(nn::boss::Storage::ctor1, "nn_boss", "__ct__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::dtor, "nn_boss", "__dt__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Finalize, "nn_boss", "Finalize__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Exist, "nn_boss", "Exist__Q3_2nn4boss7StorageCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::GetDataList, "nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcUiQ3_2nn4boss11StorageKind", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", LogType::NN_BOSS); + + // AlmightyStorage + nn::boss::AlmightyStorage::InitVTable(); + cafeExportRegisterFunc(nn::boss::AlmightyStorage::ctor, "nn_boss", "__ct__Q3_2nn4boss15AlmightyStorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::AlmightyStorage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", LogType::NN_BOSS); // NsData - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss6NsDataFv", nnBossNsDataExport_ct); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", nnBossNsDataExport_initialize); - 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); - + nn::boss::NsData::InitVTable(); + cafeExportRegisterFunc(nn::boss::NsData::ctor, "nn_boss", "__ct__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::dtor, "nn_boss", "__dt__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Initialize, "nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFileWithHistory, "nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Exist, "nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::GetSize, "nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::GetCreatedTime, "nn_boss", "GetCreatedTime__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Read, "nn_boss", "Read__Q3_2nn4boss6NsDataFPvUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::ReadWithSizeOut, "nn_boss", "Read__Q3_2nn4boss6NsDataFPLPvUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Seek, "nn_boss", "Seek__Q3_2nn4boss6NsDataFLQ3_2nn4boss12PositionBase", LogType::NN_BOSS); } diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 6d596ac..058ab07 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -44,6 +44,7 @@ const std::map<LogType, std::string> g_logging_window_mapping {LogType::CoreinitThread, "Coreinit Thread"}, {LogType::NN_NFP, "nn::nfp"}, {LogType::NN_FP, "nn::fp"}, + {LogType::NN_BOSS, "nn::boss"}, {LogType::GX2, "GX2"}, {LogType::SoundAPI, "Audio"}, {LogType::InputAPI, "Input"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index bbffd16..fe74a6b 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -35,6 +35,7 @@ enum class LogType : sint32 NN_OLV = 23, NN_NFP = 13, NN_FP = 24, + NN_BOSS = 25, TextureReadback = 29, diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 023918b..4d2fb47 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2232,6 +2232,7 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("&Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); From 5c0d5a54acf4263631d855f4181462f97a26b426 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 02:39:25 +0200 Subject: [PATCH 052/130] vcpkg/linux: Avoid dependency on liblzma for now Use port of tiff which does not rely on lzma --- .../tiff/FindCMath.patch | 13 +++ .../tiff/portfile.cmake | 86 +++++++++++++++ .../vcpkg_overlay_ports_linux/tiff/usage | 9 ++ .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ++++++++++++++++++ .../vcpkg_overlay_ports_linux/tiff/vcpkg.json | 67 +++++++++++ 5 files changed, 279 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/usage create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch new file mode 100644 index 0000000..70654cf --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake +index ad92218..dd42aba 100644 +--- a/cmake/FindCMath.cmake ++++ b/cmake/FindCMath.cmake +@@ -31,7 +31,7 @@ include(CheckSymbolExists) + include(CheckLibraryExists) + + check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) +-find_library(CMath_LIBRARY NAMES m) ++find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) + + if(NOT CMath_HAVE_LIBC_POW) + set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake new file mode 100644 index 0000000..426d8af --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake @@ -0,0 +1,86 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO libtiff/libtiff + REF "v${VERSION}" + SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa + HEAD_REF master + PATCHES + FindCMath.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + cxx cxx + jpeg jpeg + jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG + libdeflate libdeflate + libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate + lzma lzma + lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma + tools tiff-tools + webp webp + webp CMAKE_REQUIRE_FIND_PACKAGE_WebP + zip zlib + zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB + zstd zstd + zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON + -Dtiff-docs=OFF + -Dtiff-contrib=OFF + -Dtiff-tests=OFF + -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. + -Djpeg12=OFF + -Dlerc=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON + -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON + -DZSTD_HAVE_DECOMPRESS_STREAM=ON + -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF + OPTIONS_DEBUG + -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. + MAYBE_UNUSED_VARIABLES + CMAKE_DISABLE_FIND_PACKAGE_GLUT + CMAKE_DISABLE_FIND_PACKAGE_OpenGL + ZSTD_HAVE_DECOMPRESS_STREAM +) + +vcpkg_cmake_install() + +# CMake config wasn't packaged in the past and is not yet usable now, +# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 +# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") + +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") +endif() +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +if ("tools" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES + tiffcp + tiffdump + tiffinfo + tiffset + tiffsplit + AUTO_CLEAN + ) +endif() + +vcpkg_copy_pdbs() +file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/usage b/dependencies/vcpkg_overlay_ports_linux/tiff/usage new file mode 100644 index 0000000..d47265b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/usage @@ -0,0 +1,9 @@ +tiff is compatible with built-in CMake targets: + + find_package(TIFF REQUIRED) + target_link_libraries(main PRIVATE TIFF::TIFF) + +tiff provides pkg-config modules: + + # Tag Image File Format (TIFF) library. + libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 0000000..1d04ec7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,104 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0057 NEW) +set(z_vcpkg_tiff_find_options "") +if("REQUIRED" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "REQUIRED") +endif() +if("QUIET" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "QUIET") +endif() + +_find_package(${ARGS}) + +if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + include(SelectLibraryConfigurations) + set(z_vcpkg_tiff_link_libraries "") + set(z_vcpkg_tiff_libraries "") + if("@webp@") + find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") + list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) + endif() + if("@lzma@") + find_package(LibLZMA ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") + list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) + endif() + if("@jpeg@") + find_package(JPEG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") + list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) + endif() + if("@zstd@") + find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") + if(TARGET zstd::libzstd_shared) + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) + if(WIN32) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) + endif() + get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) + get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(ZSTD) + if(NOT TARGET ZSTD::ZSTD) + add_library(ZSTD::ZSTD INTERFACE IMPORTED) + set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) + endif() + list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) + list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) + unset(z_vcpkg_tiff_zstd) + unset(z_vcpkg_tiff_zstd_configs) + unset(z_vcpkg_config) + unset(z_vcpkg_tiff_zstd_target) + endif() + if("@libdeflate@") + find_package(libdeflate ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_property "IMPORTED_LOCATION_") + if(TARGET libdeflate::libdeflate_shared) + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) + if(WIN32) + set(z_vcpkg_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) + endif() + get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) + get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(Z_VCPKG_DEFLATE) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") + list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) + unset(z_vcpkg_config) + unset(z_vcpkg_libdeflate_configs) + unset(z_vcpkg_libdeflate_target) + unset(z_vcpkg_property) + unset(Z_VCPKG_DEFLATE_FOUND) + endif() + if("@zlib@") + find_package(ZLIB ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") + list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) + endif() + if(UNIX) + list(APPEND z_vcpkg_tiff_link_libraries m) + list(APPEND z_vcpkg_tiff_libraries m) + endif() + + if(TARGET TIFF::TIFF) + set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) + endif() + list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) + unset(z_vcpkg_tiff_link_libraries) + unset(z_vcpkg_tiff_libraries) +endif() +unset(z_vcpkg_tiff_find_options) +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json new file mode 100644 index 0000000..9b36e1a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "tiff", + "version": "4.6.0", + "port-version": 2, + "description": "A library that supports the manipulation of TIFF image files", + "homepage": "https://libtiff.gitlab.io/libtiff/", + "license": "libtiff", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "jpeg", + "zip" + ], + "features": { + "cxx": { + "description": "Build C++ libtiffxx library" + }, + "jpeg": { + "description": "Support JPEG compression in TIFF image files", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "libdeflate": { + "description": "Use libdeflate for faster ZIP support", + "dependencies": [ + "libdeflate", + { + "name": "tiff", + "default-features": false, + "features": [ + "zip" + ] + } + ] + }, + "tools": { + "description": "Build tools" + }, + "webp": { + "description": "Support WEBP compression in TIFF image files", + "dependencies": [ + "libwebp" + ] + }, + "zip": { + "description": "Support ZIP/deflate compression in TIFF image files", + "dependencies": [ + "zlib" + ] + }, + "zstd": { + "description": "Support ZSTD compression in TIFF image files", + "dependencies": [ + "zstd" + ] + } + } +} From 85141f17f977157b91b72883d879f50b27f17dda Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:28:00 +0200 Subject: [PATCH 053/130] vcpkg/linux: Avoid dependency on libsystemd/liblzma libsystemd which is required by dbus has an optional dependency on liblzma and since we don't need it we can just strip it out of dbus --- .../dbus/cmake.dep.patch | 15 ++++ .../dbus/getpeereid.patch | 26 ++++++ .../dbus/libsystemd.patch | 15 ++++ .../dbus/pkgconfig.patch | 21 +++++ .../dbus/portfile.cmake | 88 +++++++++++++++++++ .../vcpkg_overlay_ports_linux/dbus/vcpkg.json | 30 +++++++ 6 files changed, 195 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch new file mode 100644 index 0000000..ac827f0 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch @@ -0,0 +1,15 @@ +diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt +index 8cde1ffe0..d4d09f223 100644 +--- a/tools/CMakeLists.txt ++++ b/tools/CMakeLists.txt +@@ -91,7 +91,9 @@ endif() + add_executable(dbus-launch ${dbus_launch_SOURCES}) + target_link_libraries(dbus-launch ${DBUS_LIBRARIES}) + if(DBUS_BUILD_X11) +- target_link_libraries(dbus-launch ${X11_LIBRARIES} ) ++ find_package(Threads REQUIRED) ++ target_link_libraries(dbus-launch ${X11_LIBRARIES} ${X11_xcb_LIB} ${X11_Xau_LIB} ${X11_Xdmcp_LIB} Threads::Threads) ++ target_include_directories(dbus-launch PRIVATE ${X11_INCLUDE_DIR}) + endif() + install(TARGETS dbus-launch ${INSTALL_TARGETS_DEFAULT_ARGS}) + diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch new file mode 100644 index 0000000..5cd2309 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch @@ -0,0 +1,26 @@ +diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake +index b7f3702..e2336ba 100644 +--- a/cmake/ConfigureChecks.cmake ++++ b/cmake/ConfigureChecks.cmake +@@ -51,6 +51,7 @@ check_symbol_exists(closefrom "unistd.h" HAVE_CLOSEFROM) # + check_symbol_exists(environ "unistd.h" HAVE_DECL_ENVIRON) + check_symbol_exists(fstatfs "sys/vfs.h" HAVE_FSTATFS) + check_symbol_exists(getgrouplist "grp.h" HAVE_GETGROUPLIST) # dbus-sysdeps.c ++check_symbol_exists(getpeereid "sys/types.h;unistd.h" HAVE_GETPEEREID) # dbus-sysdeps.c, + check_symbol_exists(getpeerucred "ucred.h" HAVE_GETPEERUCRED) # dbus-sysdeps.c, dbus-sysdeps-win.c + check_symbol_exists(getpwnam_r "errno.h;pwd.h" HAVE_GETPWNAM_R) # dbus-sysdeps-util-unix.c + check_symbol_exists(getrandom "sys/random.h" HAVE_GETRANDOM) +diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake +index 77fc19c..2f25643 100644 +--- a/cmake/config.h.cmake ++++ b/cmake/config.h.cmake +@@ -140,6 +140,9 @@ + /* Define to 1 if you have getgrouplist */ + #cmakedefine HAVE_GETGROUPLIST 1 + ++/* Define to 1 if you have getpeereid */ ++#cmakedefine HAVE_GETPEEREID 1 ++ + /* Define to 1 if you have getpeerucred */ + #cmakedefine HAVE_GETPEERUCRED 1 + diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch new file mode 100644 index 0000000..74193dc --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch @@ -0,0 +1,15 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index d3ec71b..932066a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -141,6 +141,10 @@ if(DBUS_LINUX) + if(ENABLE_SYSTEMD AND SYSTEMD_FOUND) + set(DBUS_BUS_ENABLE_SYSTEMD ON) + set(HAVE_SYSTEMD ${SYSTEMD_FOUND}) ++ pkg_check_modules(SYSTEMD libsystemd IMPORTED_TARGET) ++ set(SYSTEMD_LIBRARIES PkgConfig::SYSTEMD CACHE INTERNAL "") ++ else() ++ set(SYSTEMD_LIBRARIES "" CACHE INTERNAL "") + endif() + option(ENABLE_USER_SESSION "enable user-session semantics for session bus under systemd" OFF) + set(DBUS_ENABLE_USER_SESSION ${ENABLE_USER_SESSION}) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch new file mode 100644 index 0000000..6358148 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch @@ -0,0 +1,21 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index caef738..b878f42 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -724,11 +724,11 @@ add_custom_target(help-options + # + if(DBUS_ENABLE_PKGCONFIG) + set(PLATFORM_LIBS pthread ${LIBRT}) +- if(PKG_CONFIG_FOUND) +- # convert lists of link libraries into -lstdc++ -lm etc.. +- foreach(LIB ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) +- set(LIBDBUS_LIBS "${LIBDBUS_LIBS} -l${LIB}") +- endforeach() ++ if(1) ++ set(LIBDBUS_LIBS "${CMAKE_THREAD_LIBS_INIT}") ++ if(LIBRT) ++ string(APPEND LIBDBUS_LIBS " -lrt") ++ endif() + set(original_prefix "${CMAKE_INSTALL_PREFIX}") + if(DBUS_RELOCATABLE) + set(pkgconfig_prefix "\${pcfiledir}/../..") diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake new file mode 100644 index 0000000..56c7e18 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake @@ -0,0 +1,88 @@ +vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) + +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.freedesktop.org/ + OUT_SOURCE_PATH SOURCE_PATH + REPO dbus/dbus + REF "dbus-${VERSION}" + SHA512 8e476b408514e6540c36beb84e8025827c22cda8958b6eb74d22b99c64765eb3cd5a6502aea546e3e5f0534039857b37edee89c659acef40e7cab0939947d4af + HEAD_REF master + PATCHES + cmake.dep.patch + pkgconfig.patch + getpeereid.patch # missing check from configure.ac + libsystemd.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS options + FEATURES + systemd ENABLE_SYSTEMD + x11 DBUS_BUILD_X11 + x11 CMAKE_REQUIRE_FIND_PACKAGE_X11 +) + +unset(ENV{DBUSDIR}) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DDBUS_BUILD_TESTS=OFF + -DDBUS_ENABLE_DOXYGEN_DOCS=OFF + -DDBUS_ENABLE_XML_DOCS=OFF + -DDBUS_INSTALL_SYSTEM_LIBS=OFF + #-DDBUS_SERVICE=ON + -DDBUS_WITH_GLIB=OFF + -DTHREADS_PREFER_PTHREAD_FLAG=ON + -DXSLTPROC_EXECUTABLE=FALSE + "-DCMAKE_INSTALL_SYSCONFDIR=${CURRENT_PACKAGES_DIR}/etc/${PORT}" + "-DWITH_SYSTEMD_SYSTEMUNITDIR=lib/systemd/system" + "-DWITH_SYSTEMD_USERUNITDIR=lib/systemd/user" + ${options} + OPTIONS_RELEASE + -DDBUS_DISABLE_ASSERT=OFF + -DDBUS_ENABLE_STATS=OFF + -DDBUS_ENABLE_VERBOSE_MODE=OFF + MAYBE_UNUSED_VARIABLES + DBUS_BUILD_X11 + DBUS_WITH_GLIB + ENABLE_SYSTEMD + THREADS_PREFER_PTHREAD_FLAG + WITH_SYSTEMD_SYSTEMUNITDIR + WITH_SYSTEMD_USERUNITDIR +) +vcpkg_cmake_install() +vcpkg_copy_pdbs() +vcpkg_cmake_config_fixup(PACKAGE_NAME "DBus1" CONFIG_PATH "lib/cmake/DBus1") +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/debug/var/" + "${CURRENT_PACKAGES_DIR}/etc" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/services" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/session.d" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system-services" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.d" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" + "${CURRENT_PACKAGES_DIR}/share/doc" + "${CURRENT_PACKAGES_DIR}/var" +) + +vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.conf</include>" "") +vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<includedir>${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.d</includedir>" "") +vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session-local.conf</include>" "") + +set(TOOLS daemon launch monitor run-session send test-tool update-activation-environment) +if(VCPKG_TARGET_IS_WINDOWS) + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") + file(RENAME "${CURRENT_PACKAGES_DIR}/bin/dbus-env.bat" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat" "${CURRENT_PACKAGES_DIR}" "%~dp0/../..") +else() + list(APPEND TOOLS cleanup-sockets uuidgen) +endif() +list(TRANSFORM TOOLS PREPEND "dbus-" ) +vcpkg_copy_tools(TOOL_NAMES ${TOOLS} AUTO_CLEAN) + +file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json new file mode 100644 index 0000000..853dff0 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json @@ -0,0 +1,30 @@ +{ + "name": "dbus", + "version": "1.15.8", + "port-version": 2, + "description": "D-Bus specification and reference implementation, including libdbus and dbus-daemon", + "homepage": "https://gitlab.freedesktop.org/dbus/dbus", + "license": "AFL-2.1 OR GPL-2.0-or-later", + "supports": "!uwp & !staticcrt", + "dependencies": [ + "expat", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + ], + "features": { + "x11": { + "description": "Build with X11 autolaunch support", + "dependencies": [ + "libx11" + ] + } + } +} From 075eac626b162dd2e23ae337860f4717bf3041fe Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:13:19 +0200 Subject: [PATCH 054/130] ELF: Fix crash due to not allocating recompiler ranges (#1154) --- src/Cafe/OS/RPL/elf.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Cafe/OS/RPL/elf.cpp b/src/Cafe/OS/RPL/elf.cpp index c61afb2..7ee3ba4 100644 --- a/src/Cafe/OS/RPL/elf.cpp +++ b/src/Cafe/OS/RPL/elf.cpp @@ -50,6 +50,10 @@ typedef struct static_assert(sizeof(elfSectionEntry_t) == 0x28, ""); +#define PF_X (1 << 0) /* Segment is executable */ +#define PF_W (1 << 1) /* Segment is writable */ +#define PF_R (1 << 2) /* Segment is readable */ + // Map elf into memory uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) { @@ -68,6 +72,7 @@ uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) uint32 shSize = (uint32)sectionTable[i].shSize; uint32 shOffset = (uint32)sectionTable[i].shOffset; uint32 shType = (uint32)sectionTable[i].shType; + uint32 shFlags = (uint32)sectionTable[i].shFlags; if (shOffset > (uint32)size) { @@ -89,6 +94,8 @@ uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) } // SHT_NOBITS } + if((shFlags & PF_X) > 0) + PPCRecompiler_allocateRange(shAddr, shSize); } return header->entrypoint; } From fde7230191a07c40b6811eb3275b5e9c68af6cd3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:25:13 +0200 Subject: [PATCH 055/130] vcpkg/windows/mac: Avoid dependency on liblzma via tiff --- .../vcpkg_overlay_ports/tiff/FindCMath.patch | 13 +++ .../vcpkg_overlay_ports/tiff/portfile.cmake | 86 +++++++++++++++ dependencies/vcpkg_overlay_ports/tiff/usage | 9 ++ .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ++++++++++++++++++ .../vcpkg_overlay_ports/tiff/vcpkg.json | 67 +++++++++++ .../tiff/FindCMath.patch | 13 +++ .../tiff/portfile.cmake | 86 +++++++++++++++ .../vcpkg_overlay_ports_mac/tiff/usage | 9 ++ .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ++++++++++++++++++ .../vcpkg_overlay_ports_mac/tiff/vcpkg.json | 67 +++++++++++ 10 files changed, 558 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch create mode 100644 dependencies/vcpkg_overlay_ports/tiff/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports/tiff/usage create mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in create mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg.json create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/usage create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch new file mode 100644 index 0000000..70654cf --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake +index ad92218..dd42aba 100644 +--- a/cmake/FindCMath.cmake ++++ b/cmake/FindCMath.cmake +@@ -31,7 +31,7 @@ include(CheckSymbolExists) + include(CheckLibraryExists) + + check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) +-find_library(CMath_LIBRARY NAMES m) ++find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) + + if(NOT CMath_HAVE_LIBC_POW) + set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake new file mode 100644 index 0000000..426d8af --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake @@ -0,0 +1,86 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO libtiff/libtiff + REF "v${VERSION}" + SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa + HEAD_REF master + PATCHES + FindCMath.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + cxx cxx + jpeg jpeg + jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG + libdeflate libdeflate + libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate + lzma lzma + lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma + tools tiff-tools + webp webp + webp CMAKE_REQUIRE_FIND_PACKAGE_WebP + zip zlib + zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB + zstd zstd + zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON + -Dtiff-docs=OFF + -Dtiff-contrib=OFF + -Dtiff-tests=OFF + -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. + -Djpeg12=OFF + -Dlerc=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON + -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON + -DZSTD_HAVE_DECOMPRESS_STREAM=ON + -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF + OPTIONS_DEBUG + -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. + MAYBE_UNUSED_VARIABLES + CMAKE_DISABLE_FIND_PACKAGE_GLUT + CMAKE_DISABLE_FIND_PACKAGE_OpenGL + ZSTD_HAVE_DECOMPRESS_STREAM +) + +vcpkg_cmake_install() + +# CMake config wasn't packaged in the past and is not yet usable now, +# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 +# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") + +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") +endif() +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +if ("tools" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES + tiffcp + tiffdump + tiffinfo + tiffset + tiffsplit + AUTO_CLEAN + ) +endif() + +vcpkg_copy_pdbs() +file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports/tiff/usage b/dependencies/vcpkg_overlay_ports/tiff/usage new file mode 100644 index 0000000..d47265b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/usage @@ -0,0 +1,9 @@ +tiff is compatible with built-in CMake targets: + + find_package(TIFF REQUIRED) + target_link_libraries(main PRIVATE TIFF::TIFF) + +tiff provides pkg-config modules: + + # Tag Image File Format (TIFF) library. + libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 0000000..1d04ec7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,104 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0057 NEW) +set(z_vcpkg_tiff_find_options "") +if("REQUIRED" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "REQUIRED") +endif() +if("QUIET" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "QUIET") +endif() + +_find_package(${ARGS}) + +if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + include(SelectLibraryConfigurations) + set(z_vcpkg_tiff_link_libraries "") + set(z_vcpkg_tiff_libraries "") + if("@webp@") + find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") + list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) + endif() + if("@lzma@") + find_package(LibLZMA ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") + list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) + endif() + if("@jpeg@") + find_package(JPEG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") + list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) + endif() + if("@zstd@") + find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") + if(TARGET zstd::libzstd_shared) + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) + if(WIN32) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) + endif() + get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) + get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(ZSTD) + if(NOT TARGET ZSTD::ZSTD) + add_library(ZSTD::ZSTD INTERFACE IMPORTED) + set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) + endif() + list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) + list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) + unset(z_vcpkg_tiff_zstd) + unset(z_vcpkg_tiff_zstd_configs) + unset(z_vcpkg_config) + unset(z_vcpkg_tiff_zstd_target) + endif() + if("@libdeflate@") + find_package(libdeflate ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_property "IMPORTED_LOCATION_") + if(TARGET libdeflate::libdeflate_shared) + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) + if(WIN32) + set(z_vcpkg_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) + endif() + get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) + get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(Z_VCPKG_DEFLATE) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") + list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) + unset(z_vcpkg_config) + unset(z_vcpkg_libdeflate_configs) + unset(z_vcpkg_libdeflate_target) + unset(z_vcpkg_property) + unset(Z_VCPKG_DEFLATE_FOUND) + endif() + if("@zlib@") + find_package(ZLIB ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") + list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) + endif() + if(UNIX) + list(APPEND z_vcpkg_tiff_link_libraries m) + list(APPEND z_vcpkg_tiff_libraries m) + endif() + + if(TARGET TIFF::TIFF) + set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) + endif() + list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) + unset(z_vcpkg_tiff_link_libraries) + unset(z_vcpkg_tiff_libraries) +endif() +unset(z_vcpkg_tiff_find_options) +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json new file mode 100644 index 0000000..9b36e1a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "tiff", + "version": "4.6.0", + "port-version": 2, + "description": "A library that supports the manipulation of TIFF image files", + "homepage": "https://libtiff.gitlab.io/libtiff/", + "license": "libtiff", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "jpeg", + "zip" + ], + "features": { + "cxx": { + "description": "Build C++ libtiffxx library" + }, + "jpeg": { + "description": "Support JPEG compression in TIFF image files", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "libdeflate": { + "description": "Use libdeflate for faster ZIP support", + "dependencies": [ + "libdeflate", + { + "name": "tiff", + "default-features": false, + "features": [ + "zip" + ] + } + ] + }, + "tools": { + "description": "Build tools" + }, + "webp": { + "description": "Support WEBP compression in TIFF image files", + "dependencies": [ + "libwebp" + ] + }, + "zip": { + "description": "Support ZIP/deflate compression in TIFF image files", + "dependencies": [ + "zlib" + ] + }, + "zstd": { + "description": "Support ZSTD compression in TIFF image files", + "dependencies": [ + "zstd" + ] + } + } +} diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch new file mode 100644 index 0000000..70654cf --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake +index ad92218..dd42aba 100644 +--- a/cmake/FindCMath.cmake ++++ b/cmake/FindCMath.cmake +@@ -31,7 +31,7 @@ include(CheckSymbolExists) + include(CheckLibraryExists) + + check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) +-find_library(CMath_LIBRARY NAMES m) ++find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) + + if(NOT CMath_HAVE_LIBC_POW) + set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake new file mode 100644 index 0000000..426d8af --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake @@ -0,0 +1,86 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO libtiff/libtiff + REF "v${VERSION}" + SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa + HEAD_REF master + PATCHES + FindCMath.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + cxx cxx + jpeg jpeg + jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG + libdeflate libdeflate + libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate + lzma lzma + lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma + tools tiff-tools + webp webp + webp CMAKE_REQUIRE_FIND_PACKAGE_WebP + zip zlib + zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB + zstd zstd + zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON + -Dtiff-docs=OFF + -Dtiff-contrib=OFF + -Dtiff-tests=OFF + -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. + -Djpeg12=OFF + -Dlerc=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON + -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON + -DZSTD_HAVE_DECOMPRESS_STREAM=ON + -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF + OPTIONS_DEBUG + -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. + MAYBE_UNUSED_VARIABLES + CMAKE_DISABLE_FIND_PACKAGE_GLUT + CMAKE_DISABLE_FIND_PACKAGE_OpenGL + ZSTD_HAVE_DECOMPRESS_STREAM +) + +vcpkg_cmake_install() + +# CMake config wasn't packaged in the past and is not yet usable now, +# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 +# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") + +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") +endif() +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +if ("tools" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES + tiffcp + tiffdump + tiffinfo + tiffset + tiffsplit + AUTO_CLEAN + ) +endif() + +vcpkg_copy_pdbs() +file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/usage b/dependencies/vcpkg_overlay_ports_mac/tiff/usage new file mode 100644 index 0000000..d47265b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/usage @@ -0,0 +1,9 @@ +tiff is compatible with built-in CMake targets: + + find_package(TIFF REQUIRED) + target_link_libraries(main PRIVATE TIFF::TIFF) + +tiff provides pkg-config modules: + + # Tag Image File Format (TIFF) library. + libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 0000000..1d04ec7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,104 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0057 NEW) +set(z_vcpkg_tiff_find_options "") +if("REQUIRED" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "REQUIRED") +endif() +if("QUIET" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "QUIET") +endif() + +_find_package(${ARGS}) + +if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + include(SelectLibraryConfigurations) + set(z_vcpkg_tiff_link_libraries "") + set(z_vcpkg_tiff_libraries "") + if("@webp@") + find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") + list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) + endif() + if("@lzma@") + find_package(LibLZMA ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") + list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) + endif() + if("@jpeg@") + find_package(JPEG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") + list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) + endif() + if("@zstd@") + find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") + if(TARGET zstd::libzstd_shared) + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) + if(WIN32) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) + endif() + get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) + get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(ZSTD) + if(NOT TARGET ZSTD::ZSTD) + add_library(ZSTD::ZSTD INTERFACE IMPORTED) + set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) + endif() + list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) + list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) + unset(z_vcpkg_tiff_zstd) + unset(z_vcpkg_tiff_zstd_configs) + unset(z_vcpkg_config) + unset(z_vcpkg_tiff_zstd_target) + endif() + if("@libdeflate@") + find_package(libdeflate ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_property "IMPORTED_LOCATION_") + if(TARGET libdeflate::libdeflate_shared) + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) + if(WIN32) + set(z_vcpkg_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) + endif() + get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) + get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(Z_VCPKG_DEFLATE) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") + list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) + unset(z_vcpkg_config) + unset(z_vcpkg_libdeflate_configs) + unset(z_vcpkg_libdeflate_target) + unset(z_vcpkg_property) + unset(Z_VCPKG_DEFLATE_FOUND) + endif() + if("@zlib@") + find_package(ZLIB ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") + list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) + endif() + if(UNIX) + list(APPEND z_vcpkg_tiff_link_libraries m) + list(APPEND z_vcpkg_tiff_libraries m) + endif() + + if(TARGET TIFF::TIFF) + set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) + endif() + list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) + unset(z_vcpkg_tiff_link_libraries) + unset(z_vcpkg_tiff_libraries) +endif() +unset(z_vcpkg_tiff_find_options) +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json new file mode 100644 index 0000000..9b36e1a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "tiff", + "version": "4.6.0", + "port-version": 2, + "description": "A library that supports the manipulation of TIFF image files", + "homepage": "https://libtiff.gitlab.io/libtiff/", + "license": "libtiff", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "jpeg", + "zip" + ], + "features": { + "cxx": { + "description": "Build C++ libtiffxx library" + }, + "jpeg": { + "description": "Support JPEG compression in TIFF image files", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "libdeflate": { + "description": "Use libdeflate for faster ZIP support", + "dependencies": [ + "libdeflate", + { + "name": "tiff", + "default-features": false, + "features": [ + "zip" + ] + } + ] + }, + "tools": { + "description": "Build tools" + }, + "webp": { + "description": "Support WEBP compression in TIFF image files", + "dependencies": [ + "libwebp" + ] + }, + "zip": { + "description": "Support ZIP/deflate compression in TIFF image files", + "dependencies": [ + "zlib" + ] + }, + "zstd": { + "description": "Support ZSTD compression in TIFF image files", + "dependencies": [ + "zstd" + ] + } + } +} From 74e8d205b07247b851c716ad285d938f4bc277b5 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:18:30 +0200 Subject: [PATCH 056/130] coreinit: Handle SD mounting permission in FSGetMountSource One Piece requires this to not get stuck in an infinite loop on boot. This also sets up initial infrastructure for handling cos.xml permissions --- src/Cafe/CafeSystem.cpp | 21 +++++ src/Cafe/CafeSystem.h | 4 + src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 10 +++ src/Cafe/TitleList/TitleInfo.cpp | 43 +++++++-- src/Cafe/TitleList/TitleInfo.h | 101 ++++++++++++++++++---- 5 files changed, 157 insertions(+), 22 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 75cb111..bde1611 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -914,6 +914,27 @@ namespace CafeSystem return sGameInfo_ForegroundTitle.GetBase().GetArgStr(); } + CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group) + { + if (sLaunchModeIsStandalone) + return CosCapabilityBits::All; + auto& update = sGameInfo_ForegroundTitle.GetUpdate(); + if (update.IsValid()) + { + ParsedCosXml* cosXml = update.GetCosInfo(); + if (cosXml) + return cosXml->GetCapabilityBits(group); + } + auto& base = sGameInfo_ForegroundTitle.GetBase(); + if(base.IsValid()) + { + ParsedCosXml* cosXml = base.GetCosInfo(); + if (cosXml) + return cosXml->GetCapabilityBits(group); + } + return CosCapabilityBits::All; + } + // when switching titles custom parameters can be passed, returns true if override args are used bool GetOverrideArgStr(std::vector<std::string>& args) { diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index 336c2f4..c4043a5 100644 --- a/src/Cafe/CafeSystem.h +++ b/src/Cafe/CafeSystem.h @@ -4,6 +4,9 @@ #include "Cafe/TitleList/TitleId.h" #include "config/CemuConfig.h" +enum class CosCapabilityBits : uint64; +enum class CosCapabilityGroup : uint32; + namespace CafeSystem { class SystemImplementation @@ -41,6 +44,7 @@ namespace CafeSystem std::string GetForegroundTitleName(); std::string GetForegroundTitleArgStr(); uint32 GetForegroundTitleOlvAccesskey(); + CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group); void ShutdownTitle(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 1e6eb92..a007f5e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -11,6 +11,8 @@ #include "coreinit_IPC.h" #include "Cafe/Filesystem/fsc.h" #include "coreinit_IPCBuf.h" +#include "Cafe/CafeSystem.h" +#include "Cafe/TitleList/TitleInfo.h" #define FS_CB_PLACEHOLDER_FINISHCMD (MPTR)(0xF122330E) @@ -94,6 +96,14 @@ namespace coreinit // so we can just hard code it. Other mount types are not (yet) supported. if (mountSourceType == MOUNT_TYPE::SD) { + // check for SD card permissions (from cos.xml) + // One Piece relies on failing here, otherwise it will call FSGetMountSource in an infinite loop + CosCapabilityBitsFS perms = static_cast<CosCapabilityBitsFS>(CafeSystem::GetForegroundTitleCosCapabilities(CosCapabilityGroup::FS)); + if(!HAS_FLAG(perms, CosCapabilityBitsFS::SDCARD_MOUNT)) + { + cemuLog_logOnce(LogType::Force, "Title is trying to access SD card mount info without having SD card permissions. This may not be a bug"); + return FS_RESULT::END_ITERATION; + } mountSourceInfo->sourceType = 0; strcpy(mountSourceInfo->path, "/sd"); return FS_RESULT::SUCCESS; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index d23e1d0..6d21929 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -1,13 +1,11 @@ #include "TitleInfo.h" - #include "Cafe/Filesystem/fscDeviceHostFS.h" #include "Cafe/Filesystem/FST/FST.h" - #include "pugixml.hpp" #include "Common/FileStream.h" - #include <zarchive/zarchivereader.h> #include "config/ActiveSettings.h" +#include "util/helpers/helpers.h" // detect format by reading file header/footer CafeTitleFileType DetermineCafeSystemFileType(fs::path filePath) @@ -709,10 +707,41 @@ std::string TitleInfo::GetInstallPath() const { TitleId titleId = GetAppTitleId(); TitleIdParser tip(titleId); - std::string tmp; + std::string tmp; if (tip.IsSystemTitle()) - tmp = fmt::format("sys/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - else - tmp = fmt::format("usr/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + tmp = fmt::format("sys/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + else + tmp = fmt::format("usr/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); return tmp; } + +ParsedCosXml* ParsedCosXml::Parse(uint8* xmlData, size_t xmlLen) +{ + pugi::xml_document app_doc; + if (!app_doc.load_buffer_inplace(xmlData, xmlLen)) + return nullptr; + + const auto root = app_doc.child("app"); + if (!root) + return nullptr; + + ParsedCosXml* parsedCos = new ParsedCosXml(); + + auto node = root.child("argstr"); + if (node) + parsedCos->argstr = node.text().as_string(); + + // parse permissions + auto permissionsNode = root.child("permissions"); + for(uint32 permissionIndex = 0; permissionIndex < 19; ++permissionIndex) + { + std::string permissionName = fmt::format("p{}", permissionIndex); + auto permissionNode = permissionsNode.child(permissionName.c_str()); + if (!permissionNode) + break; + parsedCos->permissions[permissionIndex].group = static_cast<CosCapabilityGroup>(ConvertString<uint32>(permissionNode.child("group").text().as_string(), 10)); + parsedCos->permissions[permissionIndex].mask = static_cast<CosCapabilityBits>(ConvertString<uint64>(permissionNode.child("mask").text().as_string(), 16)); + } + + return parsedCos; +} \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index eca6624..e9347db 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -26,29 +26,95 @@ struct ParsedAppXml uint32 sdk_version; }; +enum class CosCapabilityGroup : uint32 +{ + None = 0, + BSP = 1, + DK = 3, + USB = 9, + UHS = 12, + FS = 11, + MCP = 13, + NIM = 14, + ACT = 15, + FPD = 16, + BOSS = 17, + ACP = 18, + PDM = 19, + AC = 20, + NDM = 21, + NSEC = 22 +}; + +enum class CosCapabilityBits : uint64 +{ + All = 0xFFFFFFFFFFFFFFFFull +}; + +enum class CosCapabilityBitsFS : uint64 +{ + ODD_READ = (1llu << 0), + ODD_WRITE = (1llu << 1), + ODD_RAW_OPEN = (1llu << 2), + ODD_MOUNT = (1llu << 3), + SLCCMPT_READ = (1llu << 4), + SLCCMPT_WRITE = (1llu << 5), + SLCCMPT_RAW_OPEN = (1llu << 6), + SLCCMPT_MOUNT = (1llu << 7), + SLC_READ = (1llu << 8), + SLC_WRITE = (1llu << 9), + SLC_RAW_OPEN = (1llu << 10), + SLC_MOUNT = (1llu << 11), + MLC_READ = (1llu << 12), + MLC_WRITE = (1llu << 13), + MLC_RAW_OPEN = (1llu << 14), + MLC_MOUNT = (1llu << 15), + SDCARD_READ = (1llu << 16), + SDCARD_WRITE = (1llu << 17), + SDCARD_RAW_OPEN = (1llu << 18), + SDCARD_MOUNT = (1llu << 19), + HFIO_READ = (1llu << 20), + HFIO_WRITE = (1llu << 21), + HFIO_RAW_OPEN = (1llu << 22), + HFIO_MOUNT = (1llu << 23), + RAMDISK_READ = (1llu << 24), + RAMDISK_WRITE = (1llu << 25), + RAMDISK_RAW_OPEN = (1llu << 26), + RAMDISK_MOUNT = (1llu << 27), + USB_READ = (1llu << 28), + USB_WRITE = (1llu << 29), + USB_RAW_OPEN = (1llu << 30), + USB_MOUNT = (1llu << 31), + OTHER_READ = (1llu << 32), + OTHER_WRITE = (1llu << 33), + OTHER_RAW_OPEN = (1llu << 34), + OTHER_MOUNT = (1llu << 35) +}; +ENABLE_BITMASK_OPERATORS(CosCapabilityBitsFS); + struct ParsedCosXml { + public: + std::string argstr; - static ParsedCosXml* Parse(uint8* xmlData, size_t xmlLen) + struct Permission { - pugi::xml_document app_doc; - if (!app_doc.load_buffer_inplace(xmlData, xmlLen)) - return nullptr; + CosCapabilityGroup group{CosCapabilityGroup::None}; + CosCapabilityBits mask{CosCapabilityBits::All}; + }; + Permission permissions[19]{}; - const auto root = app_doc.child("app"); - if (!root) - return nullptr; + static ParsedCosXml* Parse(uint8* xmlData, size_t xmlLen); - ParsedCosXml* parsedCos = new ParsedCosXml(); - - for (const auto& child : root.children()) + CosCapabilityBits GetCapabilityBits(CosCapabilityGroup group) const + { + for (const auto& perm : permissions) { - std::string_view name = child.name(); - if (name == "argstr") - parsedCos->argstr = child.text().as_string(); + if (perm.group == group) + return perm.mask; } - return parsedCos; + return CosCapabilityBits::All; } }; @@ -151,7 +217,7 @@ public: // cos.xml std::string GetArgStr() const; - // meta.xml also contains a version which seems to match the one from app.xml + // meta.xml also contains a version field which seems to match the one from app.xml // the titleId in meta.xml seems to be the title id of the base game for updates specifically. For AOC content it's the AOC's titleId TitleIdParser::TITLE_TYPE GetTitleType(); @@ -160,6 +226,11 @@ public: return m_parsedMetaXml; } + ParsedCosXml* GetCosInfo() + { + return m_parsedCosXml; + } + std::string GetPrintPath() const; // formatted path including type and WUA subpath. Intended for logging and user-facing information std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..." From efbf712305fe59081d90d566e0ec310ae68c969c Mon Sep 17 00:00:00 2001 From: Maschell <Maschell@gmx.de> Date: Mon, 8 Apr 2024 19:15:49 +0200 Subject: [PATCH 057/130] nn_sl: Stub GetDefaultWhiteListAccessor__Q2_2nn2slFv to avoid crash in Wii U Menu when an online account is used (#1159) --- src/Cafe/CMakeLists.txt | 2 + src/Cafe/OS/common/OSCommon.cpp | 2 + src/Cafe/OS/libs/nn_sl/nn_sl.cpp | 115 +++++++++++++++++++++++++++++++ src/Cafe/OS/libs/nn_sl/nn_sl.h | 1 + src/Cemu/Logging/CemuLogging.h | 1 + 5 files changed, 121 insertions(+) create mode 100644 src/Cafe/OS/libs/nn_sl/nn_sl.cpp create mode 100644 src/Cafe/OS/libs/nn_sl/nn_sl.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 2085378..d64a599 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -404,6 +404,8 @@ add_library(CemuCafe OS/libs/nn_ndm/nn_ndm.h OS/libs/nn_spm/nn_spm.cpp OS/libs/nn_spm/nn_spm.h + OS/libs/nn_sl/nn_sl.cpp + OS/libs/nn_sl/nn_sl.h OS/libs/nn_nfp/AmiiboCrypto.h OS/libs/nn_nfp/nn_nfp.cpp OS/libs/nn_nfp/nn_nfp.h diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp index 5aedd19..a441002 100644 --- a/src/Cafe/OS/common/OSCommon.cpp +++ b/src/Cafe/OS/common/OSCommon.cpp @@ -13,6 +13,7 @@ #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_sl/nn_sl.h" #include "Cafe/OS/libs/nn_fp/nn_fp.h" #include "Cafe/OS/libs/nn_olv/nn_olv.h" #include "Cafe/OS/libs/nn_idbe/nn_idbe.h" @@ -208,6 +209,7 @@ void osLib_load() nn::ndm::load(); nn::spm::load(); nn::save::load(); + nnSL_load(); nsysnet_load(); nn::fp::load(); nn::olv::load(); diff --git a/src/Cafe/OS/libs/nn_sl/nn_sl.cpp b/src/Cafe/OS/libs/nn_sl/nn_sl.cpp new file mode 100644 index 0000000..b25a91b --- /dev/null +++ b/src/Cafe/OS/libs/nn_sl/nn_sl.cpp @@ -0,0 +1,115 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" + +namespace nn +{ + typedef uint32 Result; + namespace sl + { + struct VTableEntry + { + uint16be offsetA{0}; + uint16be offsetB{0}; + MEMPTR<void> ptr; + }; + static_assert(sizeof(VTableEntry) == 8); + + constexpr uint32 SL_MEM_MAGIC = 0xCAFE4321; + +#define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) + + template<typename T> + MEMPTR<T> sl_new() + { + uint32 objSize = sizeof(T); + uint32be* basePtr = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(objSize + 8, 0x8); + basePtr[0] = SL_MEM_MAGIC; + basePtr[1] = objSize; + return (T*)(basePtr + 2); + } + + void sl_delete(MEMPTR<void> mem) + { + if (!mem) + return; + uint32be* basePtr = (uint32be*)mem.GetPtr() - 2; + if (basePtr[0] != SL_MEM_MAGIC) + { + cemuLog_log(LogType::Force, "nn_sl: Detected memory corruption"); + cemu_assert_suspicious(); + } + coreinit::_weak_MEMFreeToDefaultHeap(basePtr); + } + +#pragma pack(1) + struct WhiteList + { + uint32be titleTypes[50]; + uint32be titleTypesCount; + uint32be padding; + uint64be titleIds[50]; + uint32be titleIdCount; + }; + static_assert(sizeof(WhiteList) == 0x264); +#pragma pack() + + struct WhiteListAccessor + { + MEMPTR<void> vTablePtr{}; // 0x00 + + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + VTableEntry get; + }; + static inline SysAllocator<VTable> s_titleVTable; + + static WhiteListAccessor* ctor(WhiteListAccessor* _this) + { + if (!_this) + _this = sl_new<WhiteListAccessor>(); + *_this = {}; + _this->vTablePtr = s_titleVTable; + return _this; + } + + static void dtor(WhiteListAccessor* _this, uint32 options) + { + if (_this && (options & 1)) + sl_delete(_this); + } + + static void Get(WhiteListAccessor* _this, nn::sl::WhiteList* outWhiteList) + { + *outWhiteList = {}; + } + + static void InitVTable() + { + s_titleVTable->rtti.ptr = nullptr; // todo + s_titleVTable->dtor.ptr = DTOR_WRAPPER(WhiteListAccessor); + s_titleVTable->get.ptr = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { Get(MEMPTR<WhiteListAccessor>(hCPU->gpr[3]), MEMPTR<WhiteList>(hCPU->gpr[4])); osLib_returnFromFunction(hCPU, 0); }); + } + }; + static_assert(sizeof(WhiteListAccessor) == 0x04); + + SysAllocator<WhiteListAccessor> s_defaultWhiteListAccessor; + + WhiteListAccessor* GetDefaultWhiteListAccessor() + { + return s_defaultWhiteListAccessor; + } + } // namespace sl +} // namespace nn + +void nnSL_load() +{ + nn::sl::WhiteListAccessor::InitVTable(); + nn::sl::WhiteListAccessor::ctor(nn::sl::s_defaultWhiteListAccessor); + + cafeExportRegisterFunc(nn::sl::GetDefaultWhiteListAccessor, "nn_sl", "GetDefaultWhiteListAccessor__Q2_2nn2slFv", LogType::NN_SL); +} diff --git a/src/Cafe/OS/libs/nn_sl/nn_sl.h b/src/Cafe/OS/libs/nn_sl/nn_sl.h new file mode 100644 index 0000000..08d936c --- /dev/null +++ b/src/Cafe/OS/libs/nn_sl/nn_sl.h @@ -0,0 +1 @@ +void nnSL_load(); \ No newline at end of file diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index fe74a6b..e789c2e 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -36,6 +36,7 @@ enum class LogType : sint32 NN_NFP = 13, NN_FP = 24, NN_BOSS = 25, + NN_SL = 26, TextureReadback = 29, From 9b30be02585ac3419973c2cbef30a5300d768d09 Mon Sep 17 00:00:00 2001 From: Maschell <Maschell@gmx.de> Date: Mon, 8 Apr 2024 19:50:57 +0200 Subject: [PATCH 058/130] drmapp: Stub more functions to allow title loading from Wii U Menu (#1161) --- src/Cafe/OS/libs/drmapp/drmapp.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/drmapp/drmapp.cpp b/src/Cafe/OS/libs/drmapp/drmapp.cpp index e196948..6c57b20 100644 --- a/src/Cafe/OS/libs/drmapp/drmapp.cpp +++ b/src/Cafe/OS/libs/drmapp/drmapp.cpp @@ -9,8 +9,29 @@ namespace drmapp return 1; } + uint32 PatchChkIsFinished() + { + cemuLog_logDebug(LogType::Force, "drmapp.PatchChkIsFinished() - placeholder"); + return 1; + } + + uint32 AocChkIsFinished() + { + cemuLog_logDebug(LogType::Force, "drmapp.AocChkIsFinished() - placeholder"); + return 1; + } + + uint32 TicketChkIsFinished() + { + cemuLog_logDebug(LogType::Force, "drmapp.TicketChkIsFinished__3RplFv() - placeholder"); + return 1; + } + void Initialize() { cafeExportRegisterFunc(NupChkIsFinished, "drmapp", "NupChkIsFinished__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(PatchChkIsFinished, "drmapp", "PatchChkIsFinished__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(AocChkIsFinished, "drmapp", "AocChkIsFinished__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(TicketChkIsFinished, "drmapp", "TicketChkIsFinished__3RplFv", LogType::Placeholder); } -} +} // namespace drmapp From 7b635e7eb87784a21014e8fcf0fdc420cf3a8c8d Mon Sep 17 00:00:00 2001 From: Maschell <Maschell@gmx.de> Date: Mon, 8 Apr 2024 19:51:30 +0200 Subject: [PATCH 059/130] nn_boss: Implement startIndex parameter usage in nn:boss:::GetDataList (#1162) --- src/Cafe/OS/libs/nn_boss/nn_boss.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index f53a6d7..2a05fa7 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -9,7 +9,7 @@ #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" -namespace nn +namespace nn { typedef uint32 Result; namespace boss @@ -782,9 +782,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; bossRequest->taskId = _thisptr->taskId.id; bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = isForegroundRun != 0; - + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - + return 0; } @@ -796,9 +796,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; bossRequest->taskId = _thisptr->taskId.id; bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = executeImmediately != 0; - + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - + return 0; } @@ -809,9 +809,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; bossRequest->accountId = _thisptr->accountId; bossRequest->taskId = _thisptr->taskId.id; bossRequest->titleId = _thisptr->titleId.u64; - + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - + return 0; } @@ -1001,7 +1001,7 @@ bossBufferVector->buffer = (uint8*)bossRequest; } }; static_assert(sizeof(PrivilegedTask) == 0x20); - + struct AlmightyTask : PrivilegedTask { struct VTableAlmightyTask : public VTablePrivilegedTask @@ -1169,14 +1169,17 @@ bossBufferVector->buffer = (uint8*)bossRequest; // initialize titleId of storage if not already done nnBossStorage_prepareTitleId(storage); - cemu_assert_debug(startIndex == 0); // non-zero index is todo + if(startIndex >= FAD_ENTRY_MAX_COUNT) { + *outputEntryCount = 0; + return 0; + } // load fad.db BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); if (fadTable) { sint32 validEntryCount = 0; - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + for (sint32 i = startIndex; i < FAD_ENTRY_MAX_COUNT; i++) { if( fadTable[i].name[0] == '\0' ) continue; @@ -1612,7 +1615,7 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static_assert(sizeof(NsData) == 0x58); -} +} } void nnBoss_load() { @@ -1663,7 +1666,7 @@ void nnBoss_load() cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::NN_BOSS); - + // PlayReportSetting nn::boss::PlayReportSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::PlayReportSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); From 33a74c203574090d563288ea05ffd28323e8c544 Mon Sep 17 00:00:00 2001 From: 47463915 <147349656+47463915@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:33:50 -0300 Subject: [PATCH 060/130] nn_nfp: Avoid current app from showing up as "???" for others in Friend List + View friends' status (#1157) --- src/Cafe/IOSU/legacy/iosu_fpd.cpp | 33 ++++++++++++++++++++++++++----- src/Cafe/IOSU/legacy/iosu_fpd.h | 4 ++-- src/Cemu/nex/nexFriends.cpp | 13 ++++++++++++ src/Cemu/nex/nexFriends.h | 4 ++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp index 9130b28..aca1a33 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.cpp +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -214,6 +214,12 @@ namespace iosu friendData->friendExtraData.gameKey.ukn08 = frd->presence.gameKey.ukn; NexPresenceToGameMode(&frd->presence, &friendData->friendExtraData.gameMode); + auto fixed_presence_msg = '\0' + frd->presence.msg; // avoid first character of comment from being cut off + friendData->friendExtraData.gameModeDescription.assignFromUTF8(fixed_presence_msg); + + auto fixed_comment = '\0' + frd->comment.commentString; // avoid first character of comment from being cut off + friendData->friendExtraData.comment.assignFromUTF8(fixed_comment); + // set valid dates friendData->uknDate.year = 2018; friendData->uknDate.day = 1; @@ -750,9 +756,18 @@ namespace iosu { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; - SelfPlayingGame selfPlayingGame{0}; - cemuLog_log(LogType::Force, "GetMyPlayingGame is todo"); - return WriteValueOutput<SelfPlayingGame>(vecOut, selfPlayingGame); + GameKey selfPlayingGame + { + CafeSystem::GetForegroundTitleId(), + CafeSystem::GetForegroundTitleVersion(), + {0,0,0,0,0,0} + }; + if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) != 0x00050000) + { + selfPlayingGame.titleId = 0; + selfPlayingGame.ukn08 = 0; + } + return WriteValueOutput<GameKey>(vecOut, selfPlayingGame); } nnResult CallHandler_GetFriendAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) @@ -1410,8 +1425,16 @@ namespace iosu act::getCountryIndex(currentSlot, &countryCode); // init presence g_fpd.myPresence.isOnline = 1; - g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); - g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000) + { + g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); + g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + } + else + { + g_fpd.myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others + g_fpd.myPresence.gameKey.ukn = 0; + } // resolve potential domain to IP address struct addrinfo hints = {0}, *addrs; hints.ai_family = AF_INET; diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.h b/src/Cafe/IOSU/legacy/iosu_fpd.h index 79f524d..0a6f088 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.h +++ b/src/Cafe/IOSU/legacy/iosu_fpd.h @@ -94,7 +94,7 @@ namespace iosu /* +0x1EC */ uint8 isOnline; /* +0x1ED */ uint8 _padding1ED[3]; // some other sub struct? - /* +0x1F0 */ char comment[36]; // pops up every few seconds in friend list + /* +0x1F0 */ CafeWideString<0x12> comment; // pops up every few seconds in friend list /* +0x214 */ uint32be _padding214; /* +0x218 */ FPDDate approvalTime; /* +0x220 */ FPDDate lastOnline; @@ -263,4 +263,4 @@ namespace iosu IOSUModule* GetModule(); } -} \ No newline at end of file +} diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index 4fae814..ae87ce4 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -1,6 +1,7 @@ #include "prudp.h" #include "nex.h" #include "nexFriends.h" +#include "Cafe/CafeSystem.h" static const int NOTIFICATION_SRV_FRIEND_OFFLINE = 0x0A; // the opposite event (friend online) is notified via _PRESENCE_CHANGE static const int NOTIFICATION_SRV_FRIEND_PRESENCE_CHANGE = 0x18; @@ -912,6 +913,18 @@ void NexFriends::markFriendRequestsAsReceived(uint64* messageIdList, sint32 coun void NexFriends::updateMyPresence(nexPresenceV2& myPresence) { this->myPresence = myPresence; + + if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000) + { + myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); + myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + } + else + { + myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others + myPresence.gameKey.ukn = 0; + } + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected diff --git a/src/Cemu/nex/nexFriends.h b/src/Cemu/nex/nexFriends.h index 06c7511..1077b0d 100644 --- a/src/Cemu/nex/nexFriends.h +++ b/src/Cemu/nex/nexFriends.h @@ -431,7 +431,7 @@ public: { nnaInfo.readData(pb); presence.readData(pb); - gameModeMessage.readData(pb); + comment.readData(pb); friendsSinceTimestamp = pb->readU64(); lastOnlineTimestamp = pb->readU64(); ukn6 = pb->readU64(); @@ -439,7 +439,7 @@ public: public: nexNNAInfo nnaInfo; nexPresenceV2 presence; - nexComment gameModeMessage; + nexComment comment; uint64 friendsSinceTimestamp; uint64 lastOnlineTimestamp; uint64 ukn6; From 12eda103876287283442880908425f751d14fd37 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:22:17 +0200 Subject: [PATCH 061/130] nn_acp: Implement ACPGetOlvAccesskey + code clean up Added ACPGetOlvAccesskey() which is used by Super Mario Maker iosu acp, nn_acp and nn_save all cross talk with each other and are mostly legacy code. Modernized it a tiny bit and moved functions to where they should be. A larger refactor should be done in the future but for now this works ok --- src/Cafe/CafeSystem.cpp | 1 + src/Cafe/IOSU/legacy/iosu_acp.cpp | 289 ++++++++++++++++++++++----- src/Cafe/IOSU/legacy/iosu_acp.h | 23 +++ src/Cafe/IOSU/legacy/iosu_act.cpp | 12 ++ src/Cafe/IOSU/legacy/iosu_act.h | 1 + src/Cafe/IOSU/nn/iosu_nn_service.h | 4 +- src/Cafe/OS/libs/nn_acp/nn_acp.cpp | 209 ++----------------- src/Cafe/OS/libs/nn_acp/nn_acp.h | 14 +- src/Cafe/OS/libs/nn_save/nn_save.cpp | 10 +- 9 files changed, 314 insertions(+), 249 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index bde1611..3c62a68 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -530,6 +530,7 @@ namespace CafeSystem { // entries in this list are ordered by initialization order. Shutdown in reverse order iosu::kernel::GetModule(), + iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), }; diff --git a/src/Cafe/IOSU/legacy/iosu_acp.cpp b/src/Cafe/IOSU/legacy/iosu_acp.cpp index ef5f708..f5144ee 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.cpp +++ b/src/Cafe/IOSU/legacy/iosu_acp.cpp @@ -8,10 +8,19 @@ #include "Cafe/OS/libs/nn_acp/nn_acp.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/Filesystem/fsc.h" -#include "Cafe/HW/Espresso/PPCState.h" +//#include "Cafe/HW/Espresso/PPCState.h" + +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/IOSU/nn/iosu_nn_service.h" + +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/CafeSystem.h" +#include "config/ActiveSettings.h" #include <inttypes.h> +using ACPDeviceType = iosu::acp::ACPDeviceType; + static_assert(sizeof(acpMetaXml_t) == 0x3440); static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000); static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008); @@ -506,48 +515,6 @@ namespace iosu return 0; } - sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) - { - uint32 persistentId = 0; - nn::save::GetPersistentIdEx(accountSlot, &persistentId); - - uint32 high = GetTitleIdHigh(titleId) & (~0xC); - uint32 low = GetTitleIdLow(titleId); - - sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; - char path[256]; - - sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - // copy xml meta files - nn::acp::CreateSaveMetaFiles(persistentId, titleId); - return 0; - } - int iosuAcp_thread() { SetThreadName("iosuAcp_thread"); @@ -584,7 +551,7 @@ namespace iosu } else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX) { - acpCemuRequest->returnCode = ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId); + acpCemuRequest->returnCode = acp::ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId); } else cemu_assert_unimplemented(); @@ -610,5 +577,237 @@ namespace iosu return iosuAcp.isInitialized; } + /* Above is the legacy implementation. Below is the new style implementation which also matches the official IPC protocol and works with the real nn_acp.rpl */ -} + namespace acp + { + + uint64 _ACPGetTimestamp() + { + return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK; + } + + nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) + { + if (deviceType == ACPDeviceType::UnknownType) + { + return (nnResult)0xA030FB80; + } + + // create or modify the saveinfo + const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath); + if (saveinfoData && !saveinfoData->empty()) + { + namespace xml = tinyxml2; + xml::XMLDocument doc; + tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size()); + if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT) + { + xml::XMLNode* child = doc.FirstChild(); + // check for declaration -> <?xml version="1.0" encoding="utf-8"?> + if (!child || !child->ToDeclaration()) + { + xml::XMLDeclaration* decl = doc.NewDeclaration(); + doc.InsertFirstChild(decl); + } + + xml::XMLElement* info = doc.FirstChildElement("info"); + if (!info) + { + info = doc.NewElement("info"); + doc.InsertEndChild(info); + } + + // find node with persistentId + char tmp[64]; + sprintf(tmp, "%08x", persistentId); + bool foundNode = false; + for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account")) + { + if (account->Attribute("persistentId", tmp)) + { + // found the entry! -> update timestamp + xml::XMLElement* timestamp = account->FirstChildElement("timestamp"); + sprintf(tmp, "%" PRIx64, _ACPGetTimestamp()); + if (timestamp) + timestamp->SetText(tmp); + else + { + timestamp = doc.NewElement("timestamp"); + account->InsertFirstChild(timestamp); + } + + foundNode = true; + break; + } + } + + if (!foundNode) + { + tinyxml2::XMLElement* account = doc.NewElement("account"); + { + sprintf(tmp, "%08x", persistentId); + account->SetAttribute("persistentId", tmp); + + tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp"); + { + sprintf(tmp, "%" PRIx64, _ACPGetTimestamp()); + timestamp->SetText(tmp); + } + + account->InsertFirstChild(timestamp); + } + + info->InsertFirstChild(account); + } + + // update file + tinyxml2::XMLPrinter printer; + doc.Print(&printer); + FileStream* fs = FileStream::createFile2(saveinfoPath); + if (fs) + { + fs->writeString(printer.CStr()); + delete fs; + } + } + } + return NN_RESULT_SUCCESS; + } + + void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId) + { + std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()); + + sint32 fscStatus; + FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (fscFile) + { + sint32 fileSize = fsc_getFileSize(fscFile); + + std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); + fsc_readFile(fscFile, fileContent.get(), fileSize); + fsc_close(fscFile); + + const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + + std::ofstream myFile(outPath, std::ios::out | std::ios::binary); + myFile.write((char*)fileContent.get(), fileSize); + myFile.close(); + } + + fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (fscFile) + { + sint32 fileSize = fsc_getFileSize(fscFile); + + std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); + fsc_readFile(fscFile, fileContent.get(), fileSize); + fsc_close(fscFile); + + const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + + std::ofstream myFile(outPath, std::ios::out | std::ios::binary); + myFile.write((char*)fileContent.get(), fileSize); + myFile.close(); + } + + ACPUpdateSaveTimeStamp(persistentId, titleId, iosu::acp::ACPDeviceType::InternalDeviceType); + } + + + sint32 _ACPCreateSaveDir(uint32 persistentId, uint64 titleId, ACPDeviceType type) + { + uint32 high = GetTitleIdHigh(titleId) & (~0xC); + uint32 low = GetTitleIdLow(titleId); + + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + char path[256]; + + sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + // copy xml meta files + CreateSaveMetaFiles(persistentId, titleId); + return 0; + } + + nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + return _ACPCreateSaveDir(persistentId, titleId, type); + } + + sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) + { + uint32 persistentId = 0; + cemu_assert_debug(accountSlot >= 1 && accountSlot <= 13); // outside valid slot range? + bool r = iosu::act::GetPersistentId(accountSlot, &persistentId); + cemu_assert_debug(r); + return _ACPCreateSaveDir(persistentId, titleId, ACPDeviceType::InternalDeviceType); + } + + nnResult ACPGetOlvAccesskey(uint32be* accessKey) + { + *accessKey = CafeSystem::GetForegroundTitleOlvAccesskey(); + return 0; + } + + class AcpMainService : public iosu::nn::IPCService + { + public: + AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {} + + nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + { + cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main"); + cemu_assert_unimplemented(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); + } + }; + + AcpMainService gACPMainService; + + class : public ::IOSUModule + { + void TitleStart() override + { + gACPMainService.Start(); + // gACPMainService.SetTimerUpdate(1000); // call TimerUpdate() once a second + } + void TitleStop() override + { + gACPMainService.Stop(); + } + }sIOSUModuleNNACP; + + IOSUModule* GetModule() + { + return static_cast<IOSUModule*>(&sIOSUModuleNNACP); + } + } // namespace acp +} // namespace iosu diff --git a/src/Cafe/IOSU/legacy/iosu_acp.h b/src/Cafe/IOSU/legacy/iosu_acp.h index 18197bd..a6fb6bf 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.h +++ b/src/Cafe/IOSU/legacy/iosu_acp.h @@ -1,5 +1,8 @@ #pragma once +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/OS/libs/nn_common.h" // for nnResult + typedef struct { /* +0x0000 */ uint64 title_id; // parsed via GetHex64 @@ -192,4 +195,24 @@ typedef struct namespace iosu { void iosuAcp_init(); + + namespace acp + { + enum ACPDeviceType + { + UnknownType = 0, + InternalDeviceType = 1, + USBDeviceType = 3, + }; + + class IOSUModule* GetModule(); + + void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId); + nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType); + + nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type); + sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId); + nnResult ACPGetOlvAccesskey(uint32be* accessKey); + } + } \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index ed3a69b..4285668 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -240,6 +240,18 @@ namespace iosu return true; } + bool GetPersistentId(uint8 slot, uint32* persistentId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if(!_actAccountData[accountIndex].isValid) + { + *persistentId = 0; + return false; + } + *persistentId = _actAccountData[accountIndex].persistentId; + return true; + } + class ActService : public iosu::nn::IPCService { public: diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index 5336f51..d60966d 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -49,6 +49,7 @@ namespace iosu bool getMii(uint8 slot, FFLData_t* fflData); bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getCountryIndex(uint8 slot, uint32* countryIndex); + bool GetPersistentId(uint8 slot, uint32* persistentId); std::string getAccountId2(uint8 slot); diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.h b/src/Cafe/IOSU/nn/iosu_nn_service.h index d50a079..d7d4cb0 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.h +++ b/src/Cafe/IOSU/nn/iosu_nn_service.h @@ -8,7 +8,7 @@ namespace iosu { namespace nn { - // a simple service interface which wraps handle management and Ioctlv/IoctlvAsync + // a simple service interface which wraps handle management and Ioctlv/IoctlvAsync (used by /dev/fpd and others are still to be determined) class IPCSimpleService { public: @@ -88,7 +88,7 @@ namespace iosu uint32be nnResultCode; }; - // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, ? + // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, ? class IPCService { public: diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp index 516087a..61640ae 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp @@ -17,6 +17,8 @@ #include "Common/FileStream.h" #include "Cafe/CafeSystem.h" +using ACPDeviceType = iosu::acp::ACPDeviceType; + #define acpPrepareRequest() \ StackAllocator<iosuAcpCemuRequest_t> _buf_acpRequest; \ StackAllocator<ioBufferVector_t> _buf_bufferVector; \ @@ -30,12 +32,14 @@ namespace nn { namespace acp { - ACPStatus _ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 someConstant) + ACPStatus ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 lineNumber) { // todo return ACPStatus::SUCCESS; } + #define _ACPConvertResultToACPStatus(nnResult) ACPConvertResultToACPStatus(nnResult, __func__, __LINE__) + ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId) { // todo @@ -43,6 +47,12 @@ namespace acp return ACPStatus::SUCCESS; } + ACPStatus ACPGetOlvAccesskey(uint32be* accessKey) + { + nnResult r = iosu::acp::ACPGetOlvAccesskey(accessKey); + return _ACPConvertResultToACPStatus(&r); + } + bool sSaveDirMounted{false}; ACPStatus ACPMountSaveDir() @@ -56,7 +66,7 @@ namespace acp const auto mlc = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/", high, low); FSCDeviceHostFS_Mount("/vol/save/", _pathToUtf8(mlc), FSC_PRIORITY_BASE); nnResult mountResult = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); - return _ACPConvertResultToACPStatus(&mountResult, "ACPMountSaveDir", 0x60); + return _ACPConvertResultToACPStatus(&mountResult); } ACPStatus ACPUnmountSaveDir() @@ -66,201 +76,24 @@ namespace acp return ACPStatus::SUCCESS; } - uint64 _acpGetTimestamp() - { - return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK; - } - - nnResult __ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) - { - if (deviceType == UnknownType) - { - return (nnResult)0xA030FB80; - } - - // create or modify the saveinfo - const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath); - if (saveinfoData && !saveinfoData->empty()) - { - namespace xml = tinyxml2; - xml::XMLDocument doc; - tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size()); - if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT) - { - xml::XMLNode* child = doc.FirstChild(); - // check for declaration -> <?xml version="1.0" encoding="utf-8"?> - if (!child || !child->ToDeclaration()) - { - xml::XMLDeclaration* decl = doc.NewDeclaration(); - doc.InsertFirstChild(decl); - } - - xml::XMLElement* info = doc.FirstChildElement("info"); - if (!info) - { - info = doc.NewElement("info"); - doc.InsertEndChild(info); - } - - // find node with persistentId - char tmp[64]; - sprintf(tmp, "%08x", persistentId); - bool foundNode = false; - for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account")) - { - if (account->Attribute("persistentId", tmp)) - { - // found the entry! -> update timestamp - xml::XMLElement* timestamp = account->FirstChildElement("timestamp"); - sprintf(tmp, "%" PRIx64, _acpGetTimestamp()); - if (timestamp) - timestamp->SetText(tmp); - else - { - timestamp = doc.NewElement("timestamp"); - account->InsertFirstChild(timestamp); - } - - foundNode = true; - break; - } - } - - if (!foundNode) - { - tinyxml2::XMLElement* account = doc.NewElement("account"); - { - sprintf(tmp, "%08x", persistentId); - account->SetAttribute("persistentId", tmp); - - tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp"); - { - sprintf(tmp, "%" PRIx64, _acpGetTimestamp()); - timestamp->SetText(tmp); - } - - account->InsertFirstChild(timestamp); - } - - info->InsertFirstChild(account); - } - - // update file - tinyxml2::XMLPrinter printer; - doc.Print(&printer); - FileStream* fs = FileStream::createFile2(saveinfoPath); - if (fs) - { - fs->writeString(printer.CStr()); - delete fs; - } - } - } - return NN_RESULT_SUCCESS; - } - ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) { - nnResult r = __ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType); + nnResult r = iosu::acp::ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType); return ACPStatus::SUCCESS; } - void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId) - { - std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()); - - sint32 fscStatus; - FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (fscFile) - { - sint32 fileSize = fsc_getFileSize(fscFile); - - std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); - fsc_readFile(fscFile, fileContent.get(), fileSize); - fsc_close(fscFile); - - const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - - std::ofstream myFile(outPath, std::ios::out | std::ios::binary); - myFile.write((char*)fileContent.get(), fileSize); - myFile.close(); - } - - fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (fscFile) - { - sint32 fileSize = fsc_getFileSize(fscFile); - - std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); - fsc_readFile(fscFile, fileContent.get(), fileSize); - fsc_close(fscFile); - - const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - - std::ofstream myFile(outPath, std::ios::out | std::ios::binary); - myFile.write((char*)fileContent.get(), fileSize); - myFile.close(); - } - - ACPUpdateSaveTimeStamp(persistentId, titleId, InternalDeviceType); - } - - nnResult CreateSaveDir(uint32 persistentId, ACPDeviceType type) - { - uint64 titleId = CafeSystem::GetForegroundTitleId(); - uint32 high = GetTitleIdHigh(titleId) & (~0xC); - uint32 low = GetTitleIdLow(titleId); - - sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; - char path[256]; - - sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - // not sure about this - sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - // copy xml meta files - CreateSaveMetaFiles(persistentId, titleId); - - nnResult result = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); - return result; - } - - ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) - { - nnResult result = CreateSaveDir(persistentId, type); - return _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDir", 0x2FA); - } - ACPStatus ACPCheckApplicationDeviceEmulation(uint32be* isEmulated) { *isEmulated = 0; return ACPStatus::SUCCESS; } + ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) + { + nnResult result = iosu::acp::ACPCreateSaveDir(persistentId, type); + return _ACPConvertResultToACPStatus(&result); + } + nnResult ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) { acpPrepareRequest(); @@ -279,7 +112,7 @@ namespace acp ppcDefineParamU8(accountSlot, 0); ppcDefineParamU64(titleId, 2); // index 2 because of alignment -> guessed parameters nnResult result = ACPCreateSaveDirEx(accountSlot, titleId); - osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDirEx", 0x300)); + osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result)); } void export_ACPGetSaveDataTitleIdList(PPCInterpreter_t* hCPU) @@ -511,6 +344,8 @@ namespace acp cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder); + cafeExportRegister("nn_acp", ACPGetOlvAccesskey, LogType::Placeholder); + osLib_addFunction("nn_acp", "ACPIsOverAgeEx", export_ACPIsOverAgeEx); osLib_addFunction("nn_acp", "ACPGetNetworkTime", export_ACPGetNetworkTime); diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.h b/src/Cafe/OS/libs/nn_acp/nn_acp.h index cbf36c6..9890a6d 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.h +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.h @@ -1,4 +1,5 @@ #pragma once +#include "Cafe/IOSU/legacy/iosu_acp.h" namespace nn { @@ -9,20 +10,13 @@ namespace acp SUCCESS = 0, }; - enum ACPDeviceType - { - UnknownType = 0, - InternalDeviceType = 1, - USBDeviceType = 3, - }; - - void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId); + using ACPDeviceType = iosu::acp::ACPDeviceType; ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId); ACPStatus ACPMountSaveDir(); ACPStatus ACPUnmountSaveDir(); - ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type); - ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);; + ACPStatus ACPCreateSaveDir(uint32 persistentId, iosu::acp::ACPDeviceType type); + ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, iosu::acp::ACPDeviceType deviceType); void load(); } diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 78de829..05e4943 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -72,11 +72,11 @@ namespace save return result != 0; } - bool GetCurrentTitleApplicationBox(acp::ACPDeviceType* deviceType) + bool GetCurrentTitleApplicationBox(nn::acp::ACPDeviceType* deviceType) { if (deviceType) { - *deviceType = acp::InternalDeviceType; + *deviceType = nn::acp::ACPDeviceType::InternalDeviceType; return true; } return false; @@ -84,7 +84,7 @@ namespace save void UpdateSaveTimeStamp(uint32 persistentId) { - acp::ACPDeviceType deviceType; + nn::acp::ACPDeviceType deviceType; if (GetCurrentTitleApplicationBox(&deviceType)) ACPUpdateSaveTimeStamp(persistentId, CafeSystem::GetForegroundTitleId(), deviceType); } @@ -314,7 +314,7 @@ namespace save sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); - acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId); + iosu::acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId); } return SAVE_STATUS_OK; @@ -669,7 +669,7 @@ namespace save uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { - acp::ACPStatus status = ACPCreateSaveDir(persistentId, acp::InternalDeviceType); + acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); result = ConvertACPToSaveStatus(status); } else From d45c2fa6d1ccd008a2ef22d814530894abd691d1 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:23:15 +0200 Subject: [PATCH 062/130] erreula: Avoid triggering debug assert in imgui It does not like empty window titles --- src/Cafe/OS/libs/erreula/erreula.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/erreula/erreula.cpp b/src/Cafe/OS/libs/erreula/erreula.cpp index c95816b..a7f2f35 100644 --- a/src/Cafe/OS/libs/erreula/erreula.cpp +++ b/src/Cafe/OS/libs/erreula/erreula.cpp @@ -277,10 +277,11 @@ namespace erreula ImGui::SetNextWindowBgAlpha(0.9f); ImGui::PushFont(font); - std::string title = "ErrEula"; + std::string title; if (appearArg.title) title = boost::nowide::narrow(GetText(appearArg.title.GetPtr())); - + if(title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty + title = "ErrEula"; if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags)) { const float startx = ImGui::GetWindowSize().x / 2.0f; From bac1ac3b499d84d502fdcec32e2fa5e8b176975c Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 11 Apr 2024 03:06:36 +0200 Subject: [PATCH 063/130] CI: use last vcpkg compatible CMake 3.29.0 (#1167) --- .github/workflows/build.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3b834b..58a8508 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,6 +55,11 @@ jobs: sudo apt update -qq sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build + - name: "Setup cmake" + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.29.0' + - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh @@ -154,6 +159,11 @@ jobs: echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + - name: "Setup cmake" + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.29.0' + - name: "Bootstrap vcpkg" run: | ./dependencies/vcpkg/bootstrap-vcpkg.bat @@ -234,6 +244,11 @@ jobs: brew update brew install llvm@15 ninja nasm molten-vk automake libtool + - name: "Setup cmake" + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.29.0' + - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh From 391533dbe5eec4c1f8d1f00a05629baf2216b423 Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:08:26 -0700 Subject: [PATCH 064/130] Gamelist: Enable icon column by default (#1168) --- src/config/CemuConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index bcaf846..9f1e798 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -418,7 +418,7 @@ struct CemuConfig ConfigValue<bool> did_show_graphic_pack_download{false}; ConfigValue<bool> did_show_macos_disclaimer{false}; - ConfigValue<bool> show_icon_column{ false }; + ConfigValue<bool> show_icon_column{ true }; int game_list_style = 0; std::string game_list_column_order; From 84cad8b280afa9db7637ec1fd125d1b59970b548 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 11 Apr 2024 06:41:57 +0200 Subject: [PATCH 065/130] Vulkan: Remove unecessary present fence (#1166) --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 5 --- .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 39 ++++--------------- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 7 +--- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 9 +---- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 - 5 files changed, 10 insertions(+), 51 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 5b9fc34..60124c0 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -875,11 +875,6 @@ void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, s void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView) { - if (g_renderer->GetType() == RendererAPI::Vulkan) - { - ((VulkanRenderer*)g_renderer.get())->PreparePresentationFrame(!isPadView); - } - // make sure texture is updated to latest data in cache LatteTexture_UpdateDataToLatest(textureView->baseTexture); // mark source texture as still in use diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index b00f549..75ff02b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -146,13 +146,6 @@ void SwapchainInfoVk::Create() UnrecoverableError("Failed to create semaphore for swapchain acquire"); } - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); - if (result != VK_SUCCESS) - UnrecoverableError("Failed to create fence for swapchain"); - m_acquireIndex = 0; hasDefinedSwapchainImage = false; } @@ -184,12 +177,6 @@ void SwapchainInfoVk::Cleanup() m_swapchainFramebuffers.clear(); - if (m_imageAvailableFence) - { - WaitAvailableFence(); - vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); - m_imageAvailableFence = nullptr; - } if (m_swapchain) { vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); @@ -202,18 +189,6 @@ bool SwapchainInfoVk::IsValid() const return m_swapchain && !m_acquireSemaphores.empty(); } -void SwapchainInfoVk::WaitAvailableFence() -{ - if(m_awaitableFence != VK_NULL_HANDLE) - vkWaitForFences(m_logicalDevice, 1, &m_awaitableFence, VK_TRUE, UINT64_MAX); - m_awaitableFence = VK_NULL_HANDLE; -} - -void SwapchainInfoVk::ResetAvailableFence() const -{ - vkResetFences(m_logicalDevice, 1, &m_imageAvailableFence); -} - VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() { VkSemaphore ret = m_currentSemaphore; @@ -221,15 +196,18 @@ VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() return ret; } -bool SwapchainInfoVk::AcquireImage(uint64 timeout) +bool SwapchainInfoVk::AcquireImage() { - WaitAvailableFence(); - ResetAvailableFence(); - VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, nullptr, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; + if (result == VK_TIMEOUT) + { + swapchainImageIndex = -1; + return false; + } + if (result < 0) { swapchainImageIndex = -1; @@ -238,7 +216,6 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout) return false; } m_currentSemaphore = acquireSemaphore; - m_awaitableFence = m_imageAvailableFence; m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); return true; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index 0e8c2ad..ceffab4 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -26,10 +26,7 @@ struct SwapchainInfoVk bool IsValid() const; - void WaitAvailableFence(); - void ResetAvailableFence() const; - - bool AcquireImage(uint64 timeout); + bool AcquireImage(); // retrieve semaphore of last acquire for submitting a wait operation // only one wait operation must be submitted per acquire (which submits a single signal operation) // therefore subsequent calls will return a NULL handle @@ -84,9 +81,7 @@ struct SwapchainInfoVk private: uint32 m_acquireIndex = 0; std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex - VkFence m_imageAvailableFence{}; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; - VkFence m_awaitableFence = VK_NULL_HANDLE; std::array<uint32, 2> m_swapchainQueueFamilyIndices; VkExtent2D m_actualExtent{}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index e0ebda2..9209e3c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1824,11 +1824,6 @@ void VulkanRenderer::DrawEmptyFrame(bool mainWindow) SwapBuffers(mainWindow, !mainWindow); } -void VulkanRenderer::PreparePresentationFrame(bool mainWindow) -{ - AcquireNextSwapchainImage(mainWindow); -} - void VulkanRenderer::InitFirstCommandBuffer() { cemu_assert_debug(m_state.currentCommandBuffer == nullptr); @@ -2599,7 +2594,7 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow) if (!UpdateSwapchainProperties(mainWindow)) return false; - bool result = chainInfo.AcquireImage(UINT64_MAX); + bool result = chainInfo.AcquireImage(); if (!result) return false; @@ -2612,8 +2607,6 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate) SubmitCommandBuffer(); WaitDeviceIdle(); auto& chainInfo = GetChainInfo(mainWindow); - // make sure fence has no signal operation submitted - chainInfo.WaitAvailableFence(); Vector2i size; if (mainWindow) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 2491d05..6df53da 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -228,7 +228,6 @@ public: uint64 GenUniqueId(); // return unique id (uses incrementing counter) void DrawEmptyFrame(bool mainWindow) override; - void PreparePresentationFrame(bool mainWindow); void InitFirstCommandBuffer(); void ProcessFinishedCommandBuffers(); From d5a8530246a6411c318c8df1dadb2d3e6fd658f7 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 13 Apr 2024 10:38:10 +0200 Subject: [PATCH 066/130] nlibcurl: Detect invalid header combo + refactoring Fixes error 106-0526 when opening course world on Super Mario Maker Manually attaching Content-Length header for POST requests is undefined behavior on recent libcurl. To detect the bad case some refactoring was necessary. In general we should try to move away from directly forwarding curl_easy_setopt() to the underlying instance as the behavior is diverging in modern libcurl. Much more refactoring work is required in the future to fix all of this. --- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 404 +++++++++++++++++-------- 1 file changed, 280 insertions(+), 124 deletions(-) diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 53981a5..318e658 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -1,4 +1,5 @@ #include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/common/OSUtil.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "nlibcurl.h" @@ -6,6 +7,8 @@ #include "openssl/x509.h" #include "openssl/ssl.h" +#define CURL_STRICTER + #include "curl/curl.h" #include <unordered_map> #include <atomic> @@ -98,6 +101,17 @@ struct MEMPTRHash_t } }; +struct WU_curl_slist +{ + MEMPTR<char> data; + MEMPTR<WU_curl_slist> next; +}; + +enum class WU_CURLcode +{ + placeholder = 0, +}; + struct { sint32 initialized; @@ -110,8 +124,53 @@ struct MEMPTR<curl_calloc_callback> calloc; } g_nlibcurl = {}; +using WU_CURL_off_t = uint64be; -#pragma pack(1) +enum class WU_HTTPREQ : uint32 +{ + HTTPREQ_GET = 0x1, + HTTPREQ_POST = 0x2, + UKN_3 = 0x3, +}; + +struct WU_UserDefined +{ + // starting at 0xD8 (probably) in CURL_t + /* 0x0D8 / +0x00 */ uint32be ukn0D8; + /* 0x0DC / +0x04 */ uint32be ukn0DC; + /* 0x0E0 / +0x08 */ MEMPTR<WU_curl_slist> headers; + /* 0x0E4 / +0x0C */ uint32be ukn0E4; + /* 0x0E8 / +0x10 */ uint32be ukn0E8; + /* 0x0EC / +0x14 */ uint32be ukn0EC; + /* 0x0F0 / +0x18 */ uint32be ukn0F0[4]; + /* 0x100 / +0x28 */ uint32be ukn100[4]; + /* 0x110 / +0x38 */ uint32be ukn110[4]; // +0x40 -> WU_CURL_off_t postfieldsize ? + /* 0x120 / +0x48 */ uint32be ukn120[4]; + /* 0x130 / +0x58 */ uint32be ukn130[4]; + /* 0x140 / +0x68 */ uint32be ukn140[4]; + /* 0x150 / +0x78 */ uint32be ukn150[4]; + /* 0x160 / +0x88 */ uint32be ukn160[4]; + /* 0x170 / +0x98 */ uint32be ukn170[4]; + /* 0x180 / +0xA8 */ uint32be ukn180[4]; + /* 0x190 / +0xB0 */ sint64be infilesize_190{0}; + /* 0x198 / +0xB8 */ uint32be ukn198; + /* 0x19C / +0xBC */ uint32be ukn19C; + /* 0x1A0 / +0xC8 */ uint32be ukn1A0[4]; + /* 0x1B0 / +0xD8 */ uint32be ukn1B0[4]; + /* 0x1C0 / +0xE8 */ uint32be ukn1C0[4]; + /* 0x1D0 / +0xF8 */ uint32be ukn1D0[4]; + /* 0x1E0 / +0x108 */ uint32be ukn1E0; + /* 0x1E4 / +0x108 */ uint32be ukn1E4; + /* 0x1E8 / +0x108 */ uint32be ukn1E8; + /* 0x1EC / +0x108 */ betype<WU_HTTPREQ> httpreq_1EC; + /* 0x1F0 / +0x118 */ uint32be ukn1F0[4]; + + void SetToDefault() + { + memset(this, 0, sizeof(WU_UserDefined)); + httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; + } +}; struct CURL_t { @@ -137,6 +196,7 @@ struct CURL_t OSThread_t* curlThread; MEMPTR<char> info_redirectUrl; // stores CURLINFO_REDIRECT_URL ptr MEMPTR<char> info_contentType; // stores CURLINFO_CONTENT_TYPE ptr + bool isDirty{true}; // debug struct @@ -149,10 +209,44 @@ struct CURL_t FileStream* file_responseRaw{}; }debug; + // fields below match the actual memory layout, above still needs refactoring + /* 0x78 */ uint32be ukn078; + /* 0x7C */ uint32be ukn07C; + /* 0x80 */ uint32be ukn080; + /* 0x84 */ uint32be ukn084; + /* 0x88 */ uint32be ukn088; + /* 0x8C */ uint32be ukn08C; + /* 0x90 */ uint32be ukn090[4]; + /* 0xA0 */ uint32be ukn0A0[4]; + /* 0xB0 */ uint32be ukn0B0[4]; + /* 0xC0 */ uint32be ukn0C0[4]; + /* 0xD0 */ uint32be ukn0D0; + /* 0xD4 */ uint32be ukn0D4; + /* 0xD8 */ WU_UserDefined set; + /* 0x200 */ uint32be ukn200[4]; + /* 0x210 */ uint32be ukn210[4]; + /* 0x220 */ uint32be ukn220[4]; + /* 0x230 */ uint32be ukn230[4]; + /* 0x240 */ uint32be ukn240[4]; + /* 0x250 */ uint32be ukn250[4]; + /* 0x260 */ uint32be ukn260[4]; + /* 0x270 */ uint32be ukn270[4]; + /* 0x280 */ uint8be ukn280; + /* 0x281 */ uint8be opt_no_body_281; + /* 0x282 */ uint8be ukn282; + /* 0x283 */ uint8be upload_283; }; static_assert(sizeof(CURL_t) <= 0x8698); +static_assert(offsetof(CURL_t, ukn078) == 0x78); +static_assert(offsetof(CURL_t, set) == 0xD8); +static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, headers) == 0xE0); +static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, infilesize_190) == 0x190); +static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, httpreq_1EC) == 0x1EC); +static_assert(offsetof(CURL_t, opt_no_body_281) == 0x281); typedef MEMPTR<CURL_t> CURLPtr; +#pragma pack(1) // may affect structs below, we can probably remove this but lets keep it for now as the code below is fragile + typedef struct { //uint32be specifier; // 0x00 @@ -173,18 +267,12 @@ typedef MEMPTR<CURLSH_t> CURLSHPtr; typedef struct { CURLM* curlm; - std::vector< MEMPTR<CURL> > curl; + std::vector<MEMPTR<CURL_t>> curl; }CURLM_t; static_assert(sizeof(CURLM_t) <= 0x80, "sizeof(CURLM_t)"); typedef MEMPTR<CURLM_t> CURLMPtr; -struct curl_slist_t -{ - MEMPTR<char> data; - MEMPTR<curl_slist_t> next; -}; - -static_assert(sizeof(curl_slist_t) <= 0x8, "sizeof(curl_slist_t)"); +static_assert(sizeof(WU_curl_slist) <= 0x8, "sizeof(curl_slist_t)"); struct CURLMsg_t { @@ -298,6 +386,89 @@ uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) return result; } +int curl_closesocket(void *clientp, curl_socket_t item); + +void _curl_set_default_parameters(CURL_t* curl) +{ + curl->set.SetToDefault(); + + // default parameters + curl_easy_setopt(curl->curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl->curl, CURLOPT_HEADERDATA, curl); + + curl_easy_setopt(curl->curl, CURLOPT_CLOSESOCKETFUNCTION, curl_closesocket); + curl_easy_setopt(curl->curl, CURLOPT_CLOSESOCKETDATA, nullptr); +} + +void _curl_sync_parameters(CURL_t* curl) +{ + // sync ppc curl to actual curl state + // not all parameters are covered yet, many are still set directly in easy_setopt + bool isPost = curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_POST; + // http request type + if(curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_GET) + { + ::curl_easy_setopt(curl->curl, CURLOPT_HTTPGET, 1); + cemu_assert_debug(curl->opt_no_body_281 == 0); + cemu_assert_debug(curl->upload_283 == 0); + } + else if(curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_POST) + { + ::curl_easy_setopt(curl->curl, CURLOPT_POST, 1); + cemu_assert_debug(curl->upload_283 == 0); + ::curl_easy_setopt(curl->curl, CURLOPT_NOBODY, curl->opt_no_body_281 ? 1 : 0); + } + else + { + cemu_assert_unimplemented(); + } + + // CURLOPT_HTTPHEADER + std::optional<uint64> manualHeaderContentLength; + if (curl->set.headers) + { + struct curl_slist* list = nullptr; + WU_curl_slist* ppcList = curl->set.headers; + while(ppcList) + { + if(isPost) + { + // for recent libcurl manually adding Content-Length header is undefined behavior. Instead CURLOPT_INFILESIZE(_LARGE) should be set + // here we remove Content-Length and instead substitute it with CURLOPT_INFILESIZE (NEX DataStore in Super Mario Maker requires this) + if(strncmp(ppcList->data.GetPtr(), "Content-Length:", 15) == 0) + { + manualHeaderContentLength = std::stoull(ppcList->data.GetPtr() + 15); + ppcList = ppcList->next; + continue; + } + } + + cemuLog_logDebug(LogType::Force, "curl_slist_append: {}", ppcList->data.GetPtr()); + curlDebug_logEasySetOptStr(curl, "CURLOPT_HTTPHEADER", (const char*)ppcList->data.GetPtr()); + list = ::curl_slist_append(list, ppcList->data.GetPtr()); + ppcList = ppcList->next; + } + ::curl_easy_setopt(curl->curl, CURLOPT_HTTPHEADER, list); + // todo - prevent leaking of list (maybe store in host curl object, similar to how our zlib implementation does stuff) + } + else + ::curl_easy_setopt(curl->curl, CURLOPT_HTTPHEADER, nullptr); + + // infile size (post data size) + if (curl->set.infilesize_190) + { + cemu_assert_debug(manualHeaderContentLength == 0); // should not have both? + ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, curl->set.infilesize_190); + } + else + { + if(isPost && manualHeaderContentLength > 0) + ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, manualHeaderContentLength); + else + ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, 0); + } +} + void export_malloc(PPCInterpreter_t* hCPU) { ppcDefineParamU32(size, 0); @@ -340,7 +511,6 @@ void export_realloc(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result.GetMPTR()); } - CURLcode curl_global_init(uint32 flags) { if (g_nlibcurl.initialized++) @@ -436,6 +606,18 @@ void export_curl_multi_perform(PPCInterpreter_t* hCPU) //cemuLog_logDebug(LogType::Force, "curl_multi_perform(0x{:08x}, 0x{:08x})", curlm.GetMPTR(), runningHandles.GetMPTR()); + //curl_multi_get_handles(curlm->curlm); + + for(auto _curl : curlm->curl) + { + CURL_t* curl = (CURL_t*)_curl.GetPtr(); + if(curl->isDirty) + { + curl->isDirty = false; + _curl_sync_parameters(curl); + } + } + //g_callerQueue = curlm->callerQueue; //g_threadQueue = curlm->threadQueue; int tempRunningHandles = 0; @@ -555,7 +737,7 @@ void export_curl_multi_info_read(PPCInterpreter_t* hCPU) if (msg->easy_handle) { const auto it = find_if(curlm->curl.cbegin(), curlm->curl.cend(), - [msg](const MEMPTR<void>& curl) + [msg](const MEMPTR<CURL_t>& curl) { const MEMPTR<CURL_t> _curl{ curl }; return _curl->curl = msg->easy_handle; @@ -661,26 +843,6 @@ void export_curl_share_cleanup(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -int my_trace(CURL *handle, curl_infotype type, char *ptr, size_t size, - void *userp) -{ - FILE* f = (FILE*)userp; - - //if (type == CURLINFO_TEXT) - { - char tmp[1024] = {}; - sprintf(tmp, "0x%p: ", handle); - fwrite(tmp, 1, strlen(tmp), f); - - memcpy(tmp, ptr, std::min(size, (size_t)990)); - fwrite(tmp, 1, std::min(size + 1, (size_t)991), f); - - fflush(f); - - } - return 0; -} - static int curl_closesocket(void *clientp, curl_socket_t item) { nsysnet_notifyCloseSharedSocket((SOCKET)item); @@ -688,36 +850,30 @@ static int curl_closesocket(void *clientp, curl_socket_t item) return 0; } -void export_curl_easy_init(PPCInterpreter_t* hCPU) +CURL_t* curl_easy_init() { if (g_nlibcurl.initialized == 0) { if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { - osLib_returnFromFunction(hCPU, 0); - return; + return nullptr; } } // Curl_open - CURLPtr result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURL_t>()) }; + MEMPTR<CURL_t> result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURL_t>()) }; cemuLog_logDebug(LogType::Force, "curl_easy_init() -> 0x{:08x}", result.GetMPTR()); if (result) { memset(result.GetPtr(), 0, sizeof(CURL_t)); *result = {}; - result->curl = curl_easy_init(); + result->curl = ::curl_easy_init(); result->curlThread = coreinit::OSGetCurrentThread(); result->info_contentType = nullptr; result->info_redirectUrl = nullptr; - // default parameters - curl_easy_setopt(result->curl, CURLOPT_HEADERFUNCTION, header_callback); - curl_easy_setopt(result->curl, CURLOPT_HEADERDATA, result.GetPtr()); - - curl_easy_setopt(result->curl, CURLOPT_CLOSESOCKETFUNCTION, curl_closesocket); - curl_easy_setopt(result->curl, CURLOPT_CLOSESOCKETDATA, nullptr); + _curl_set_default_parameters(result.GetPtr()); if (g_nlibcurl.proxyConfig) { @@ -725,7 +881,12 @@ void export_curl_easy_init(PPCInterpreter_t* hCPU) } } - osLib_returnFromFunction(hCPU, result.GetMPTR()); + return result; +} + +CURL_t* mw_curl_easy_init() +{ + return curl_easy_init(); } void export_curl_easy_pause(PPCInterpreter_t* hCPU) @@ -971,18 +1132,47 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) ppcDefineParamU64(parameterU64, 2); CURL* curlObj = curl->curl; + curl->isDirty = true; CURLcode result = CURLE_OK; switch (option) { - case CURLOPT_NOSIGNAL: + case CURLOPT_POST: + { + if(parameter) + { + curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_POST; + curl->opt_no_body_281 = 0; + } + else + curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; + break; + } case CURLOPT_HTTPGET: + { + if (parameter) + { + curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; + curl->opt_no_body_281 = 0; + curl->upload_283 = 0; + } + break; + } + case CURLOPT_INFILESIZE: + { + curl->set.infilesize_190 = (sint64)(sint32)(uint32)parameter.GetBEValue(); + break; + } + case CURLOPT_INFILESIZE_LARGE: + { + curl->set.infilesize_190 = (sint64)(uint64)parameterU64; + break; + } + case CURLOPT_NOSIGNAL: case CURLOPT_FOLLOWLOCATION: case CURLOPT_BUFFERSIZE: case CURLOPT_TIMEOUT: case CURLOPT_CONNECTTIMEOUT_MS: - case CURLOPT_POST: - case CURLOPT_INFILESIZE: case CURLOPT_NOPROGRESS: case CURLOPT_LOW_SPEED_LIMIT: case CURLOPT_LOW_SPEED_TIME: @@ -1068,8 +1258,6 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) curlSh->curl = curl; shObj = curlSh->curlsh; } - - result = ::curl_easy_setopt(curlObj, CURLOPT_SHARE, shObj); break; } @@ -1101,17 +1289,8 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) } case CURLOPT_HTTPHEADER: { - struct curl_slist* list = nullptr; - bool isFirst = true; - for (curl_slist_t* ppcList = (curl_slist_t*)parameter.GetPtr(); ppcList; ppcList = ppcList->next.GetPtr()) - { - cemuLog_logDebug(LogType::Force, "curl_slist_append: {}", ppcList->data.GetPtr()); - curlDebug_logEasySetOptStr(curl.GetPtr(), isFirst?"CURLOPT_HTTPHEADER" : "CURLOPT_HTTPHEADER(continue)", (const char*)ppcList->data.GetPtr()); - list = ::curl_slist_append(list, ppcList->data.GetPtr()); - isFirst = false; - } - - result = ::curl_easy_setopt(curlObj, CURLOPT_HTTPHEADER, list); + curl->set.headers = (WU_curl_slist*)parameter.GetPtr(); + result = CURLE_OK; break; } case CURLOPT_SOCKOPTFUNCTION: @@ -1163,15 +1342,18 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result); } -void export_curl_easy_perform(PPCInterpreter_t* hCPU) +WU_CURLcode curl_easy_perform(CURL_t* curl) { - ppcDefineParamMEMPTR(curl, CURL_t, 0); - curlDebug_markActiveRequest(curl.GetPtr()); - curlDebug_notifySubmitRequest(curl.GetPtr()); - cemuLog_logDebug(LogType::Force, "curl_easy_perform(0x{:08x})", curl.GetMPTR()); - const uint32 result = SendOrderToWorker(curl.GetPtr(), QueueOrder_Perform); - cemuLog_logDebug(LogType::Force, "curl_easy_perform(0x{:08x}) -> 0x{:x} DONE", curl.GetMPTR(), result); - osLib_returnFromFunction(hCPU, result); + curlDebug_markActiveRequest(curl); + curlDebug_notifySubmitRequest(curl); + + if(curl->isDirty) + { + curl->isDirty = false; + _curl_sync_parameters(curl); + } + const uint32 result = SendOrderToWorker(curl, QueueOrder_Perform); + return static_cast<WU_CURLcode>(result); } void _updateGuestString(CURL_t* curl, MEMPTR<char>& ppcStr, char* hostStr) @@ -1246,14 +1428,6 @@ void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result); } - - -void export_curl_global_init(PPCInterpreter_t* hCPU) -{ - ppcDefineParamU32(flags, 0); - osLib_returnFromFunction(hCPU, curl_global_init(flags)); -} - void export_curl_easy_strerror(PPCInterpreter_t* hCPU) { ppcDefineParamU32(code, 0); @@ -1270,21 +1444,16 @@ void export_curl_easy_strerror(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result.GetMPTR()); } -void export_curl_slist_append(PPCInterpreter_t* hCPU) +WU_curl_slist* curl_slist_append(WU_curl_slist* list, const char* data) { - ppcDefineParamMEMPTR(list, curl_slist_t, 0); - ppcDefineParamMEMPTR(data, const char, 1); - - - MEMPTR<char> dupdata{ PPCCoreCallback(g_nlibcurl.strdup.GetMPTR(), data.GetMPTR()) }; + MEMPTR<char> dupdata{ PPCCoreCallback(g_nlibcurl.strdup.GetMPTR(), data) }; if (!dupdata) { - cemuLog_logDebug(LogType::Force, "curl_slist_append(0x{:08x}, 0x{:08x} [{}]) -> 0x00000000", list.GetMPTR(), data.GetMPTR(), data.GetPtr()); - osLib_returnFromFunction(hCPU, 0); - return; + cemuLog_logDebug(LogType::Force, "curl_slist_append(): Failed to duplicate string"); + return nullptr; } - MEMPTR<curl_slist_t> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<curl_slist_t>()) }; + MEMPTR<WU_curl_slist> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<WU_curl_slist>()) }; if (result) { result->data = dupdata; @@ -1293,7 +1462,7 @@ void export_curl_slist_append(PPCInterpreter_t* hCPU) // update last obj of list if (list) { - MEMPTR<curl_slist_t> tmp = list; + MEMPTR<WU_curl_slist> tmp = list; while (tmp->next) { tmp = tmp->next; @@ -1303,38 +1472,24 @@ void export_curl_slist_append(PPCInterpreter_t* hCPU) } } else + { + cemuLog_logDebug(LogType::Force, "curl_slist_append(): Failed to allocate memory"); PPCCoreCallback(g_nlibcurl.free.GetMPTR(), dupdata.GetMPTR()); - - cemuLog_logDebug(LogType::Force, "curl_slist_append(0x{:08x}, 0x{:08x} [{}]) -> 0x{:08x}", list.GetMPTR(), data.GetMPTR(), data.GetPtr(), result.GetMPTR()); + } if(list) - osLib_returnFromFunction(hCPU, list.GetMPTR()); - else - osLib_returnFromFunction(hCPU, result.GetMPTR()); + return list; + return result; } -void export_curl_slist_free_all(PPCInterpreter_t* hCPU) +void curl_slist_free_all(WU_curl_slist* list) { - ppcDefineParamMEMPTR(list, curl_slist_t, 0); - cemuLog_logDebug(LogType::Force, "export_curl_slist_free_all: TODO"); - - osLib_returnFromFunction(hCPU, 0); } -void export_curl_global_init_mem(PPCInterpreter_t* hCPU) +CURLcode curl_global_init_mem(uint32 flags, MEMPTR<curl_malloc_callback> malloc_callback, MEMPTR<curl_free_callback> free_callback, MEMPTR<curl_realloc_callback> realloc_callback, MEMPTR<curl_strdup_callback> strdup_callback, MEMPTR<curl_calloc_callback> calloc_callback) { - ppcDefineParamU32(flags, 0); - ppcDefineParamMEMPTR(m, curl_malloc_callback, 1); - ppcDefineParamMEMPTR(f, curl_free_callback, 2); - ppcDefineParamMEMPTR(r, curl_realloc_callback, 3); - ppcDefineParamMEMPTR(s, curl_strdup_callback, 4); - ppcDefineParamMEMPTR(c, curl_calloc_callback, 5); - - if (!m || !f || !r || !s || !c) - { - osLib_returnFromFunction(hCPU, CURLE_FAILED_INIT); - return; - } + if(!malloc_callback || !free_callback || !realloc_callback || !strdup_callback || !calloc_callback) + return CURLE_FAILED_INIT; CURLcode result = CURLE_OK; if (g_nlibcurl.initialized == 0) @@ -1342,31 +1497,30 @@ void export_curl_global_init_mem(PPCInterpreter_t* hCPU) result = curl_global_init(flags); if (result == CURLE_OK) { - g_nlibcurl.malloc = m; - g_nlibcurl.free = f; - g_nlibcurl.realloc = r; - g_nlibcurl.strdup = s; - g_nlibcurl.calloc = c; + g_nlibcurl.malloc = malloc_callback; + g_nlibcurl.free = free_callback; + g_nlibcurl.realloc = realloc_callback; + g_nlibcurl.strdup = strdup_callback; + g_nlibcurl.calloc = calloc_callback; } } - - cemuLog_logDebug(LogType::Force, "curl_global_init_mem(0x{:x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}) -> 0x{:08x}", flags, m.GetMPTR(), f.GetMPTR(), r.GetMPTR(), s.GetMPTR(), c.GetMPTR(), result); - osLib_returnFromFunction(hCPU, result); + return result; } void load() { - osLib_addFunction("nlibcurl", "curl_global_init_mem", export_curl_global_init_mem); - osLib_addFunction("nlibcurl", "curl_global_init", export_curl_global_init); + cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::Force); + cafeExportRegister("nlibcurl", curl_global_init, LogType::Force); - osLib_addFunction("nlibcurl", "curl_slist_append", export_curl_slist_append); - osLib_addFunction("nlibcurl", "curl_slist_free_all", export_curl_slist_free_all); + cafeExportRegister("nlibcurl", curl_slist_append, LogType::Force); + cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::Force); osLib_addFunction("nlibcurl", "curl_easy_strerror", export_curl_easy_strerror); osLib_addFunction("nlibcurl", "curl_share_init", export_curl_share_init); osLib_addFunction("nlibcurl", "curl_share_setopt", export_curl_share_setopt); osLib_addFunction("nlibcurl", "curl_share_cleanup", export_curl_share_cleanup); + cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::Force); osLib_addFunction("nlibcurl", "curl_multi_init", export_curl_multi_init); osLib_addFunction("nlibcurl", "curl_multi_add_handle", export_curl_multi_add_handle); osLib_addFunction("nlibcurl", "curl_multi_perform", export_curl_multi_perform); @@ -1377,12 +1531,14 @@ void load() osLib_addFunction("nlibcurl", "curl_multi_cleanup", export_curl_multi_cleanup); osLib_addFunction("nlibcurl", "curl_multi_timeout", export_curl_multi_timeout); - osLib_addFunction("nlibcurl", "curl_easy_init", export_curl_easy_init); - osLib_addFunction("nlibcurl", "mw_curl_easy_init", export_curl_easy_init); + cafeExportRegister("nlibcurl", curl_easy_init, LogType::Force); osLib_addFunction("nlibcurl", "curl_easy_reset", export_curl_easy_reset); osLib_addFunction("nlibcurl", "curl_easy_setopt", export_curl_easy_setopt); osLib_addFunction("nlibcurl", "curl_easy_getinfo", export_curl_easy_getinfo); - osLib_addFunction("nlibcurl", "curl_easy_perform", export_curl_easy_perform); + cafeExportRegister("nlibcurl", curl_easy_perform, LogType::Force); + + + osLib_addFunction("nlibcurl", "curl_easy_cleanup", export_curl_easy_cleanup); osLib_addFunction("nlibcurl", "curl_easy_pause", export_curl_easy_pause); } From 9c28a728e4c78594e8f274990c0ffe6a3fabc07f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 13 Apr 2024 10:43:13 +0200 Subject: [PATCH 067/130] prudp: Dont expect sessionId to match for PING+ACK Fixes friend service connection periodically timing-out on Pretendo. Seems that unlike Nintendo's servers, Pretendo doesn't set sessionId for PING ack packets. --- src/Cemu/nex/prudp.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 051e489..4ef50b1 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -452,9 +452,7 @@ prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, } else { -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); -#endif + cemu_assert_suspicious(); } } @@ -696,6 +694,8 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) if (currentConnectionState == STATE_CONNECTING) { lastPingTimestamp = prudpGetMSTimestamp(); + if(serverSessionId != 0) + cemuLog_logDebug(LogType::Force, "PRUDP: ServerSessionId is already set"); serverSessionId = incomingPacket->sessionId; currentConnectionState = STATE_CONNECTED; //printf("Connection established. ClientSession %02x ServerSession %02x\n", clientSessionId, serverSessionId); @@ -763,7 +763,6 @@ bool prudpClient::update() sint32 r = recvfrom(socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); if (r >= 0) { - //printf("RECV 0x%04x byte\n", r); // todo: Verify sender (receiveFrom) // calculate packet size sint32 pIdx = 0; @@ -772,18 +771,25 @@ bool prudpClient::update() sint32 packetLength = prudpPacket::calculateSizeFromPacketData(receiveBuffer + pIdx, r - pIdx); if (packetLength <= 0 || (pIdx + packetLength) > r) { - //printf("Invalid packet length\n"); + cemuLog_logDebug(LogType::Force, "PRUDP: Invalid packet length"); break; } prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); + pIdx += packetLength; if (incomingPacket->hasError()) { + cemuLog_logDebug(LogType::Force, "PRUDP: Packet error"); delete incomingPacket; break; } - if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) + // sessionId validation is complicated and depends on specific flags and type combinations. It does not seem to cover all packet types + bool validateSessionId = serverSessionId != 0; + if((incomingPacket->type == prudpPacket::TYPE_PING && (incomingPacket->flags&prudpPacket::FLAG_ACK) != 0)) + validateSessionId = false; // PING + ack -> disable session id validation. Pretendo's friend server sends PING ack packets without setting the sessionId (it is 0) + if (validateSessionId && incomingPacket->sessionId != serverSessionId) { + cemuLog_logDebug(LogType::Force, "PRUDP: Invalid session id"); delete incomingPacket; continue; // different session } From 6ea42d958ca349944485e04b67d675954892dde3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 13 Apr 2024 11:03:02 +0200 Subject: [PATCH 068/130] nlibcurl: Fix compile error --- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 318e658..0268c7d 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -386,7 +386,12 @@ uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) return result; } -int curl_closesocket(void *clientp, curl_socket_t item); +static int curl_closesocket(void *clientp, curl_socket_t item) +{ + nsysnet_notifyCloseSharedSocket((SOCKET)item); + closesocket(item); + return 0; +} void _curl_set_default_parameters(CURL_t* curl) { @@ -843,13 +848,6 @@ void export_curl_share_cleanup(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -static int curl_closesocket(void *clientp, curl_socket_t item) -{ - nsysnet_notifyCloseSharedSocket((SOCKET)item); - closesocket(item); - return 0; -} - CURL_t* curl_easy_init() { if (g_nlibcurl.initialized == 0) From 10c78ecccef14b352f362db03f6d91f70e9d3e74 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:20:39 +0200 Subject: [PATCH 069/130] CI: don't strip debug symbols from binary in AppImage (#1175) --- dist/linux/appimage.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index 60a5032..7bfc470 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -33,6 +33,7 @@ chmod +x AppDir/usr/bin/Cemu cp /usr/lib/x86_64-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync" +export NO_STRIP=1 ./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \ --appdir="${GITHUB_WORKSPACE}"/AppDir/ \ -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \ From ee36992bd6f1e16f93cd847c293a04555139012d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:38:20 +0200 Subject: [PATCH 070/130] prudp: Improve ping and ack logic Fixes the issue where the friend service connection would always timeout on Pretendo servers The individual changes are: - Outgoing ping packets now use their own incrementing sequenceId (matches official NEX behavior) - If the server sends us a ping packet with NEEDS_ACK, we now respond - Misc smaller refactoring and code clean up - Added PRUDP as a separate logging option --- src/Cemu/Logging/CemuLogging.h | 1 + src/Cemu/nex/nexFriends.cpp | 3 +- src/Cemu/nex/prudp.cpp | 172 +++++++++++++++++++++------------ src/Cemu/nex/prudp.h | 10 +- src/gui/MainWindow.cpp | 1 + 5 files changed, 122 insertions(+), 65 deletions(-) diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index e789c2e..44e8936 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -42,6 +42,7 @@ enum class LogType : sint32 ProcUi = 39, + PRUDP = 40, }; template <> diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index ae87ce4..927418c 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -221,7 +221,8 @@ NexFriends::NexFriends(uint32 authServerIp, uint16 authServerPort, const char* a NexFriends::~NexFriends() { - nexCon->destroy(); + if(nexCon) + nexCon->destroy(); } void NexFriends::doAsyncLogin() diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 4ef50b1..7c01bec 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -219,7 +219,7 @@ prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 this->type = type; this->flags = flags; this->sessionId = sessionId; - this->sequenceId = sequenceId; + this->m_sequenceId = sequenceId; this->specifiedPacketSignature = packetSignature; this->streamSettings = streamSettings; this->fragmentIndex = 0; @@ -257,7 +257,7 @@ sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) *(uint16*)(packetBuffer + 0x02) = typeAndFlags; *(uint8*)(packetBuffer + 0x04) = sessionId; *(uint32*)(packetBuffer + 0x05) = packetSignature(); - *(uint16*)(packetBuffer + 0x09) = sequenceId; + *(uint16*)(packetBuffer + 0x09) = m_sequenceId; writeIndex = 0xB; // variable fields if (this->type == TYPE_SYN) @@ -286,7 +286,9 @@ sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) // no data } else - assert_dbg(); + { + cemu_assert_suspicious(); + } // checksum *(uint8*)(packetBuffer + writeIndex) = calculateChecksum(packetBuffer, writeIndex); writeIndex++; @@ -585,7 +587,7 @@ void prudpClient::acknowledgePacket(uint16 sequenceId) auto it = std::begin(list_packetsWithAckReq); while (it != std::end(list_packetsWithAckReq)) { - if (it->packet->sequenceId == sequenceId) + if (it->packet->GetSequenceId() == sequenceId) { delete it->packet; list_packetsWithAckReq.erase(it); @@ -634,16 +636,45 @@ sint32 prudpClient::kerberosEncryptData(uint8* input, sint32 length, uint8* outp void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) { + if(incomingPacket->type == prudpPacket::TYPE_PING) + { + if (incomingPacket->flags&prudpPacket::FLAG_ACK) + { + // ack for our ping packet + if(incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected ping packet with both ACK and NEED_ACK set"); + if(m_unacknowledgedPingCount > 0) + { + if(incomingPacket->sequenceId == m_outgoingSequenceId_ping) + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK (unacknowledged count: {})", m_unacknowledgedPingCount); + m_unacknowledgedPingCount = 0; + } + else + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK with wrong sequenceId (expected: {}, received: {})", m_outgoingSequenceId_ping, incomingPacket->sequenceId); + } + } + else + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK which we dont need"); + } + } + else if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + { + // other side is asking for ping ack + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet with NEED_ACK set. Sending ACK back"); + cemu_assert_debug(incomingPacket->packetData.empty()); // todo - echo data? + prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); + directSendPacket(&ackPacket, dstIp, dstPort); + } + delete incomingPacket; + return; + } + // handle general packet ACK if (incomingPacket->flags&prudpPacket::FLAG_ACK) { - // ack packet acknowledgePacket(incomingPacket->sequenceId); - if ((incomingPacket->type == prudpPacket::TYPE_DATA || incomingPacket->type == prudpPacket::TYPE_PING) && incomingPacket->packetData.empty()) - { - // ack packet - delete incomingPacket; - return; - } } // special cases if (incomingPacket->type == prudpPacket::TYPE_SYN) @@ -680,7 +711,7 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) // set packet specific data (client connection signature) conPacket->setData((uint8*)&this->clientConnectionSignature, sizeof(uint32)); } - // sent packet + // send packet queuePacket(conPacket, dstIp, dstPort); // remember con packet as sent hasSentCon = true; @@ -694,17 +725,28 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) if (currentConnectionState == STATE_CONNECTING) { lastPingTimestamp = prudpGetMSTimestamp(); - if(serverSessionId != 0) - cemuLog_logDebug(LogType::Force, "PRUDP: ServerSessionId is already set"); + cemu_assert_debug(serverSessionId == 0); serverSessionId = incomingPacket->sessionId; currentConnectionState = STATE_CONNECTED; - //printf("Connection established. ClientSession %02x ServerSession %02x\n", clientSessionId, serverSessionId); + cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", clientSessionId, serverSessionId); } delete incomingPacket; return; } else if (incomingPacket->type == prudpPacket::TYPE_DATA) { + // send ack back if requested + if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + { + prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); + directSendPacket(&ackPacket, dstIp, dstPort); + } + // skip data packets without payload + if (incomingPacket->packetData.empty()) + { + delete incomingPacket; + return; + } // verify some values uint16 seqDist = incomingPacket->sequenceId - incomingSequenceId; if (seqDist >= 0xC000) @@ -719,7 +761,7 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) if (it->sequenceId == incomingPacket->sequenceId) { // already queued (should check other values too, like packet type?) - cemuLog_logDebug(LogType::Force, "Duplicate PRUDP packet received"); + cemuLog_log(LogType::PRUDP, "Duplicate PRUDP packet received"); delete incomingPacket; return; } @@ -738,21 +780,12 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) delete incomingPacket; return; } - - if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK && incomingPacket->type == prudpPacket::TYPE_DATA) - { - // send ack back - prudpPacket* ackPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); - queuePacket(ackPacket, dstIp, dstPort); - } } bool prudpClient::update() { if (currentConnectionState == STATE_DISCONNECTED) - { return false; - } uint32 currentTimestamp = prudpGetMSTimestamp(); // check for incoming packets uint8 receiveBuffer[4096]; @@ -771,25 +804,20 @@ bool prudpClient::update() sint32 packetLength = prudpPacket::calculateSizeFromPacketData(receiveBuffer + pIdx, r - pIdx); if (packetLength <= 0 || (pIdx + packetLength) > r) { - cemuLog_logDebug(LogType::Force, "PRUDP: Invalid packet length"); + cemuLog_log(LogType::Force, "[PRUDP] Invalid packet length"); break; } prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); - pIdx += packetLength; if (incomingPacket->hasError()) { - cemuLog_logDebug(LogType::Force, "PRUDP: Packet error"); + cemuLog_log(LogType::Force, "[PRUDP] Packet error"); delete incomingPacket; break; } - // sessionId validation is complicated and depends on specific flags and type combinations. It does not seem to cover all packet types - bool validateSessionId = serverSessionId != 0; - if((incomingPacket->type == prudpPacket::TYPE_PING && (incomingPacket->flags&prudpPacket::FLAG_ACK) != 0)) - validateSessionId = false; // PING + ack -> disable session id validation. Pretendo's friend server sends PING ack packets without setting the sessionId (it is 0) - if (validateSessionId && incomingPacket->sessionId != serverSessionId) + if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) { - cemuLog_logDebug(LogType::Force, "PRUDP: Invalid session id"); + cemuLog_log(LogType::PRUDP, "[PRUDP] Invalid session id"); delete incomingPacket; continue; // different session } @@ -816,13 +844,44 @@ bool prudpClient::update() } } // check if we need to send another ping - if (currentConnectionState == STATE_CONNECTED && (currentTimestamp - lastPingTimestamp) >= 20000) + if (currentConnectionState == STATE_CONNECTED) { - // send ping - prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, this->clientSessionId, this->outgoingSequenceId, serverConnectionSignature); - this->outgoingSequenceId++; // increase since prudpPacket::FLAG_RELIABLE is set (note: official Wii U friends client sends ping packets without FLAG_RELIABLE) - queuePacket(pingPacket, dstIp, dstPort); - lastPingTimestamp = currentTimestamp; + if(m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack + { + // we are waiting for the ack of the previous ping, but it hasn't arrived yet so send another ping packet + if((currentTimestamp - lastPingTimestamp) >= 1500) + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Resending ping packet (no ack received)"); + if(m_unacknowledgedPingCount >= 10) + { + // too many unacknowledged pings, assume the connection is dead + currentConnectionState = STATE_DISCONNECTED; + cemuLog_log(LogType::PRUDP, "PRUDP: Connection did not receive a ping response in a while. Assuming disconnect"); + return false; + } + // resend the ping packet + prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); + directSendPacket(pingPacket, dstIp, dstPort); + m_unacknowledgedPingCount++; + delete pingPacket; + lastPingTimestamp = currentTimestamp; + } + } + else + { + if((currentTimestamp - lastPingTimestamp) >= 20000) + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping+1); + // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId + // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack + this->m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 + prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); + directSendPacket(pingPacket, dstIp, dstPort); + m_unacknowledgedPingCount++; + delete pingPacket; + lastPingTimestamp = currentTimestamp; + } + } } return false; } @@ -844,6 +903,7 @@ void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) { if (packet->requiresAck()) { + cemu_assert_debug(packet->GetType() != prudpPacket::TYPE_PING); // ping packets use their own logic for acks, dont queue them // remember this packet until we receive the ack prudpAckRequired_t ackRequired = { 0 }; ackRequired.packet = packet; @@ -861,16 +921,18 @@ void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) void prudpClient::sendDatagram(uint8* input, sint32 length, bool reliable) { - if (reliable == false) + cemu_assert_debug(reliable); // non-reliable packets require testing + if(length >= 0x300) { - assert_dbg(); // todo + cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long"); } - if (length >= 0x300) - assert_dbg(); // too long, need to split into multiple fragments - // single fragment data packet - prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, clientSessionId, outgoingSequenceId, 0); - outgoingSequenceId++; + uint16 flags = prudpPacket::FLAG_NEED_ACK; + if(reliable) + flags |= prudpPacket::FLAG_RELIABLE; + prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, flags, clientSessionId, outgoingSequenceId, 0); + if(reliable) + outgoingSequenceId++; packet->setFragmentIndex(0); packet->setData(input, length); queuePacket(packet, dstIp, dstPort); @@ -919,13 +981,7 @@ sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) } delete incomingPacket; // remove packet from queue - sint32 size = (sint32)queue_incomingPackets.size(); - size--; - for (sint32 i = 0; i < size; i++) - { - queue_incomingPackets[i] = queue_incomingPackets[i + 1]; - } - queue_incomingPackets.resize(size); + queue_incomingPackets.erase(queue_incomingPackets.begin()); // advance expected sequence id this->incomingSequenceId++; return datagramLen; @@ -975,13 +1031,7 @@ sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) delete incomingPacket; } // remove packets from queue - sint32 size = (sint32)queue_incomingPackets.size(); - size -= chainLength; - for (sint32 i = 0; i < size; i++) - { - queue_incomingPackets[i] = queue_incomingPackets[i + chainLength]; - } - queue_incomingPackets.resize(size); + queue_incomingPackets.erase(queue_incomingPackets.begin(), queue_incomingPackets.begin() + chainLength); this->incomingSequenceId += chainLength; return writeIndex; } diff --git a/src/Cemu/nex/prudp.h b/src/Cemu/nex/prudp.h index aa68f4f..5ed5bcb 100644 --- a/src/Cemu/nex/prudp.h +++ b/src/Cemu/nex/prudp.h @@ -71,15 +71,14 @@ public: void setData(uint8* data, sint32 length); void setFragmentIndex(uint8 fragmentIndex); sint32 buildData(uint8* output, sint32 maxLength); + uint8 GetType() const { return type; } + uint16 GetSequenceId() const { return m_sequenceId; } private: uint32 packetSignature(); uint8 calculateChecksum(uint8* data, sint32 length); -public: - uint16 sequenceId; - private: uint8 src; uint8 dst; @@ -91,6 +90,8 @@ private: prudpStreamSettings_t* streamSettings; std::vector<uint8> packetData; bool isEncrypted; + uint16 m_sequenceId{0}; + }; class prudpIncomingPacket @@ -186,6 +187,9 @@ private: uint16 outgoingSequenceId; uint16 incomingSequenceId; + uint16 m_outgoingSequenceId_ping{0}; + uint8 m_unacknowledgedPingCount{0}; + uint8 clientSessionId; uint8 serverSessionId; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4d2fb47..da57870 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2232,6 +2232,7 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("&PRUDP (for NN FP)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); From e2f972571906b909ad19cb922b1fa5549e3522da Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:22:28 +0200 Subject: [PATCH 071/130] prudp: Code cleanup --- src/Cemu/nex/nex.cpp | 42 +-- src/Cemu/nex/prudp.cpp | 642 ++++++++++++++++++++--------------------- src/Cemu/nex/prudp.h | 148 +++++----- 3 files changed, 410 insertions(+), 422 deletions(-) diff --git a/src/Cemu/nex/nex.cpp b/src/Cemu/nex/nex.cpp index d085750..973a439 100644 --- a/src/Cemu/nex/nex.cpp +++ b/src/Cemu/nex/nex.cpp @@ -106,7 +106,7 @@ nexService::nexService() nexService::nexService(prudpClient* con) : nexService() { - if (con->isConnected() == false) + if (con->IsConnected() == false) cemu_assert_suspicious(); this->conNexService = con; bufferReceive = std::vector<uint8>(1024 * 4); @@ -191,7 +191,7 @@ void nexService::processQueuedRequest(queuedRequest_t* queuedRequest) uint32 callId = _currentCallId; _currentCallId++; // check state of connection - if (conNexService->getConnectionState() != prudpClient::STATE_CONNECTED) + if (conNexService->GetConnectionState() != prudpClient::ConnectionState::Connected) { nexServiceResponse_t response = { 0 }; response.isSuccessful = false; @@ -214,7 +214,7 @@ void nexService::processQueuedRequest(queuedRequest_t* queuedRequest) assert_dbg(); memcpy((packetBuffer + 0x0D), &queuedRequest->parameterData.front(), queuedRequest->parameterData.size()); sint32 length = 0xD + (sint32)queuedRequest->parameterData.size(); - conNexService->sendDatagram(packetBuffer, length, true); + conNexService->SendDatagram(packetBuffer, length, true); // remember request nexActiveRequestInfo_t requestInfo = { 0 }; requestInfo.callId = callId; @@ -299,13 +299,13 @@ void nexService::registerForAsyncProcessing() void nexService::updateTemporaryConnections() { // check for connection - conNexService->update(); - if (conNexService->isConnected()) + conNexService->Update(); + if (conNexService->IsConnected()) { if (connectionState == STATE_CONNECTING) connectionState = STATE_CONNECTED; } - if (conNexService->getConnectionState() == prudpClient::STATE_DISCONNECTED) + if (conNexService->GetConnectionState() == prudpClient::ConnectionState::Disconnected) connectionState = STATE_DISCONNECTED; } @@ -356,18 +356,18 @@ void nexService::sendRequestResponse(nexServiceRequest_t* request, uint32 errorC // update length field *(uint32*)response.getDataPtr() = response.getWriteIndex()-4; if(request->nex->conNexService) - request->nex->conNexService->sendDatagram(response.getDataPtr(), response.getWriteIndex(), true); + request->nex->conNexService->SendDatagram(response.getDataPtr(), response.getWriteIndex(), true); } void nexService::updateNexServiceConnection() { - if (conNexService->getConnectionState() == prudpClient::STATE_DISCONNECTED) + if (conNexService->GetConnectionState() == prudpClient::ConnectionState::Disconnected) { this->connectionState = STATE_DISCONNECTED; return; } - conNexService->update(); - sint32 datagramLen = conNexService->receiveDatagram(bufferReceive); + conNexService->Update(); + sint32 datagramLen = conNexService->ReceiveDatagram(bufferReceive); if (datagramLen > 0) { if (nexIsRequest(&bufferReceive[0], datagramLen)) @@ -454,12 +454,12 @@ bool _extractStationUrlParamValue(const char* urlStr, const char* paramName, cha return false; } -void nexServiceAuthentication_parseStationURL(char* urlStr, stationUrl_t* stationUrl) +void nexServiceAuthentication_parseStationURL(char* urlStr, prudpStationUrl* stationUrl) { // example: // prudps:/address=34.210.xxx.xxx;port=60181;CID=1;PID=2;sid=1;stream=10;type=2 - memset(stationUrl, 0, sizeof(stationUrl_t)); + memset(stationUrl, 0, sizeof(prudpStationUrl)); char optionValue[128]; if (_extractStationUrlParamValue(urlStr, "address", optionValue, sizeof(optionValue))) @@ -499,7 +499,7 @@ typedef struct sint32 kerberosTicketSize; uint8 kerberosTicket2[4096]; sint32 kerberosTicket2Size; - stationUrl_t server; + prudpStationUrl server; // progress info bool hasError; bool done; @@ -611,18 +611,18 @@ void nexServiceSecure_handleResponse_RegisterEx(nexService* nex, nexServiceRespo return; } -nexService* nex_secureLogin(authServerInfo_t* authServerInfo, const char* accessKey, const char* nexToken) +nexService* nex_secureLogin(prudpAuthServerInfo* authServerInfo, const char* accessKey, const char* nexToken) { prudpClient* prudpSecureSock = new prudpClient(authServerInfo->server.ip, authServerInfo->server.port, accessKey, authServerInfo); // wait until connected while (true) { - prudpSecureSock->update(); - if (prudpSecureSock->isConnected()) + prudpSecureSock->Update(); + if (prudpSecureSock->IsConnected()) { break; } - if (prudpSecureSock->getConnectionState() == prudpClient::STATE_DISCONNECTED) + if (prudpSecureSock->GetConnectionState() == prudpClient::ConnectionState::Disconnected) { // timeout or disconnected cemuLog_log(LogType::Force, "NEX: Secure login connection time-out"); @@ -638,7 +638,7 @@ nexService* nex_secureLogin(authServerInfo_t* authServerInfo, const char* access nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); char clientStationUrl[256]; - sprintf(clientStationUrl, "prudp:/port=%u;natf=0;natm=0;pmp=0;sid=15;type=2;upnp=0", (uint32)nex->getPRUDPConnection()->getSourcePort()); + sprintf(clientStationUrl, "prudp:/port=%u;natf=0;natm=0;pmp=0;sid=15;type=2;upnp=0", (uint32)nex->getPRUDPConnection()->GetSourcePort()); // station url list packetBuffer.writeU32(1); packetBuffer.writeString(clientStationUrl); @@ -737,9 +737,9 @@ nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServer return nullptr; } // auth info - auto authServerInfo = std::make_unique<authServerInfo_t>(); + auto authServerInfo = std::make_unique<prudpAuthServerInfo>(); // decrypt ticket - RC4Ctx_t rc4Ticket; + RC4Ctx rc4Ticket; RC4_initCtx(&rc4Ticket, kerberosKey, 16); RC4_transform(&rc4Ticket, nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, nexAuthService.kerberosTicket2); nexPacketBuffer packetKerberosTicket(nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, false); @@ -756,7 +756,7 @@ nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServer memcpy(authServerInfo->kerberosKey, kerberosKey, 16); memcpy(authServerInfo->secureKey, secureKey, 16); - memcpy(&authServerInfo->server, &nexAuthService.server, sizeof(stationUrl_t)); + memcpy(&authServerInfo->server, &nexAuthService.server, sizeof(prudpStationUrl)); authServerInfo->userPid = pid; return nex_secureLogin(authServerInfo.get(), accessKey, nexToken); diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 7c01bec..5c773fe 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -1,72 +1,57 @@ #include "prudp.h" #include "util/crypto/md5.h" -#include<bitset> -#include<random> +#include <bitset> +#include <random> #include <boost/random/uniform_int.hpp> -void swap(unsigned char *a, unsigned char *b) +static void KSA(unsigned char* key, int keyLen, unsigned char* S) { - int tmp = *a; - *a = *b; - *b = tmp; -} - -void KSA(unsigned char *key, int keyLen, unsigned char *S) -{ - int j = 0; - for (int i = 0; i < RC4_N; i++) S[i] = i; - - for (int i = 0; i < RC4_N; i++) + int j = 0; + for (int i = 0; i < RC4_N; i++) { j = (j + S[i] + key[i % keyLen]) % RC4_N; - - swap(&S[i], &S[j]); + std::swap(S[i], S[j]); } } -void PRGA(unsigned char *S, unsigned char* input, int len, unsigned char* output) +static void PRGA(unsigned char* S, unsigned char* input, int len, unsigned char* output) { - int i = 0; - int j = 0; - - for (size_t n = 0; n < len; n++) + for (size_t n = 0; n < len; n++) { - i = (i + 1) % RC4_N; - j = (j + S[i]) % RC4_N; - - swap(&S[i], &S[j]); + int i = (i + 1) % RC4_N; + int j = (j + S[i]) % RC4_N; + std::swap(S[i], S[j]); int rnd = S[(S[i] + S[j]) % RC4_N]; - output[n] = rnd ^ input[n]; } } -void RC4(char* key, unsigned char* input, int len, unsigned char* output) +static void RC4(char* key, unsigned char* input, int len, unsigned char* output) { unsigned char S[RC4_N]; KSA((unsigned char*)key, (int)strlen(key), S); PRGA(S, input, len, output); } -void RC4_initCtx(RC4Ctx_t* rc4Ctx, const char* key) +void RC4_initCtx(RC4Ctx* rc4Ctx, const char* key) { rc4Ctx->i = 0; rc4Ctx->j = 0; KSA((unsigned char*)key, (int)strlen(key), rc4Ctx->S); } -void RC4_initCtx(RC4Ctx_t* rc4Ctx, unsigned char* key, int keyLen) +void RC4_initCtx(RC4Ctx* rc4Ctx, unsigned char* key, int keyLen) { rc4Ctx->i = 0; rc4Ctx->j = 0; KSA(key, keyLen, rc4Ctx->S); } -void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned char* output) +void RC4_transform(RC4Ctx* rc4Ctx, unsigned char* input, int len, unsigned char* output) { int i = rc4Ctx->i; int j = rc4Ctx->j; @@ -75,13 +60,10 @@ void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned cha { i = (i + 1) % RC4_N; j = (j + rc4Ctx->S[i]) % RC4_N; - - swap(&rc4Ctx->S[i], &rc4Ctx->S[j]); + std::swap(rc4Ctx->S[i], rc4Ctx->S[j]); int rnd = rc4Ctx->S[(rc4Ctx->S[i] + rc4Ctx->S[j]) % RC4_N]; - output[n] = rnd ^ input[n]; } - rc4Ctx->i = i; rc4Ctx->j = j; } @@ -91,34 +73,14 @@ uint32 prudpGetMSTimestamp() return GetTickCount(); } -std::bitset<10000> _portUsageMask; - -uint16 getRandomSrcPRUDPPort() -{ - while (true) - { - sint32 p = rand() % 10000; - if (_portUsageMask.test(p)) - continue; - _portUsageMask.set(p); - return 40000 + p; - } - return 0; -} - -void releasePRUDPPort(uint16 port) -{ - uint32 bitIndex = port - 40000; - _portUsageMask.reset(bitIndex); -} - std::mt19937_64 prudpRG(GetTickCount()); -// workaround for static asserts when using uniform_int_distribution -boost::random::uniform_int_distribution<int> prudpDis8(0, 0xFF); +// workaround for static asserts when using uniform_int_distribution (see https://github.com/cemu-project/Cemu/issues/48) +boost::random::uniform_int_distribution<int> prudpRandomDistribution8(0, 0xFF); +boost::random::uniform_int_distribution<int> prudpRandomDistributionPortGen(0, 10000); uint8 prudp_generateRandomU8() { - return prudpDis8(prudpRG); + return prudpRandomDistribution8(prudpRG); } uint32 prudp_generateRandomU32() @@ -133,7 +95,29 @@ uint32 prudp_generateRandomU32() return v; } -uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) +std::bitset<10000> _portUsageMask; + +static uint16 AllocateRandomSrcPRUDPPort() +{ + while (true) + { + sint32 p = prudpRandomDistributionPortGen(prudpRG); + if (_portUsageMask.test(p)) + continue; + _portUsageMask.set(p); + return 40000 + p; + } +} + +static void ReleasePRUDPSrcPort(uint16 port) +{ + cemu_assert_debug(port >= 40000); + uint32 bitIndex = port - 40000; + cemu_assert_debug(_portUsageMask.test(bitIndex)); + _portUsageMask.reset(bitIndex); +} + +static uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) { uint32 checksum32 = 0; for (sint32 i = 0; i < length / 4; i++) @@ -141,7 +125,7 @@ uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) checksum32 += *(uint32*)(data + i * 4); } uint8 checksum = checksumBase; - for (sint32 i = length&(~3); i < length; i++) + for (sint32 i = length & (~3); i < length; i++) { checksum += data[i]; } @@ -161,16 +145,16 @@ sint32 prudpPacket::calculateSizeFromPacketData(uint8* data, sint32 length) return 0; // get flags fields uint16 typeAndFlags = *(uint16*)(data + 0x02); - uint16 type = (typeAndFlags&0xF); + uint16 type = (typeAndFlags & 0xF); uint16 flags = (typeAndFlags >> 4); - if ((flags&FLAG_HAS_SIZE) == 0) + if ((flags & FLAG_HAS_SIZE) == 0) return length; // without a size field, we cant calculate the length sint32 calculatedSize; if (type == TYPE_SYN) { if (length < (0xB + 0x4 + 2)) return 0; - uint16 payloadSize = *(uint16*)(data+0xB+0x4); + uint16 payloadSize = *(uint16*)(data + 0xB + 0x4); calculatedSize = 0xB + 0x4 + 2 + (sint32)payloadSize + 1; // base header + connection signature (SYN param) + payloadSize field + checksum after payload if (calculatedSize > length) return 0; @@ -212,7 +196,7 @@ sint32 prudpPacket::calculateSizeFromPacketData(uint8* data, sint32 length) return length; } -prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature) +prudpPacket::prudpPacket(prudpStreamSettings* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature) { this->src = src; this->dst = dst; @@ -228,7 +212,7 @@ prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 bool prudpPacket::requiresAck() { - return (flags&FLAG_NEED_ACK) != 0; + return (flags & FLAG_NEED_ACK) != 0; } sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) @@ -352,7 +336,8 @@ prudpIncomingPacket::prudpIncomingPacket() streamSettings = nullptr; } -prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, uint8* data, sint32 length) : prudpIncomingPacket() +prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings* streamSettings, uint8* data, sint32 length) + : prudpIncomingPacket() { if (length < 0xB + 1) { @@ -418,7 +403,7 @@ prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, bool hasPayloadSize = (this->flags & prudpPacket::FLAG_HAS_SIZE) != 0; // verify length - if ((length-readIndex) < 1+(hasPayloadSize?2:0)) + if ((length - readIndex) < 1 + (hasPayloadSize ? 2 : 0)) { // too short isInvalid = true; @@ -475,57 +460,45 @@ void prudpIncomingPacket::decrypt() RC4_transform(&streamSettings->rc4Server, &packetData.front(), (int)packetData.size(), &packetData.front()); } -#define PRUDP_VPORT(__streamType, __port) (((__streamType)<<4) | (__port)) +#define PRUDP_VPORT(__streamType, __port) (((__streamType) << 4) | (__port)) prudpClient::prudpClient() { - currentConnectionState = STATE_CONNECTING; - serverConnectionSignature = 0; - clientConnectionSignature = 0; - hasSentCon = false; - outgoingSequenceId = 0; - incomingSequenceId = 0; + m_currentConnectionState = ConnectionState::Connecting; + m_serverConnectionSignature = 0; + m_clientConnectionSignature = 0; + m_incomingSequenceId = 0; - clientSessionId = 0; - serverSessionId = 0; - - isSecureConnection = false; + m_clientSessionId = 0; + m_serverSessionId = 0; } -prudpClient::~prudpClient() +prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) + : prudpClient() { - if (srcPort != 0) - { - releasePRUDPPort(srcPort); - closesocket(socketUdp); - } -} - -prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) : prudpClient() -{ - this->dstIp = dstIp; - this->dstPort = dstPort; + m_dstIp = dstIp; + m_dstPort = dstPort; // get unused random source port for (sint32 tries = 0; tries < 5; tries++) { - srcPort = getRandomSrcPRUDPPort(); + m_srcPort = AllocateRandomSrcPRUDPPort(); // create and bind udp socket - socketUdp = socket(AF_INET, SOCK_DGRAM, 0); + m_socketUdp = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in udpServer; udpServer.sin_family = AF_INET; udpServer.sin_addr.s_addr = INADDR_ANY; - udpServer.sin_port = htons(srcPort); - if (bind(socketUdp, (struct sockaddr *)&udpServer, sizeof(udpServer)) == SOCKET_ERROR) + udpServer.sin_port = htons(m_srcPort); + if (bind(m_socketUdp, (struct sockaddr*)&udpServer, sizeof(udpServer)) == SOCKET_ERROR) { + ReleasePRUDPSrcPort(m_srcPort); + m_srcPort = 0; if (tries == 4) { cemuLog_log(LogType::Force, "PRUDP: Failed to bind UDP socket"); - currentConnectionState = STATE_DISCONNECTED; - srcPort = 0; + m_currentConnectionState = ConnectionState::Disconnected; return; } - releasePRUDPPort(srcPort); - closesocket(socketUdp); + closesocket(m_socketUdp); continue; } else @@ -533,79 +506,77 @@ prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) : prudpC } // set socket to non-blocking mode #if BOOST_OS_WINDOWS - u_long nonBlockingMode = 1; // 1 to enable non-blocking socket - ioctlsocket(socketUdp, FIONBIO, &nonBlockingMode); + u_long nonBlockingMode = 1; // 1 to enable non-blocking socket + ioctlsocket(m_socketUdp, FIONBIO, &nonBlockingMode); #else int flags = fcntl(socketUdp, F_GETFL); fcntl(socketUdp, F_SETFL, flags | O_NONBLOCK); #endif // generate frequently used parameters - this->vport_src = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); - this->vport_dst = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); + this->m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); + this->m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); // set stream settings uint8 checksumBase = 0; for (sint32 i = 0; key[i] != '\0'; i++) { checksumBase += key[i]; } - streamSettings.checksumBase = checksumBase; + m_streamSettings.checksumBase = checksumBase; MD5_CTX md5Ctx; MD5_Init(&md5Ctx); MD5_Update(&md5Ctx, key, (int)strlen(key)); - MD5_Final(streamSettings.accessKeyDigest, &md5Ctx); + MD5_Final(m_streamSettings.accessKeyDigest, &md5Ctx); // init stream ciphers - RC4_initCtx(&streamSettings.rc4Server, "CD&ML"); - RC4_initCtx(&streamSettings.rc4Client, "CD&ML"); + RC4_initCtx(&m_streamSettings.rc4Server, "CD&ML"); + RC4_initCtx(&m_streamSettings.rc4Client, "CD&ML"); // send syn packet - prudpPacket* synPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_SYN, prudpPacket::FLAG_NEED_ACK, 0, 0, 0); - queuePacket(synPacket, dstIp, dstPort); - outgoingSequenceId++; + SendCurrentHandshakePacket(); // set incoming sequence id to 1 - incomingSequenceId = 1; + m_incomingSequenceId = 1; } -prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, authServerInfo_t* authInfo) : prudpClient(dstIp, dstPort, key) +prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAuthServerInfo* authInfo) + : prudpClient(dstIp, dstPort, key) { - RC4_initCtx(&streamSettings.rc4Server, authInfo->secureKey, 16); - RC4_initCtx(&streamSettings.rc4Client, authInfo->secureKey, 16); - this->isSecureConnection = true; - memcpy(&this->authInfo, authInfo, sizeof(authServerInfo_t)); + RC4_initCtx(&m_streamSettings.rc4Server, authInfo->secureKey, 16); + RC4_initCtx(&m_streamSettings.rc4Client, authInfo->secureKey, 16); + this->m_isSecureConnection = true; + memcpy(&this->m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); } -bool prudpClient::isConnected() +prudpClient::~prudpClient() { - return currentConnectionState == STATE_CONNECTED; + if (m_srcPort != 0) + { + ReleasePRUDPSrcPort(m_srcPort); + closesocket(m_socketUdp); + } } -uint8 prudpClient::getConnectionState() +void prudpClient::AcknowledgePacket(uint16 sequenceId) { - return currentConnectionState; -} - -void prudpClient::acknowledgePacket(uint16 sequenceId) -{ - auto it = std::begin(list_packetsWithAckReq); - while (it != std::end(list_packetsWithAckReq)) + auto it = std::begin(m_dataPacketsWithAckReq); + while (it != std::end(m_dataPacketsWithAckReq)) { if (it->packet->GetSequenceId() == sequenceId) { delete it->packet; - list_packetsWithAckReq.erase(it); + m_dataPacketsWithAckReq.erase(it); return; } it++; } } -void prudpClient::sortIncomingDataPacket(prudpIncomingPacket* incomingPacket) +void prudpClient::SortIncomingDataPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket) { uint16 sequenceIdIncomingPacket = incomingPacket->sequenceId; // find insert index sint32 insertIndex = 0; - while (insertIndex < queue_incomingPackets.size() ) + while (insertIndex < m_incomingPacketQueue.size()) { - uint16 seqDif = sequenceIdIncomingPacket - queue_incomingPackets[insertIndex]->sequenceId; - if (seqDif&0x8000) + uint16 seqDif = sequenceIdIncomingPacket - m_incomingPacketQueue[insertIndex]->sequenceId; + if (seqDif & 0x8000) break; // negative seqDif -> insert before current element #ifdef CEMU_DEBUG_ASSERT if (seqDif == 0) @@ -613,39 +584,83 @@ void prudpClient::sortIncomingDataPacket(prudpIncomingPacket* incomingPacket) #endif insertIndex++; } - // insert - sint32 currentSize = (sint32)queue_incomingPackets.size(); - queue_incomingPackets.resize(currentSize+1); - for(sint32 i=currentSize; i>insertIndex; i--) + m_incomingPacketQueue.insert(m_incomingPacketQueue.begin() + insertIndex, std::move(incomingPacket)); + // debug check if packets are really ordered by sequence id +#ifdef CEMU_DEBUG_ASSERT + for (sint32 i = 1; i < m_incomingPacketQueue.size(); i++) { - queue_incomingPackets[i] = queue_incomingPackets[i - 1]; + uint16 seqDif = m_incomingPacketQueue[i]->sequenceId - m_incomingPacketQueue[i - 1]->sequenceId; + if (seqDif & 0x8000) + seqDif = -seqDif; + if (seqDif >= 0x8000) + assert_dbg(); } - queue_incomingPackets[insertIndex] = incomingPacket; +#endif } -sint32 prudpClient::kerberosEncryptData(uint8* input, sint32 length, uint8* output) +sint32 prudpClient::KerberosEncryptData(uint8* input, sint32 length, uint8* output) { - RC4Ctx_t rc4Kerberos; - RC4_initCtx(&rc4Kerberos, this->authInfo.secureKey, 16); + RC4Ctx rc4Kerberos; + RC4_initCtx(&rc4Kerberos, this->m_authInfo.secureKey, 16); memcpy(output, input, length); RC4_transform(&rc4Kerberos, output, length, output); // calculate and append hmac - hmacMD5(this->authInfo.secureKey, 16, output, length, output+length); + hmacMD5(this->m_authInfo.secureKey, 16, output, length, output + length); return length + 16; } -void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) +// (re)sends either CON or SYN based on what stage of the login we are at +// the sequenceId for both is hardcoded for both because we'll never send anything in between +void prudpClient::SendCurrentHandshakePacket() { - if(incomingPacket->type == prudpPacket::TYPE_PING) + if (!m_hasSynAck) { - if (incomingPacket->flags&prudpPacket::FLAG_ACK) + // send syn (with a fixed sequenceId of 0) + prudpPacket synPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_SYN, prudpPacket::FLAG_NEED_ACK, 0, 0, 0); + DirectSendPacket(&synPacket); + } + else + { + // send con (with a fixed sequenceId of 1) + prudpPacket conPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_CON, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, this->m_clientSessionId, 1, m_serverConnectionSignature); + if (this->m_isSecureConnection) + { + uint8 tempBuffer[512]; + nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); + conData.writeU32(this->m_clientConnectionSignature); + conData.writeBuffer(m_authInfo.secureTicket, m_authInfo.secureTicketLength); + // encrypted request data + uint8 requestData[4 * 3]; + uint8 requestDataEncrypted[4 * 3 + 0x10]; + *(uint32*)(requestData + 0x0) = m_authInfo.userPid; + *(uint32*)(requestData + 0x4) = m_authInfo.server.cid; + *(uint32*)(requestData + 0x8) = prudp_generateRandomU32(); // todo - check value + sint32 encryptedSize = KerberosEncryptData(requestData, sizeof(requestData), requestDataEncrypted); + conData.writeBuffer(requestDataEncrypted, encryptedSize); + conPacket.setData(conData.getDataPtr(), conData.getWriteIndex()); + } + else + { + conPacket.setData((uint8*)&this->m_clientConnectionSignature, sizeof(uint32)); + } + DirectSendPacket(&conPacket); + } + m_lastHandshakeTimestamp = prudpGetMSTimestamp(); + m_handshakeRetryCount++; +} + +void prudpClient::HandleIncomingPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket) +{ + if (incomingPacket->type == prudpPacket::TYPE_PING) + { + if (incomingPacket->flags & prudpPacket::FLAG_ACK) { // ack for our ping packet - if(incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected ping packet with both ACK and NEED_ACK set"); - if(m_unacknowledgedPingCount > 0) + if (m_unacknowledgedPingCount > 0) { - if(incomingPacket->sequenceId == m_outgoingSequenceId_ping) + if (incomingPacket->sequenceId == m_outgoingSequenceId_ping) { cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK (unacknowledged count: {})", m_unacknowledgedPingCount); m_unacknowledgedPingCount = 0; @@ -660,140 +675,127 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK which we dont need"); } } - else if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + else if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) { // other side is asking for ping ack cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet with NEED_ACK set. Sending ACK back"); - cemu_assert_debug(incomingPacket->packetData.empty()); // todo - echo data? - prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); - directSendPacket(&ackPacket, dstIp, dstPort); + prudpPacket ackPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->m_clientSessionId, incomingPacket->sequenceId, 0); + if(!incomingPacket->packetData.empty()) + ackPacket.setData(incomingPacket->packetData.data(), incomingPacket->packetData.size()); + DirectSendPacket(&ackPacket); } - delete incomingPacket; return; } - // handle general packet ACK - if (incomingPacket->flags&prudpPacket::FLAG_ACK) + else if (incomingPacket->type == prudpPacket::TYPE_SYN) { - acknowledgePacket(incomingPacket->sequenceId); - } - // special cases - if (incomingPacket->type == prudpPacket::TYPE_SYN) - { - if (hasSentCon == false && incomingPacket->hasData && incomingPacket->packetData.size() == 4) + // syn packet from server is expected to have ACK set + if (!(incomingPacket->flags & prudpPacket::FLAG_ACK)) { - this->serverConnectionSignature = *(uint32*)&incomingPacket->packetData.front(); - this->clientSessionId = prudp_generateRandomU8(); - // generate client session id - this->clientConnectionSignature = prudp_generateRandomU32(); - // send con packet - prudpPacket* conPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_CON, prudpPacket::FLAG_NEED_ACK|prudpPacket::FLAG_RELIABLE, this->clientSessionId, outgoingSequenceId, serverConnectionSignature); - outgoingSequenceId++; - - if (this->isSecureConnection) - { - // set packet specific data (client connection signature) - uint8 tempBuffer[512]; - nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); - conData.writeU32(this->clientConnectionSignature); - conData.writeBuffer(authInfo.secureTicket, authInfo.secureTicketLength); - // encrypted request data - uint8 requestData[4 * 3]; - uint8 requestDataEncrypted[4 * 3 + 0x10]; - *(uint32*)(requestData + 0x0) = authInfo.userPid; - *(uint32*)(requestData + 0x4) = authInfo.server.cid; - *(uint32*)(requestData + 0x8) = prudp_generateRandomU32(); // todo - check value - sint32 encryptedSize = kerberosEncryptData(requestData, sizeof(requestData), requestDataEncrypted); - conData.writeBuffer(requestDataEncrypted, encryptedSize); - conPacket->setData(conData.getDataPtr(), conData.getWriteIndex()); - } - else - { - // set packet specific data (client connection signature) - conPacket->setData((uint8*)&this->clientConnectionSignature, sizeof(uint32)); - } - // send packet - queuePacket(conPacket, dstIp, dstPort); - // remember con packet as sent - hasSentCon = true; + cemuLog_log(LogType::Force, "[PRUDP] Received SYN packet without ACK flag set"); // always log this + return; } - delete incomingPacket; + if (m_hasSynAck || !incomingPacket->hasData || incomingPacket->packetData.size() != 4) + { + // syn already acked or not a valid syn packet + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected SYN packet"); + return; + } + m_hasSynAck = true; + this->m_serverConnectionSignature = *(uint32*)&incomingPacket->packetData.front(); + // generate client session id and connection signature + this->m_clientSessionId = prudp_generateRandomU8(); + this->m_clientConnectionSignature = prudp_generateRandomU32(); + // send con packet + m_handshakeRetryCount = 0; + SendCurrentHandshakePacket(); return; } else if (incomingPacket->type == prudpPacket::TYPE_CON) { - // connected! - if (currentConnectionState == STATE_CONNECTING) + if (!m_hasSynAck || m_hasConAck) { - lastPingTimestamp = prudpGetMSTimestamp(); - cemu_assert_debug(serverSessionId == 0); - serverSessionId = incomingPacket->sessionId; - currentConnectionState = STATE_CONNECTED; - cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", clientSessionId, serverSessionId); + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected CON packet"); + return; } - delete incomingPacket; + // make sure the packet has the ACK flag set + if (!(incomingPacket->flags & prudpPacket::FLAG_ACK)) + { + cemuLog_log(LogType::Force, "[PRUDP] Received CON packet without ACK flag set"); + return; + } + m_hasConAck = true; + m_handshakeRetryCount = 0; + cemu_assert_debug(m_currentConnectionState == ConnectionState::Connecting); + // connected! + m_lastPingTimestamp = prudpGetMSTimestamp(); + cemu_assert_debug(m_serverSessionId == 0); + m_serverSessionId = incomingPacket->sessionId; + m_currentConnectionState = ConnectionState::Connected; + cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", m_clientSessionId, m_serverSessionId); return; } else if (incomingPacket->type == prudpPacket::TYPE_DATA) { - // send ack back if requested - if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + // handle ACK + if (incomingPacket->flags & prudpPacket::FLAG_ACK) { - prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); - directSendPacket(&ackPacket, dstIp, dstPort); + AcknowledgePacket(incomingPacket->sequenceId); + if(!incomingPacket->packetData.empty()) + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ACK data packet with payload"); + return; + } + // send ack back if requested + if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) + { + prudpPacket ackPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->m_clientSessionId, incomingPacket->sequenceId, 0); + DirectSendPacket(&ackPacket); } // skip data packets without payload if (incomingPacket->packetData.empty()) - { - delete incomingPacket; return; - } - // verify some values - uint16 seqDist = incomingPacket->sequenceId - incomingSequenceId; + // verify sequence id + uint16 seqDist = incomingPacket->sequenceId - m_incomingSequenceId; if (seqDist >= 0xC000) { // outdated - delete incomingPacket; return; } // check if packet is already queued - for (auto& it : queue_incomingPackets) + for (auto& it : m_incomingPacketQueue) { if (it->sequenceId == incomingPacket->sequenceId) { // already queued (should check other values too, like packet type?) cemuLog_log(LogType::PRUDP, "Duplicate PRUDP packet received"); - delete incomingPacket; return; } } // put into ordered receive queue - sortIncomingDataPacket(incomingPacket); + SortIncomingDataPacket(std::move(incomingPacket)); } else if (incomingPacket->type == prudpPacket::TYPE_DISCONNECT) { - currentConnectionState = STATE_DISCONNECTED; + m_currentConnectionState = ConnectionState::Disconnected; return; } else { - // ignore unknown packet - delete incomingPacket; - return; + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unknown packet type"); } } -bool prudpClient::update() +bool prudpClient::Update() { - if (currentConnectionState == STATE_DISCONNECTED) + if (m_currentConnectionState == ConnectionState::Disconnected) return false; uint32 currentTimestamp = prudpGetMSTimestamp(); // check for incoming packets uint8 receiveBuffer[4096]; while (true) { - sockaddr receiveFrom = { 0 }; + sockaddr receiveFrom = {0}; socklen_t receiveFromLen = sizeof(receiveFrom); - sint32 r = recvfrom(socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); + sint32 r = recvfrom(m_socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); if (r >= 0) { // todo: Verify sender (receiveFrom) @@ -807,203 +809,195 @@ bool prudpClient::update() cemuLog_log(LogType::Force, "[PRUDP] Invalid packet length"); break; } - prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); + auto incomingPacket = std::make_unique<prudpIncomingPacket>(&m_streamSettings, receiveBuffer + pIdx, packetLength); pIdx += packetLength; if (incomingPacket->hasError()) { cemuLog_log(LogType::Force, "[PRUDP] Packet error"); - delete incomingPacket; break; } - if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) + if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != m_serverSessionId) { cemuLog_log(LogType::PRUDP, "[PRUDP] Invalid session id"); - delete incomingPacket; continue; // different session } - handleIncomingPacket(incomingPacket); + HandleIncomingPacket(std::move(incomingPacket)); } } else break; } // check for ack timeouts - for (auto &it : list_packetsWithAckReq) + for (auto& it : m_dataPacketsWithAckReq) { if ((currentTimestamp - it.lastRetryTimestamp) >= 2300) { if (it.retryCount >= 7) { // after too many retries consider the connection dead - currentConnectionState = STATE_DISCONNECTED; + m_currentConnectionState = ConnectionState::Disconnected; } // resend - directSendPacket(it.packet, dstIp, dstPort); + DirectSendPacket(it.packet); it.lastRetryTimestamp = currentTimestamp; it.retryCount++; } } - // check if we need to send another ping - if (currentConnectionState == STATE_CONNECTED) + if (m_currentConnectionState == ConnectionState::Connecting) { - if(m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack + // check if we need to resend SYN or CON + uint32 timeSinceLastHandshake = currentTimestamp - m_lastHandshakeTimestamp; + if (timeSinceLastHandshake >= 1200) + { + if (m_handshakeRetryCount >= 5) + { + // too many retries, assume the other side doesn't listen + m_currentConnectionState = ConnectionState::Disconnected; + cemuLog_log(LogType::PRUDP, "PRUDP: Failed to connect"); + return false; + } + SendCurrentHandshakePacket(); + } + } + else if (m_currentConnectionState == ConnectionState::Connected) + { + // handle pings + if (m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack { // we are waiting for the ack of the previous ping, but it hasn't arrived yet so send another ping packet - if((currentTimestamp - lastPingTimestamp) >= 1500) + if ((currentTimestamp - m_lastPingTimestamp) >= 1500) { cemuLog_log(LogType::PRUDP, "[PRUDP] Resending ping packet (no ack received)"); - if(m_unacknowledgedPingCount >= 10) + if (m_unacknowledgedPingCount >= 10) { // too many unacknowledged pings, assume the connection is dead - currentConnectionState = STATE_DISCONNECTED; + m_currentConnectionState = ConnectionState::Disconnected; cemuLog_log(LogType::PRUDP, "PRUDP: Connection did not receive a ping response in a while. Assuming disconnect"); return false; } // resend the ping packet - prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); - directSendPacket(pingPacket, dstIp, dstPort); + prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); + DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; - delete pingPacket; - lastPingTimestamp = currentTimestamp; + m_lastPingTimestamp = currentTimestamp; } } else { - if((currentTimestamp - lastPingTimestamp) >= 20000) + if ((currentTimestamp - m_lastPingTimestamp) >= 20000) { - cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping+1); + cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping + 1); // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack this->m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 - prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); - directSendPacket(pingPacket, dstIp, dstPort); + prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); + DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; - delete pingPacket; - lastPingTimestamp = currentTimestamp; + m_lastPingTimestamp = currentTimestamp; } } } return false; } -void prudpClient::directSendPacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) +void prudpClient::DirectSendPacket(prudpPacket* packet) { uint8 packetBuffer[prudpPacket::PACKET_RAW_SIZE_MAX]; - sint32 len = packet->buildData(packetBuffer, prudpPacket::PACKET_RAW_SIZE_MAX); - sockaddr_in destAddr; destAddr.sin_family = AF_INET; - destAddr.sin_port = htons(dstPort); - destAddr.sin_addr.s_addr = dstIp; - sendto(socketUdp, (const char*)packetBuffer, len, 0, (const sockaddr*)&destAddr, sizeof(destAddr)); + destAddr.sin_port = htons(m_dstPort); + destAddr.sin_addr.s_addr = m_dstIp; + sendto(m_socketUdp, (const char*)packetBuffer, len, 0, (const sockaddr*)&destAddr, sizeof(destAddr)); } -void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) +void prudpClient::QueuePacket(prudpPacket* packet) { + cemu_assert_debug(packet->GetType() == prudpPacket::TYPE_DATA); // only data packets should be queued if (packet->requiresAck()) { - cemu_assert_debug(packet->GetType() != prudpPacket::TYPE_PING); // ping packets use their own logic for acks, dont queue them // remember this packet until we receive the ack - prudpAckRequired_t ackRequired = { 0 }; - ackRequired.packet = packet; - ackRequired.initialSendTimestamp = prudpGetMSTimestamp(); - ackRequired.lastRetryTimestamp = ackRequired.initialSendTimestamp; - list_packetsWithAckReq.push_back(ackRequired); - directSendPacket(packet, dstIp, dstPort); + m_dataPacketsWithAckReq.emplace_back(packet, prudpGetMSTimestamp()); + DirectSendPacket(packet); } else { - directSendPacket(packet, dstIp, dstPort); + DirectSendPacket(packet); delete packet; } } -void prudpClient::sendDatagram(uint8* input, sint32 length, bool reliable) +void prudpClient::SendDatagram(uint8* input, sint32 length, bool reliable) { - cemu_assert_debug(reliable); // non-reliable packets require testing - if(length >= 0x300) + cemu_assert_debug(reliable); // non-reliable packets require correct sequenceId handling and testing + cemu_assert_debug(m_hasSynAck && m_hasConAck); // cant send data packets before we are connected + if (length >= 0x300) { - cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long"); + cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long. Fragmentation not implemented yet"); } // single fragment data packet uint16 flags = prudpPacket::FLAG_NEED_ACK; - if(reliable) + if (reliable) flags |= prudpPacket::FLAG_RELIABLE; - prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, flags, clientSessionId, outgoingSequenceId, 0); - if(reliable) - outgoingSequenceId++; + prudpPacket* packet = new prudpPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_DATA, flags, m_clientSessionId, m_outgoingReliableSequenceId, 0); + if (reliable) + m_outgoingReliableSequenceId++; packet->setFragmentIndex(0); packet->setData(input, length); - queuePacket(packet, dstIp, dstPort); + QueuePacket(packet); } -uint16 prudpClient::getSourcePort() +sint32 prudpClient::ReceiveDatagram(std::vector<uint8>& outputBuffer) { - return this->srcPort; -} - -SOCKET prudpClient::getSocket() -{ - if (currentConnectionState == STATE_DISCONNECTED) - { - return INVALID_SOCKET; - } - return this->socketUdp; -} - -sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) -{ - if (queue_incomingPackets.empty()) + outputBuffer.clear(); + if (m_incomingPacketQueue.empty()) return -1; - prudpIncomingPacket* incomingPacket = queue_incomingPackets[0]; - if (incomingPacket->sequenceId != this->incomingSequenceId) + prudpIncomingPacket* frontPacket = m_incomingPacketQueue[0].get(); + if (frontPacket->sequenceId != this->m_incomingSequenceId) return -1; - - if (incomingPacket->fragmentIndex == 0) + if (frontPacket->fragmentIndex == 0) { // single-fragment packet // decrypt - incomingPacket->decrypt(); + frontPacket->decrypt(); // read data - sint32 datagramLen = (sint32)incomingPacket->packetData.size(); - if (datagramLen > 0) + if (!frontPacket->packetData.empty()) { - // resize buffer if necessary - if (datagramLen > outputBuffer.size()) - outputBuffer.resize(datagramLen); - // to conserve memory we will also shrink the buffer if it was previously extended beyond 64KB - constexpr size_t BUFFER_TARGET_SIZE = 1024 * 64; - if (datagramLen < BUFFER_TARGET_SIZE && outputBuffer.size() > BUFFER_TARGET_SIZE) + // to conserve memory we will also shrink the buffer if it was previously extended beyond 32KB + constexpr size_t BUFFER_TARGET_SIZE = 1024 * 32; + if (frontPacket->packetData.size() < BUFFER_TARGET_SIZE && outputBuffer.capacity() > BUFFER_TARGET_SIZE) + { outputBuffer.resize(BUFFER_TARGET_SIZE); - // copy datagram to buffer - memcpy(outputBuffer.data(), &incomingPacket->packetData.front(), datagramLen); + outputBuffer.shrink_to_fit(); + outputBuffer.clear(); + } + // write packet data to output buffer + cemu_assert_debug(outputBuffer.empty()); + outputBuffer.insert(outputBuffer.end(), frontPacket->packetData.begin(), frontPacket->packetData.end()); } - delete incomingPacket; - // remove packet from queue - queue_incomingPackets.erase(queue_incomingPackets.begin()); + m_incomingPacketQueue.erase(m_incomingPacketQueue.begin()); // advance expected sequence id - this->incomingSequenceId++; - return datagramLen; + this->m_incomingSequenceId++; + return (sint32)outputBuffer.size(); } else { // multi-fragment packet - if (incomingPacket->fragmentIndex != 1) + if (frontPacket->fragmentIndex != 1) return -1; // first packet of the chain not received yet // verify chain sint32 packetIndex = 1; sint32 chainLength = -1; // if full chain found, set to count of packets - for(sint32 i=1; i<queue_incomingPackets.size(); i++) + for (sint32 i = 1; i < m_incomingPacketQueue.size(); i++) { - uint8 itFragmentIndex = queue_incomingPackets[packetIndex]->fragmentIndex; + uint8 itFragmentIndex = m_incomingPacketQueue[packetIndex]->fragmentIndex; // sequence id must increase by 1 for every packet - if (queue_incomingPackets[packetIndex]->sequenceId != (this->incomingSequenceId+i) ) + if (m_incomingPacketQueue[packetIndex]->sequenceId != (m_incomingSequenceId + i)) return -1; // missing packets // last fragment in chain is marked by fragment index 0 if (itFragmentIndex == 0) { - chainLength = i+1; + chainLength = i + 1; break; } packetIndex++; @@ -1011,29 +1005,17 @@ sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) if (chainLength < 1) return -1; // chain not complete // extract data from packet chain - sint32 writeIndex = 0; + cemu_assert_debug(outputBuffer.empty()); for (sint32 i = 0; i < chainLength; i++) { - incomingPacket = queue_incomingPackets[i]; - // decrypt + prudpIncomingPacket* incomingPacket = m_incomingPacketQueue[i].get(); incomingPacket->decrypt(); - // extract data - sint32 datagramLen = (sint32)incomingPacket->packetData.size(); - if (datagramLen > 0) - { - // make sure output buffer can fit the data - if ((writeIndex + datagramLen) > outputBuffer.size()) - outputBuffer.resize(writeIndex + datagramLen + 4 * 1024); - memcpy(outputBuffer.data()+writeIndex, &incomingPacket->packetData.front(), datagramLen); - writeIndex += datagramLen; - } - // free packet memory - delete incomingPacket; + outputBuffer.insert(outputBuffer.end(), incomingPacket->packetData.begin(), incomingPacket->packetData.end()); } // remove packets from queue - queue_incomingPackets.erase(queue_incomingPackets.begin(), queue_incomingPackets.begin() + chainLength); - this->incomingSequenceId += chainLength; - return writeIndex; + m_incomingPacketQueue.erase(m_incomingPacketQueue.begin(), m_incomingPacketQueue.begin() + chainLength); + m_incomingSequenceId += chainLength; + return (sint32)outputBuffer.size(); } return -1; } diff --git a/src/Cemu/nex/prudp.h b/src/Cemu/nex/prudp.h index 5ed5bcb..3192c83 100644 --- a/src/Cemu/nex/prudp.h +++ b/src/Cemu/nex/prudp.h @@ -4,26 +4,26 @@ #define RC4_N 256 -typedef struct +struct RC4Ctx { unsigned char S[RC4_N]; int i; int j; -}RC4Ctx_t; +}; -void RC4_initCtx(RC4Ctx_t* rc4Ctx, char *key); -void RC4_initCtx(RC4Ctx_t* rc4Ctx, unsigned char* key, int keyLen); -void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned char* output); +void RC4_initCtx(RC4Ctx* rc4Ctx, const char* key); +void RC4_initCtx(RC4Ctx* rc4Ctx, unsigned char* key, int keyLen); +void RC4_transform(RC4Ctx* rc4Ctx, unsigned char* input, int len, unsigned char* output); -typedef struct +struct prudpStreamSettings { uint8 checksumBase; // calculated from key uint8 accessKeyDigest[16]; // MD5 hash of key - RC4Ctx_t rc4Client; - RC4Ctx_t rc4Server; -}prudpStreamSettings_t; + RC4Ctx rc4Client; + RC4Ctx rc4Server; +}; -typedef struct +struct prudpStationUrl { uint32 ip; uint16 port; @@ -32,19 +32,17 @@ typedef struct sint32 sid; sint32 stream; sint32 type; -}stationUrl_t; +}; -typedef struct +struct prudpAuthServerInfo { uint32 userPid; uint8 secureKey[16]; uint8 kerberosKey[16]; uint8 secureTicket[1024]; sint32 secureTicketLength; - stationUrl_t server; -}authServerInfo_t; - -uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length); + prudpStationUrl server; +}; class prudpPacket { @@ -66,7 +64,7 @@ public: static sint32 calculateSizeFromPacketData(uint8* data, sint32 length); - prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature); + prudpPacket(prudpStreamSettings* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature); bool requiresAck(); void setData(uint8* data, sint32 length); void setFragmentIndex(uint8 fragmentIndex); @@ -87,7 +85,7 @@ private: uint16 flags; uint8 sessionId; uint32 specifiedPacketSignature; - prudpStreamSettings_t* streamSettings; + prudpStreamSettings* streamSettings; std::vector<uint8> packetData; bool isEncrypted; uint16 m_sequenceId{0}; @@ -97,7 +95,7 @@ private: class prudpIncomingPacket { public: - prudpIncomingPacket(prudpStreamSettings_t* streamSettings, uint8* data, sint32 length); + prudpIncomingPacket(prudpStreamSettings* streamSettings, uint8* data, sint32 length); bool hasError(); @@ -122,83 +120,91 @@ public: private: bool isInvalid = false; - prudpStreamSettings_t* streamSettings = nullptr; - + prudpStreamSettings* streamSettings = nullptr; }; -typedef struct -{ - prudpPacket* packet; - uint32 initialSendTimestamp; - uint32 lastRetryTimestamp; - sint32 retryCount; -}prudpAckRequired_t; - class prudpClient { + struct PacketWithAckRequired + { + PacketWithAckRequired(prudpPacket* packet, uint32 initialSendTimestamp) : + packet(packet), initialSendTimestamp(initialSendTimestamp), lastRetryTimestamp(initialSendTimestamp) { } + prudpPacket* packet; + uint32 initialSendTimestamp; + uint32 lastRetryTimestamp; + sint32 retryCount{0}; + }; public: - static const int STATE_CONNECTING = 0; - static const int STATE_CONNECTED = 1; - static const int STATE_DISCONNECTED = 2; + enum class ConnectionState : uint8 + { + Connecting, + Connected, + Disconnected + }; -public: prudpClient(uint32 dstIp, uint16 dstPort, const char* key); - prudpClient(uint32 dstIp, uint16 dstPort, const char* key, authServerInfo_t* authInfo); + prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAuthServerInfo* authInfo); ~prudpClient(); - bool isConnected(); + bool IsConnected() const { return m_currentConnectionState == ConnectionState::Connected; } + ConnectionState GetConnectionState() const { return m_currentConnectionState; } + uint16 GetSourcePort() const { return m_srcPort; } - uint8 getConnectionState(); - void acknowledgePacket(uint16 sequenceId); - void sortIncomingDataPacket(prudpIncomingPacket* incomingPacket); - void handleIncomingPacket(prudpIncomingPacket* incomingPacket); - bool update(); // check for new incoming packets, returns true if receiveDatagram() should be called + bool Update(); // update connection state and check for incoming packets. Returns true if ReceiveDatagram() should be called - sint32 receiveDatagram(std::vector<uint8>& outputBuffer); - void sendDatagram(uint8* input, sint32 length, bool reliable = true); - - uint16 getSourcePort(); - - SOCKET getSocket(); + sint32 ReceiveDatagram(std::vector<uint8>& outputBuffer); + void SendDatagram(uint8* input, sint32 length, bool reliable = true); private: prudpClient(); - void directSendPacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort); - sint32 kerberosEncryptData(uint8* input, sint32 length, uint8* output); - void queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort); + + void HandleIncomingPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket); + void DirectSendPacket(prudpPacket* packet); + sint32 KerberosEncryptData(uint8* input, sint32 length, uint8* output); + void QueuePacket(prudpPacket* packet); + + void AcknowledgePacket(uint16 sequenceId); + void SortIncomingDataPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket); + + void SendCurrentHandshakePacket(); private: - uint16 srcPort; - uint32 dstIp; - uint16 dstPort; - uint8 vport_src; - uint8 vport_dst; - prudpStreamSettings_t streamSettings; - std::vector<prudpAckRequired_t> list_packetsWithAckReq; - std::vector<prudpIncomingPacket*> queue_incomingPackets; - - // connection - uint8 currentConnectionState; - uint32 serverConnectionSignature; - uint32 clientConnectionSignature; - bool hasSentCon; - uint32 lastPingTimestamp; + uint16 m_srcPort; + uint32 m_dstIp; + uint16 m_dstPort; + uint8 m_srcVPort; + uint8 m_dstVPort; + prudpStreamSettings m_streamSettings; + std::vector<PacketWithAckRequired> m_dataPacketsWithAckReq; + std::vector<std::unique_ptr<prudpIncomingPacket>> m_incomingPacketQueue; - uint16 outgoingSequenceId; - uint16 incomingSequenceId; + // connection handshake state + bool m_hasSynAck{false}; + bool m_hasConAck{false}; + uint32 m_lastHandshakeTimestamp{0}; + uint8 m_handshakeRetryCount{0}; + + // connection + ConnectionState m_currentConnectionState; + uint32 m_serverConnectionSignature; + uint32 m_clientConnectionSignature; + uint32 m_lastPingTimestamp; + + uint16 m_outgoingReliableSequenceId{2}; // 1 is reserved for CON + uint16 m_incomingSequenceId; uint16 m_outgoingSequenceId_ping{0}; uint8 m_unacknowledgedPingCount{0}; - uint8 clientSessionId; - uint8 serverSessionId; + uint8 m_clientSessionId; + uint8 m_serverSessionId; // secure - bool isSecureConnection; - authServerInfo_t authInfo; + bool m_isSecureConnection{false}; + prudpAuthServerInfo m_authInfo; // socket - SOCKET socketUdp; + SOCKET m_socketUdp; }; uint32 prudpGetMSTimestamp(); \ No newline at end of file From 989e2b8c8c14f2cebf86d97eeca5bf7877989c96 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 18 Apr 2024 23:11:19 +0200 Subject: [PATCH 072/130] prudp: More code cleanup + fix compile error --- src/Cemu/nex/prudp.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 5c773fe..771fe09 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -288,7 +288,7 @@ uint32 prudpPacket::packetSignature() return specifiedPacketSignature; else if (type == TYPE_DATA) { - if (packetData.size() == 0) + if (packetData.empty()) return 0x12345678; HMACMD5Ctx ctx; @@ -307,8 +307,7 @@ uint32 prudpPacket::packetSignature() void prudpPacket::setData(uint8* data, sint32 length) { - packetData.resize(length); - memcpy(&packetData.front(), data, length); + packetData.assign(data, data + length); } void prudpPacket::setFragmentIndex(uint8 fragmentIndex) @@ -509,12 +508,12 @@ prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) u_long nonBlockingMode = 1; // 1 to enable non-blocking socket ioctlsocket(m_socketUdp, FIONBIO, &nonBlockingMode); #else - int flags = fcntl(socketUdp, F_GETFL); - fcntl(socketUdp, F_SETFL, flags | O_NONBLOCK); + int flags = fcntl(m_socketUdp, F_GETFL); + fcntl(m_socketUdp, F_SETFL, flags | O_NONBLOCK); #endif // generate frequently used parameters - this->m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); - this->m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); + m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); + m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); // set stream settings uint8 checksumBase = 0; for (sint32 i = 0; key[i] != '\0'; i++) @@ -540,8 +539,8 @@ prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAut { RC4_initCtx(&m_streamSettings.rc4Server, authInfo->secureKey, 16); RC4_initCtx(&m_streamSettings.rc4Client, authInfo->secureKey, 16); - this->m_isSecureConnection = true; - memcpy(&this->m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); + m_isSecureConnection = true; + memcpy(&m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); } prudpClient::~prudpClient() @@ -601,7 +600,7 @@ void prudpClient::SortIncomingDataPacket(std::unique_ptr<prudpIncomingPacket> in sint32 prudpClient::KerberosEncryptData(uint8* input, sint32 length, uint8* output) { RC4Ctx rc4Kerberos; - RC4_initCtx(&rc4Kerberos, this->m_authInfo.secureKey, 16); + RC4_initCtx(&rc4Kerberos, m_authInfo.secureKey, 16); memcpy(output, input, length); RC4_transform(&rc4Kerberos, output, length, output); // calculate and append hmac @@ -627,7 +626,7 @@ void prudpClient::SendCurrentHandshakePacket() { uint8 tempBuffer[512]; nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); - conData.writeU32(this->m_clientConnectionSignature); + conData.writeU32(m_clientConnectionSignature); conData.writeBuffer(m_authInfo.secureTicket, m_authInfo.secureTicketLength); // encrypted request data uint8 requestData[4 * 3]; @@ -641,7 +640,7 @@ void prudpClient::SendCurrentHandshakePacket() } else { - conPacket.setData((uint8*)&this->m_clientConnectionSignature, sizeof(uint32)); + conPacket.setData((uint8*)&m_clientConnectionSignature, sizeof(uint32)); } DirectSendPacket(&conPacket); } @@ -889,7 +888,7 @@ bool prudpClient::Update() cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping + 1); // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack - this->m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 + m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; From efbbb817fe1cbe09ee132344b44a0f61f8b8ac96 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 20 Apr 2024 12:19:06 +0200 Subject: [PATCH 073/130] DownloadManager: Always use Nintendo servers + additional streamlining - Download manager now always uses Nintendo servers. Requires only a valid OTP and SEEPROM dump so you can use it in combination with a Pretendo setup even without a NNID - Account drop down removed from download manager since it's not required - Internally all our API requests now support overriding which service to use - Drop support for act-url and ecs-url command line parameters. Usage of network_services.xml ("custom" option in the UI) is preferred --- src/Cafe/IOSU/legacy/iosu_boss.cpp | 2 +- src/Cafe/IOSU/legacy/iosu_crypto.cpp | 10 - src/Cafe/IOSU/legacy/iosu_crypto.h | 1 - src/Cafe/IOSU/legacy/iosu_nim.cpp | 2 +- src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp | 2 +- .../nn_olv/nn_olv_DownloadCommunityTypes.cpp | 2 +- .../OS/libs/nn_olv/nn_olv_InitializeTypes.cpp | 2 +- .../nn_olv/nn_olv_UploadCommunityTypes.cpp | 2 +- .../nn_olv/nn_olv_UploadFavoriteTypes.cpp | 2 +- .../Tools/DownloadManager/DownloadManager.cpp | 130 ++++------ .../Tools/DownloadManager/DownloadManager.h | 16 +- src/Cemu/napi/napi.h | 23 +- src/Cemu/napi/napi_act.cpp | 49 ++-- src/Cemu/napi/napi_ec.cpp | 229 +++++++++--------- src/Cemu/napi/napi_helper.cpp | 24 +- src/Cemu/napi/napi_helper.h | 4 +- src/Cemu/napi/napi_idbe.cpp | 10 +- src/Cemu/napi/napi_version.cpp | 6 +- src/Cemu/ncrypto/ncrypto.cpp | 18 +- src/Cemu/ncrypto/ncrypto.h | 4 + src/config/ActiveSettings.cpp | 1 - src/config/LaunchSettings.cpp | 37 +-- src/config/LaunchSettings.h | 10 - src/config/NetworkSettings.cpp | 3 - src/config/NetworkSettings.h | 27 ++- src/gui/CemuApp.cpp | 2 +- src/gui/GeneralSettings2.cpp | 8 +- src/gui/TitleManager.cpp | 33 ++- src/gui/TitleManager.h | 2 + 29 files changed, 323 insertions(+), 338 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp index c2c1eb5..760e5b6 100644 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ b/src/Cafe/IOSU/legacy/iosu_boss.cpp @@ -498,7 +498,7 @@ namespace iosu curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it)); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C); - if (GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() == NetworkService::Custom || ActiveSettings::GetNetworkService() == NetworkService::Pretendo) // remove Pretendo Function once SSL is in the Service + if (IsNetworkServiceSSLDisabled(ActiveSettings::GetNetworkService())) { curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L); } diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.cpp b/src/Cafe/IOSU/legacy/iosu_crypto.cpp index 80eb2f0..a4f7543 100644 --- a/src/Cafe/IOSU/legacy/iosu_crypto.cpp +++ b/src/Cafe/IOSU/legacy/iosu_crypto.cpp @@ -292,16 +292,6 @@ void iosuCrypto_generateDeviceCertificate() BN_CTX_free(context); } -bool iosuCrypto_hasAllDataForLogin() -{ - if (hasOtpMem == false) - return false; - if (hasSeepromMem == false) - return false; - // todo - check if certificates are available - return true; -} - sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output) { iosuCrypto_base64Encode((uint8*)&g_wiiuDeviceCert, sizeof(g_wiiuDeviceCert), output); diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.h b/src/Cafe/IOSU/legacy/iosu_crypto.h index 9f1429c..d4fc49b 100644 --- a/src/Cafe/IOSU/legacy/iosu_crypto.h +++ b/src/Cafe/IOSU/legacy/iosu_crypto.h @@ -2,7 +2,6 @@ void iosuCrypto_init(); -bool iosuCrypto_hasAllDataForLogin(); bool iosuCrypto_getDeviceId(uint32* deviceId); void iosuCrypto_getDeviceSerialString(char* serialString); diff --git a/src/Cafe/IOSU/legacy/iosu_nim.cpp b/src/Cafe/IOSU/legacy/iosu_nim.cpp index e7cf97e..b529640 100644 --- a/src/Cafe/IOSU/legacy/iosu_nim.cpp +++ b/src/Cafe/IOSU/legacy/iosu_nim.cpp @@ -228,7 +228,7 @@ namespace iosu } } - auto result = NAPI::IDBE_Request(titleId); + auto result = NAPI::IDBE_Request(ActiveSettings::GetNetworkService(), titleId); if (!result) { memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0)); diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp index a78494c..a69f32a 100644 --- a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp @@ -42,7 +42,7 @@ namespace nn void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, OSThread_t* thread) { - std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(titleId); + std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(ActiveSettings::GetNetworkService(), titleId); if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) { // icon does not exist or has the wrong size diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp index 1bf2b37..db1885a 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp @@ -43,7 +43,7 @@ namespace nn return res; CurlRequestHelper req; - req.initate(reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp index 5e6dba7..ba657ff 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp @@ -195,7 +195,7 @@ namespace nn break; } - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp index 179d66b..6f3c43b 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp @@ -50,7 +50,7 @@ namespace nn CurlRequestHelper req; - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp index 307004b..1e2d40a 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp @@ -40,7 +40,7 @@ namespace nn 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); + req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp index 807a4e7..9e683ed 100644 --- a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp @@ -33,14 +33,7 @@ void DownloadManager::downloadTitleVersionList() { if (m_hasTitleVersionList) return; - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + NAPI::AuthInfo authInfo = GetAuthInfo(false); auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); if (!versionListVersionResult.isValid) return; @@ -195,15 +188,7 @@ public: bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - + NAPI::AuthInfo authInfo = GetAuthInfo(false); // query IAS/ECS account id and device token (if not cached) auto rChallenge = NAPI::IAS_GetChallenge(authInfo); if (rChallenge.apiError != NAPI_RESULT::SUCCESS) @@ -211,7 +196,6 @@ bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() auto rRegistrationInfo = NAPI::IAS_GetRegistrationInfo_QueryInfo(authInfo, rChallenge.challenge); if (rRegistrationInfo.apiError != NAPI_RESULT::SUCCESS) return false; - m_iasToken.serviceAccountId = rRegistrationInfo.accountId; m_iasToken.deviceToken = rRegistrationInfo.deviceToken; // store to cache @@ -221,24 +205,13 @@ bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() std::vector<uint8> serializedData; if (!storedTokenInfo.serialize(serializedData)) return false; - s_nupFileCache->AddFileAsync({ fmt::format("{}/token_info", m_authInfo.nnidAccountName) }, serializedData.data(), serializedData.size()); + s_nupFileCache->AddFileAsync({ fmt::format("{}/token_info", m_authInfo.cachefileName) }, serializedData.data(), serializedData.size()); return true; } bool DownloadManager::_connect_queryAccountStatusAndServiceURLs() { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); NAPI::NAPI_ECSGetAccountStatus_Result accountStatusResult = NAPI::ECS_GetAccountStatus(authInfo); if (accountStatusResult.apiError != NAPI_RESULT::SUCCESS) { @@ -291,7 +264,7 @@ void DownloadManager::loadTicketCache() m_ticketCache.clear(); cemu_assert_debug(m_ticketCache.empty()); std::vector<uint8> ticketCacheBlob; - if (!s_nupFileCache->GetFile({ fmt::format("{}/eticket_cache", m_authInfo.nnidAccountName) }, ticketCacheBlob)) + if (!s_nupFileCache->GetFile({ fmt::format("{}/eticket_cache", m_authInfo.cachefileName) }, ticketCacheBlob)) return; MemStreamReader memReader(ticketCacheBlob.data(), ticketCacheBlob.size()); uint8 version = memReader.readBE<uint8>(); @@ -343,23 +316,12 @@ void DownloadManager::storeTicketCache() memWriter.writePODVector(cert); } auto serializedBlob = memWriter.getResult(); - s_nupFileCache->AddFileAsync({ fmt::format("{}/eticket_cache", m_authInfo.nnidAccountName) }, serializedBlob.data(), serializedBlob.size()); + s_nupFileCache->AddFileAsync({ fmt::format("{}/eticket_cache", m_authInfo.cachefileName) }, serializedBlob.data(), serializedBlob.size()); } bool DownloadManager::syncAccountTickets() { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); // query TIV list from server NAPI::NAPI_ECSAccountListETicketIds_Result resultTicketIds = NAPI::ECS_AccountListETicketIds(authInfo); if (!resultTicketIds.isValid()) @@ -425,19 +387,7 @@ bool DownloadManager::syncAccountTickets() bool DownloadManager::syncSystemTitleTickets() { setStatusMessage(_("Downloading system tickets...").utf8_string(), DLMGR_STATUS_CODE::CONNECTING); - // todo - add GetAuth() function - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); auto querySystemTitleTicket = [&](uint64 titleId) -> void { // check if cached already @@ -520,8 +470,7 @@ bool DownloadManager::syncUpdateTickets() if (findTicketByTitleIdAndVersion(itr.titleId, itr.availableTitleVersion)) continue; - NAPI::AuthInfo dummyAuth; - auto cetkResult = NAPI::CCS_GetCETK(dummyAuth, itr.titleId, itr.availableTitleVersion); + auto cetkResult = NAPI::CCS_GetCETK(GetDownloadMgrNetworkService(), itr.titleId, itr.availableTitleVersion); if (!cetkResult.isValid) continue; NCrypto::ETicketParser ticketParser; @@ -657,7 +606,7 @@ void DownloadManager::_handle_connect() if (s_nupFileCache) { std::vector<uint8> serializationBlob; - if (s_nupFileCache->GetFile({ fmt::format("{}/token_info", m_authInfo.nnidAccountName) }, serializationBlob)) + if (s_nupFileCache->GetFile({ fmt::format("{}/token_info", m_authInfo.cachefileName) }, serializationBlob)) { StoredTokenInfo storedTokenInfo; if (storedTokenInfo.deserialize(serializationBlob)) @@ -683,7 +632,7 @@ void DownloadManager::_handle_connect() if (!_connect_queryAccountStatusAndServiceURLs()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("Failed to query account status. Invalid account information?").utf8_string(), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed to query account status").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } // load ticket cache and sync @@ -692,7 +641,7 @@ void DownloadManager::_handle_connect() if (!syncTicketCache()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("Failed to request tickets (invalid NNID?)").utf8_string(), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed to request tickets").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } searchForIncompleteDownloads(); @@ -713,22 +662,10 @@ void DownloadManager::connect( std::string_view serial, std::string_view deviceCertBase64) { - if (nnidAccountName.empty()) - { - m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("This account is not linked with an NNID").utf8_string(), DLMGR_STATUS_CODE::FAILED); - return; - } runManager(); m_authInfo.nnidAccountName = nnidAccountName; m_authInfo.passwordHash = passwordHash; - if (std::all_of(m_authInfo.passwordHash.begin(), m_authInfo.passwordHash.end(), [](uint8 v) { return v == 0; })) - { - cemuLog_log(LogType::Force, "DLMgr: Invalid password hash"); - m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("Failed. Account does not have password set").utf8_string(), DLMGR_STATUS_CODE::FAILED); - return; - } + m_authInfo.cachefileName = nnidAccountName.empty() ? "DefaultName" : nnidAccountName; m_authInfo.region = region; m_authInfo.country = country; m_authInfo.deviceCertBase64 = deviceCertBase64; @@ -744,6 +681,31 @@ bool DownloadManager::IsConnected() const return m_connectState.load() != CONNECT_STATE::UNINITIALIZED; } +NetworkService DownloadManager::GetDownloadMgrNetworkService() +{ + return NetworkService::Nintendo; +} + +NAPI::AuthInfo DownloadManager::GetAuthInfo(bool withIasToken) +{ + NAPI::AuthInfo authInfo; + authInfo.serviceOverwrite = GetDownloadMgrNetworkService(); + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + if(withIasToken) + { + cemu_assert_debug(!m_iasToken.serviceAccountId.empty()); + authInfo.IASToken.accountId = m_iasToken.serviceAccountId; + authInfo.IASToken.deviceToken = m_iasToken.deviceToken; + } + return authInfo; +} + /* package / downloading */ // start/resume/retry download @@ -1022,17 +984,7 @@ void DownloadManager::reportPackageProgress(Package* package, uint32 currentProg void DownloadManager::asyncPackageDownloadTMD(Package* package) { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); TitleIdParser titleIdParser(package->titleId); NAPI::NAPI_CCSGetTMD_Result tmdResult; if (titleIdParser.GetType() == TitleIdParser::TITLE_TYPE::AOC) @@ -1196,7 +1148,7 @@ void DownloadManager::asyncPackageDownloadContentFile(Package* package, uint16 i setPackageError(package, _("Cannot create file").utf8_string()); return; } - if (!NAPI::CCS_GetContentFile(titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) + if (!NAPI::CCS_GetContentFile(GetDownloadMgrNetworkService(), titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) { setPackageError(package, _("Download failed").utf8_string()); delete callbackInfoData.fileOutput; @@ -1490,7 +1442,7 @@ void DownloadManager::prepareIDBE(uint64 titleId) if (s_nupFileCache->GetFile({ fmt::format("idbe/{0:016x}", titleId) }, idbeFile) && idbeFile.size() == sizeof(NAPI::IDBEIconDataV0)) return addToCache(titleId, (NAPI::IDBEIconDataV0*)(idbeFile.data())); // not cached, query from server - std::optional<NAPI::IDBEIconDataV0> iconData = NAPI::IDBE_Request(titleId); + std::optional<NAPI::IDBEIconDataV0> iconData = NAPI::IDBE_Request(GetDownloadMgrNetworkService(), titleId); if (!iconData) return; s_nupFileCache->AddFileAsync({ fmt::format("idbe/{0:016x}", titleId) }, (uint8*)&(*iconData), sizeof(NAPI::IDBEIconDataV0)); diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.h b/src/Cemu/Tools/DownloadManager/DownloadManager.h index 1693318..8f693a3 100644 --- a/src/Cemu/Tools/DownloadManager/DownloadManager.h +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.h @@ -2,17 +2,14 @@ #include "util/helpers/Semaphore.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/TitleList/TitleId.h" - #include "util/helpers/ConcurrentQueue.h" +#include "config/NetworkSettings.h" -#include <functional> -#include <optional> - -#include <future> - +// forward declarations namespace NAPI { struct IDBEIconDataV0; + struct AuthInfo; } namespace NCrypto @@ -86,7 +83,6 @@ public: bool IsConnected() const; - private: /* connect / login */ @@ -101,6 +97,7 @@ private: struct { + std::string cachefileName; std::string nnidAccountName; std::array<uint8, 32> passwordHash; std::string deviceCertBase64; @@ -122,7 +119,10 @@ private: void _handle_connect(); bool _connect_refreshIASAccountIdAndDeviceToken(); bool _connect_queryAccountStatusAndServiceURLs(); - + + NetworkService GetDownloadMgrNetworkService(); + NAPI::AuthInfo GetAuthInfo(bool withIasToken); + /* idbe cache */ public: void prepareIDBE(uint64 titleId); diff --git a/src/Cemu/napi/napi.h b/src/Cemu/napi/napi.h index ab17a7b..e1397d6 100644 --- a/src/Cemu/napi/napi.h +++ b/src/Cemu/napi/napi.h @@ -1,6 +1,7 @@ #pragma once -#include <optional> #include "config/CemuConfig.h" // for ConsoleLanguage +#include "config/NetworkSettings.h" // for NetworkService +#include "config/ActiveSettings.h" // for GetNetworkService() enum class NAPI_RESULT { @@ -16,8 +17,6 @@ namespace NAPI // common auth info structure shared by ACT, ECS and IAS service struct AuthInfo { - // todo - constructor for account name + raw password - // nnid std::string accountId; std::array<uint8, 32> passwordHash; @@ -41,9 +40,13 @@ namespace NAPI std::string deviceToken; }IASToken; - // ACT token (for account.nintendo.net requests) - + // service selection, if not set fall back to global setting + std::optional<NetworkService> serviceOverwrite; + NetworkService GetService() const + { + return serviceOverwrite.value_or(ActiveSettings::GetNetworkService()); + } }; bool NAPI_MakeAuthInfoFromCurrentAccount(AuthInfo& authInfo); // helper function. Returns false if online credentials/dumped files are not available @@ -232,9 +235,9 @@ namespace NAPI NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId); - NAPI_CCSGetETicket_Result CCS_GetCETK(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); - bool CCS_GetContentFile(uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); - NAPI_CCSGetContentH3_Result CCS_GetContentH3File(uint64 titleId, uint32 contentId); + NAPI_CCSGetETicket_Result CCS_GetCETK(NetworkService service, uint64 titleId, uint16 titleVersion); + bool CCS_GetContentFile(NetworkService service, uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); + NAPI_CCSGetContentH3_Result CCS_GetContentH3File(NetworkService service, uint64 titleId, uint32 contentId); /* IDBE */ @@ -286,8 +289,8 @@ namespace NAPI static_assert(sizeof(IDBEHeader) == 2+32); - std::optional<IDBEIconDataV0> IDBE_Request(uint64 titleId); - std::vector<uint8> IDBE_RequestRawEncrypted(uint64 titleId); // same as IDBE_Request but doesn't strip the header and decrypt the IDBE + std::optional<IDBEIconDataV0> IDBE_Request(NetworkService networkService, uint64 titleId); + std::vector<uint8> IDBE_RequestRawEncrypted(NetworkService networkService, uint64 titleId); // same as IDBE_Request but doesn't strip the header and decrypt the IDBE /* Version list */ diff --git a/src/Cemu/napi/napi_act.cpp b/src/Cemu/napi/napi_act.cpp index 9716c41..c72d9f4 100644 --- a/src/Cemu/napi/napi_act.cpp +++ b/src/Cemu/napi/napi_act.cpp @@ -14,6 +14,21 @@ namespace NAPI { + std::string _getACTUrl(NetworkService service) + { + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::ACTURL; + case NetworkService::Pretendo: + return PretendoURLs::ACTURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.ACT.GetValue(); + default: + return NintendoURLs::ACTURL; + } + } + struct ACTOauthToken : public _NAPI_CommonResultACT { std::string token; @@ -91,7 +106,7 @@ namespace NAPI struct OAuthTokenCacheEntry { - OAuthTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view token, std::string_view refreshToken, uint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), token(token), refreshToken(refreshToken) + OAuthTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view token, std::string_view refreshToken, uint64 expiresIn, NetworkService service) : accountId(accountId), passwordHash(passwordHash), token(token), refreshToken(refreshToken), service(service) { expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; }; @@ -107,10 +122,10 @@ namespace NAPI } std::string accountId; std::array<uint8, 32> passwordHash; - std::string token; std::string refreshToken; uint64 expires; + NetworkService service; }; std::vector<OAuthTokenCacheEntry> g_oauthTokenCache; @@ -122,11 +137,12 @@ namespace NAPI ACTOauthToken result{}; // check cache first + NetworkService service = authInfo.GetService(); g_oauthTokenCacheMtx.lock(); auto cacheItr = g_oauthTokenCache.begin(); while (cacheItr != g_oauthTokenCache.end()) { - if (cacheItr->CheckIfSameAccount(authInfo)) + if (cacheItr->CheckIfSameAccount(authInfo) && cacheItr->service == service) { if (cacheItr->CheckIfExpired()) { @@ -145,7 +161,7 @@ namespace NAPI // token not cached, request from server via oauth2 CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/oauth20/access_token/generate", LaunchSettings::GetActURLPrefix()), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/oauth20/access_token/generate", _getACTUrl(authInfo.GetService())), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); @@ -220,7 +236,7 @@ namespace NAPI if (expiration > 0) { g_oauthTokenCacheMtx.lock(); - g_oauthTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, result.token, result.refreshToken, expiration); + g_oauthTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, result.token, result.refreshToken, expiration, service); g_oauthTokenCacheMtx.unlock(); } return result; @@ -230,14 +246,13 @@ namespace NAPI { CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/people/@me/profile", LaunchSettings::GetActURLPrefix()), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/people/@me/profile", _getACTUrl(authInfo.GetService())), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); // get oauth2 token ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, 0x0005001010001C00, 0x0001C); - cemu_assert_unimplemented(); return true; @@ -245,15 +260,16 @@ namespace NAPI struct NexTokenCacheEntry { - NexTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, uint32 gameServerId, ACTNexToken& nexToken) : accountId(accountId), passwordHash(passwordHash), nexToken(nexToken), gameServerId(gameServerId) {}; + NexTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, NetworkService networkService, uint32 gameServerId, ACTNexToken& nexToken) : accountId(accountId), passwordHash(passwordHash), networkService(networkService), nexToken(nexToken), gameServerId(gameServerId) {}; bool IsMatch(const AuthInfo& authInfo, const uint32 gameServerId) const { - return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && this->gameServerId == gameServerId; + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && authInfo.GetService() == networkService && this->gameServerId == gameServerId; } std::string accountId; std::array<uint8, 32> passwordHash; + NetworkService networkService; uint32 gameServerId; ACTNexToken nexToken; @@ -297,7 +313,7 @@ namespace NAPI } // do request CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/provider/nex_token/@me?game_server_id={:08X}", LaunchSettings::GetActURLPrefix(), serverId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/provider/nex_token/@me?game_server_id={:08X}", _getACTUrl(authInfo.GetService()), serverId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); @@ -374,21 +390,21 @@ namespace NAPI result.nexToken.port = (uint16)StringHelpers::ToInt(port); result.apiError = NAPI_RESULT::SUCCESS; g_nexTokenCacheMtx.lock(); - g_nexTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, serverId, result.nexToken); + g_nexTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, authInfo.GetService(), serverId, result.nexToken); g_nexTokenCacheMtx.unlock(); return result; } struct IndependentTokenCacheEntry { - IndependentTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view clientId, std::string_view independentToken, sint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), clientId(clientId), independentToken(independentToken) + IndependentTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, NetworkService networkService, std::string_view clientId, std::string_view independentToken, sint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), networkService(networkService), clientId(clientId), independentToken(independentToken) { expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; }; bool IsMatch(const AuthInfo& authInfo, const std::string_view clientId) const { - return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && this->clientId == clientId; + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && authInfo.GetService() == networkService && this->clientId == clientId; } bool CheckIfExpired() const @@ -398,6 +414,7 @@ namespace NAPI std::string accountId; std::array<uint8, 32> passwordHash; + NetworkService networkService; std::string clientId; sint64 expires; @@ -449,7 +466,7 @@ namespace NAPI } // do request CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/provider/service_token/@me?client_id={}", LaunchSettings::GetActURLPrefix(), clientId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/provider/service_token/@me?client_id={}", _getACTUrl(authInfo.GetService()), clientId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); @@ -494,7 +511,7 @@ namespace NAPI result.apiError = NAPI_RESULT::SUCCESS; g_IndependentTokenCacheMtx.lock(); - g_IndependentTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, clientId, result.token, 3600); + g_IndependentTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, authInfo.GetService(), clientId, result.token, 3600); g_IndependentTokenCacheMtx.unlock(); return result; } @@ -520,7 +537,7 @@ namespace NAPI } // do request CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input={}", LaunchSettings::GetActURLPrefix(), nnid), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input={}", _getACTUrl(authInfo.GetService()), nnid), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); diff --git a/src/Cemu/napi/napi_ec.cpp b/src/Cemu/napi/napi_ec.cpp index 9bc4bfb..2c0812e 100644 --- a/src/Cemu/napi/napi_ec.cpp +++ b/src/Cemu/napi/napi_ec.cpp @@ -16,103 +16,112 @@ namespace NAPI { /* Service URL manager */ - std::string s_serviceURL_ContentPrefixURL; - std::string s_serviceURL_UncachedContentPrefixURL; - std::string s_serviceURL_EcsURL; - std::string s_serviceURL_IasURL; - std::string s_serviceURL_CasURL; - std::string s_serviceURL_NusURL; - - std::string _getNUSUrl() + struct CachedServiceUrls { - if (!s_serviceURL_NusURL.empty()) - return s_serviceURL_NusURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::NUSURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::NUSURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.NUS; - break; - default: - return NintendoURLs::NUSURL; - break; - } + std::string s_serviceURL_ContentPrefixURL; + std::string s_serviceURL_UncachedContentPrefixURL; + std::string s_serviceURL_EcsURL; + std::string s_serviceURL_IasURL; + std::string s_serviceURL_CasURL; + std::string s_serviceURL_NusURL; + }; + + std::unordered_map<NetworkService, CachedServiceUrls> s_cachedServiceUrlsMap; + + CachedServiceUrls& GetCachedServiceUrls(NetworkService service) + { + return s_cachedServiceUrlsMap[service]; } - std::string _getIASUrl() + std::string _getNUSUrl(NetworkService service) { - if (!s_serviceURL_IasURL.empty()) - return s_serviceURL_IasURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::IASURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::IASURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.IAS; - break; - default: - return NintendoURLs::IASURL; - break; - } + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_NusURL.empty()) + return cachedServiceUrls.s_serviceURL_NusURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::NUSURL; + case NetworkService::Pretendo: + return PretendoURLs::NUSURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.NUS; + default: + return NintendoURLs::NUSURL; + } } - std::string _getECSUrl() + std::string _getIASUrl(NetworkService service) + { + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_IasURL.empty()) + return cachedServiceUrls.s_serviceURL_IasURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::IASURL; + case NetworkService::Pretendo: + return PretendoURLs::IASURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.IAS; + default: + return NintendoURLs::IASURL; + } + } + + std::string _getECSUrl(NetworkService service) { // this is the first url queried (GetAccountStatus). The others are dynamically set if provided by the server but will fallback to hardcoded defaults otherwise - if (!s_serviceURL_EcsURL.empty()) - return s_serviceURL_EcsURL; - return LaunchSettings::GetServiceURL_ecs(); // by default this is "https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP" + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_EcsURL.empty()) + return cachedServiceUrls.s_serviceURL_EcsURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::ECSURL; + case NetworkService::Pretendo: + return PretendoURLs::ECSURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.ECS; + default: + return NintendoURLs::ECSURL; + } } - std::string _getCCSUncachedUrl() // used for TMD requests + std::string _getCCSUncachedUrl(NetworkService service) // used for TMD requests { - if (!s_serviceURL_UncachedContentPrefixURL.empty()) - return s_serviceURL_UncachedContentPrefixURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::CCSUURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::CCSUURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.CCSU; - break; - default: - return NintendoURLs::CCSUURL; - break; - } + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL.empty()) + return cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::CCSUURL; + case NetworkService::Pretendo: + return PretendoURLs::CCSUURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.CCSU; + default: + return NintendoURLs::CCSUURL; + } } - std::string _getCCSUrl() // used for game data downloads + std::string _getCCSUrl(NetworkService service) // used for game data downloads { - if (!s_serviceURL_ContentPrefixURL.empty()) - return s_serviceURL_ContentPrefixURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::CCSURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::CCSURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.CCS; - break; - default: - return NintendoURLs::CCSURL; - break; - } + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_ContentPrefixURL.empty()) + return cachedServiceUrls.s_serviceURL_ContentPrefixURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::CCSURL; + case NetworkService::Pretendo: + return PretendoURLs::CCSURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.CCS; + default: + return NintendoURLs::CCSURL; + } } /* NUS */ @@ -122,8 +131,8 @@ namespace NAPI { NAPI_NUSGetSystemCommonETicket_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("nus", _getNUSUrl(), "GetSystemCommonETicket", "1.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("nus", _getNUSUrl(authInfo.GetService()), "GetSystemCommonETicket", "1.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("RegionId", NCrypto::GetRegionAsString(authInfo.region)); @@ -175,8 +184,8 @@ namespace NAPI { NAPI_IASGetChallenge_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ias", _getIASUrl(), "GetChallenge", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ias", _getIASUrl(authInfo.GetService()), "GetChallenge", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // not validated but the generated Challenge is bound to this DeviceId soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -200,8 +209,8 @@ namespace NAPI NAPI_IASGetRegistrationInfo_Result IAS_GetRegistrationInfo_QueryInfo(AuthInfo& authInfo, std::string challenge) { NAPI_IASGetRegistrationInfo_Result result; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ias", _getIASUrl(), "GetRegistrationInfo", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ias", _getIASUrl(authInfo.GetService()), "GetRegistrationInfo", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // this must match the DeviceId used to generate Challenge soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -301,8 +310,8 @@ namespace NAPI { NAPI_ECSGetAccountStatus_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ecs", _getECSUrl(), "GetAccountStatus", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "GetAccountStatus", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -367,19 +376,19 @@ namespace NAPI } // assign service URLs + auto& cachedServiceUrls = GetCachedServiceUrls(authInfo.GetService()); if (!result.serviceURLs.ContentPrefixURL.empty()) - s_serviceURL_ContentPrefixURL = result.serviceURLs.ContentPrefixURL; + cachedServiceUrls.s_serviceURL_ContentPrefixURL = result.serviceURLs.ContentPrefixURL; if (!result.serviceURLs.UncachedContentPrefixURL.empty()) - s_serviceURL_UncachedContentPrefixURL = result.serviceURLs.UncachedContentPrefixURL; + cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL = result.serviceURLs.UncachedContentPrefixURL; if (!result.serviceURLs.IasURL.empty()) - s_serviceURL_IasURL = result.serviceURLs.IasURL; + cachedServiceUrls.s_serviceURL_IasURL = result.serviceURLs.IasURL; if (!result.serviceURLs.CasURL.empty()) - s_serviceURL_CasURL = result.serviceURLs.CasURL; + cachedServiceUrls.s_serviceURL_CasURL = result.serviceURLs.CasURL; if (!result.serviceURLs.NusURL.empty()) - s_serviceURL_NusURL = result.serviceURLs.NusURL; + cachedServiceUrls.s_serviceURL_NusURL = result.serviceURLs.NusURL; if (!result.serviceURLs.EcsURL.empty()) - s_serviceURL_EcsURL = result.serviceURLs.EcsURL; - + cachedServiceUrls.s_serviceURL_EcsURL = result.serviceURLs.EcsURL; return result; } @@ -387,8 +396,8 @@ namespace NAPI { NAPI_ECSAccountListETicketIds_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ecs", _getECSUrl(), "AccountListETicketIds", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "AccountListETicketIds", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -446,8 +455,8 @@ namespace NAPI { NAPI_ECSAccountGetETickets_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ecs", _getECSUrl(), "AccountGetETickets", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "AccountGetETickets", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -512,7 +521,7 @@ namespace NAPI { NAPI_CCSGetTMD_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/tmd.{}?deviceId={}&accountId={}", _getCCSUncachedUrl(), titleId, titleVersion, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(authInfo.GetService(), fmt::format("{}/{:016x}/tmd.{}?deviceId={}&accountId={}", _getCCSUncachedUrl(authInfo.GetService()), titleId, titleVersion, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { @@ -528,7 +537,7 @@ namespace NAPI { NAPI_CCSGetTMD_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/tmd?deviceId={}&accountId={}", _getCCSUncachedUrl(), titleId, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(authInfo.GetService(), fmt::format("{}/{:016x}/tmd?deviceId={}&accountId={}", _getCCSUncachedUrl(authInfo.GetService()), titleId, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { @@ -540,11 +549,11 @@ namespace NAPI return result; } - NAPI_CCSGetETicket_Result CCS_GetCETK(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) + NAPI_CCSGetETicket_Result CCS_GetCETK(NetworkService service, uint64 titleId, uint16 titleVersion) { NAPI_CCSGetETicket_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/cetk", _getCCSUncachedUrl(), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(service, fmt::format("{}/{:016x}/cetk", _getCCSUncachedUrl(service), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { @@ -556,10 +565,10 @@ namespace NAPI return result; } - bool CCS_GetContentFile(uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) + bool CCS_GetContentFile(NetworkService service, uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) { CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/{:08x}", _getCCSUrl(), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(service, fmt::format("{}/{:016x}/{:08x}", _getCCSUrl(service), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setWriteCallback(cbWriteCallback, userData); req.setTimeout(0); if (!req.submitRequest(false)) @@ -570,11 +579,11 @@ namespace NAPI return true; } - NAPI_CCSGetContentH3_Result CCS_GetContentH3File(uint64 titleId, uint32 contentId) + NAPI_CCSGetContentH3_Result CCS_GetContentH3File(NetworkService service, uint64 titleId, uint32 contentId) { NAPI_CCSGetContentH3_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/{:08x}.h3", _getCCSUrl(), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(service, fmt::format("{}/{:016x}/{:08x}.h3", _getCCSUrl(service), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request content hash file {:08x}.h3 for title {:016X}", contentId, titleId)); diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index 776baf3..e498d07 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -119,7 +119,7 @@ CurlRequestHelper::~CurlRequestHelper() curl_easy_cleanup(m_curl); } -void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) +void CurlRequestHelper::initate(NetworkService service, std::string url, SERVER_SSL_CONTEXT sslContext) { // reset parameters m_headerExtraFields.clear(); @@ -131,8 +131,10 @@ void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 60); // SSL - if (GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() == NetworkService::Custom || ActiveSettings::GetNetworkService() == NetworkService::Pretendo){ //Remove once Pretendo has SSL - curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); + if (IsNetworkServiceSSLDisabled(service)) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); } else if (sslContext == SERVER_SSL_CONTEXT::ACT || sslContext == SERVER_SSL_CONTEXT::TAGAYA) { @@ -256,18 +258,24 @@ bool CurlRequestHelper::submitRequest(bool isPost) return true; } -CurlSOAPHelper::CurlSOAPHelper() +CurlSOAPHelper::CurlSOAPHelper(NetworkService service) { m_curl = curl_easy_init(); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); // SSL - if (!GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() != NetworkService::Pretendo && ActiveSettings::GetNetworkService() != NetworkService::Custom) { //Remove once Pretendo has SSL - curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); - curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + if (!IsNetworkServiceSSLDisabled(service)) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); } - if(GetConfig().proxy_server.GetValue() != "") + else + { + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + if (GetConfig().proxy_server.GetValue() != "") { curl_easy_setopt(m_curl, CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); } diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h index 6403f07..adfe739 100644 --- a/src/Cemu/napi/napi_helper.h +++ b/src/Cemu/napi/napi_helper.h @@ -38,7 +38,7 @@ public: return m_curl; } - void initate(std::string url, SERVER_SSL_CONTEXT sslContext); + void initate(NetworkService service, std::string url, SERVER_SSL_CONTEXT sslContext); void addHeaderField(const char* fieldName, std::string_view value); void addPostField(const char* fieldName, std::string_view value); void setWriteCallback(bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); @@ -74,7 +74,7 @@ private: class CurlSOAPHelper // todo - make this use CurlRequestHelper { public: - CurlSOAPHelper(); + CurlSOAPHelper(NetworkService service); ~CurlSOAPHelper(); CURL* getCURL() diff --git a/src/Cemu/napi/napi_idbe.cpp b/src/Cemu/napi/napi_idbe.cpp index acf7799..db7fda2 100644 --- a/src/Cemu/napi/napi_idbe.cpp +++ b/src/Cemu/napi/napi_idbe.cpp @@ -54,11 +54,11 @@ namespace NAPI AES128_CBC_decrypt((uint8*)iconData, (uint8*)iconData, sizeof(IDBEIconDataV0), aesKey, iv); } - std::vector<uint8> IDBE_RequestRawEncrypted(uint64 titleId) + std::vector<uint8> IDBE_RequestRawEncrypted(NetworkService networkService, uint64 titleId) { CurlRequestHelper req; std::string requestUrl; - switch (ActiveSettings::GetNetworkService()) + switch (networkService) { case NetworkService::Pretendo: requestUrl = PretendoURLs::IDBEURL; @@ -72,7 +72,7 @@ namespace NAPI break; } requestUrl.append(fmt::format(fmt::runtime("/{0:02X}/{1:016X}.idbe"), (uint32)((titleId >> 8) & 0xFF), titleId)); - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); + req.initate(networkService, requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); if (!req.submitRequest(false)) { @@ -90,7 +90,7 @@ namespace NAPI return receivedData; } - std::optional<IDBEIconDataV0> IDBE_Request(uint64 titleId) + std::optional<IDBEIconDataV0> IDBE_Request(NetworkService networkService, uint64 titleId) { if (titleId == 0x000500301001500A || titleId == 0x000500301001510A || @@ -101,7 +101,7 @@ namespace NAPI return std::nullopt; } - std::vector<uint8> idbeData = IDBE_RequestRawEncrypted(titleId); + std::vector<uint8> idbeData = IDBE_RequestRawEncrypted(networkService, titleId); if (idbeData.size() < 0x22) return std::nullopt; if (idbeData[0] != 0) diff --git a/src/Cemu/napi/napi_version.cpp b/src/Cemu/napi/napi_version.cpp index 9fc7155..a1f5879 100644 --- a/src/Cemu/napi/napi_version.cpp +++ b/src/Cemu/napi/napi_version.cpp @@ -18,7 +18,7 @@ namespace NAPI CurlRequestHelper req; std::string requestUrl; - switch (ActiveSettings::GetNetworkService()) + switch (authInfo.GetService()) { case NetworkService::Pretendo: requestUrl = PretendoURLs::TAGAYAURL; @@ -32,7 +32,7 @@ namespace NAPI break; } requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country)); - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + req.initate(authInfo.GetService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { @@ -63,7 +63,7 @@ namespace NAPI { NAPI_VersionList_Result result; CurlRequestHelper req; - req.initate(fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request update list")); diff --git a/src/Cemu/ncrypto/ncrypto.cpp b/src/Cemu/ncrypto/ncrypto.cpp index 8a989d2..bbda681 100644 --- a/src/Cemu/ncrypto/ncrypto.cpp +++ b/src/Cemu/ncrypto/ncrypto.cpp @@ -20,7 +20,8 @@ void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size); -extern bool hasSeepromMem; // remove later +extern bool hasSeepromMem; // remove later (migrate otp/seeprom loading & parsing to this class) +extern bool hasOtpMem; // remove later namespace NCrypto { @@ -792,6 +793,16 @@ namespace NCrypto return (CafeConsoleRegion)seepromRegionU32[3]; } + bool OTP_IsPresent() + { + return hasOtpMem; + } + + bool HasDataForConsoleCert() + { + return SEEPROM_IsPresent() && OTP_IsPresent(); + } + std::string GetRegionAsString(CafeConsoleRegion regionCode) { if (regionCode == CafeConsoleRegion::EUR) @@ -957,6 +968,11 @@ namespace NCrypto return it->second; } + size_t GetCountryCount() + { + return g_countryTable.size(); + } + void unitTests() { base64Tests(); diff --git a/src/Cemu/ncrypto/ncrypto.h b/src/Cemu/ncrypto/ncrypto.h index 51f3d9c..5f399ad 100644 --- a/src/Cemu/ncrypto/ncrypto.h +++ b/src/Cemu/ncrypto/ncrypto.h @@ -205,7 +205,11 @@ namespace NCrypto CafeConsoleRegion SEEPROM_GetRegion(); std::string GetRegionAsString(CafeConsoleRegion regionCode); const char* GetCountryAsString(sint32 index); // returns NN if index is not valid or known + size_t GetCountryCount(); bool SEEPROM_IsPresent(); + bool OTP_IsPresent(); + + bool HasDataForConsoleCert(); void unitTests(); } \ No newline at end of file diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 2049bd6..81662ab 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -39,7 +39,6 @@ ActiveSettings::LoadOnce( g_config.SetFilename(GetConfigPath("settings.xml").generic_wstring()); g_config.Load(); - LaunchSettings::ChangeNetworkServiceURL(GetConfig().account.active_service); std::string additionalErrorInfo; s_has_required_online_files = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo) == IOS_CRYPTO_ONLINE_REQ_OK; return failed_write_access; diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index b7a79a1..1731f50 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -69,11 +69,7 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) ("account,a", po::value<std::string>(), "Persistent id of account") ("force-interpreter", po::value<bool>()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler") - ("enable-gdbstub", po::value<bool>()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger") - - ("act-url", po::value<std::string>(), "URL prefix for account server") - ("ecs-url", po::value<std::string>(), "URL for ECS service"); - + ("enable-gdbstub", po::value<bool>()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger"); po::options_description hidden{ "Hidden options" }; hidden.add_options() @@ -190,16 +186,6 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) if (vm.count("output")) log_path = vm["output"].as<std::wstring>(); - // urls - if (vm.count("act-url")) - { - serviceURL_ACT = vm["act-url"].as<std::string>(); - if (serviceURL_ACT.size() > 0 && serviceURL_ACT.back() == '/') - serviceURL_ACT.pop_back(); - } - if (vm.count("ecs-url")) - serviceURL_ECS = vm["ecs-url"].as<std::string>(); - if(!extract_path.empty()) { ExtractorTool(extract_path, output_path, log_path); @@ -280,24 +266,3 @@ bool LaunchSettings::ExtractorTool(std::wstring_view wud_path, std::string_view return true; } - - -void LaunchSettings::ChangeNetworkServiceURL(int ID){ - NetworkService Network = static_cast<NetworkService>(ID); - switch (Network) - { - case NetworkService::Pretendo: - serviceURL_ACT = PretendoURLs::ACTURL; - serviceURL_ECS = PretendoURLs::ECSURL; - break; - case NetworkService::Custom: - serviceURL_ACT = GetNetworkConfig().urls.ACT.GetValue(); - serviceURL_ECS = GetNetworkConfig().urls.ECS.GetValue(); - break; - case NetworkService::Nintendo: - default: - serviceURL_ACT = NintendoURLs::ACTURL; - serviceURL_ECS = NintendoURLs::ECSURL; - break; - } -} diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index be989e6..b0f673a 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -29,10 +29,6 @@ public: static std::optional<uint32> GetPersistentId() { return s_persistent_id; } - static std::string GetActURLPrefix() { return serviceURL_ACT; } - static std::string GetServiceURL_ecs() { return serviceURL_ECS; } - static void ChangeNetworkServiceURL(int ID); - private: inline static std::optional<fs::path> s_load_game_file{}; inline static std::optional<uint64> s_load_title_id{}; @@ -48,12 +44,6 @@ private: inline static std::optional<uint32> s_persistent_id{}; - // service URLS - inline static std::string serviceURL_ACT; - inline static std::string serviceURL_ECS; - // todo - npts and other boss urls - - static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path); }; diff --git a/src/config/NetworkSettings.cpp b/src/config/NetworkSettings.cpp index 5cc66a9..b086d0a 100644 --- a/src/config/NetworkSettings.cpp +++ b/src/config/NetworkSettings.cpp @@ -30,8 +30,6 @@ void NetworkConfig::Load(XMLConfigParser& parser) urls.BOSS = u.get("boss", NintendoURLs::BOSSURL); urls.TAGAYA = u.get("tagaya", NintendoURLs::TAGAYAURL); urls.OLV = u.get("olv", NintendoURLs::OLVURL); - if (static_cast<NetworkService>(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) - LaunchSettings::ChangeNetworkServiceURL(2); } bool NetworkConfig::XMLExists() @@ -41,7 +39,6 @@ bool NetworkConfig::XMLExists() { if (static_cast<NetworkService>(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) { - LaunchSettings::ChangeNetworkServiceURL(0); GetConfig().account.active_service = 0; } return false; diff --git a/src/config/NetworkSettings.h b/src/config/NetworkSettings.h index 26137cd..be31118 100644 --- a/src/config/NetworkSettings.h +++ b/src/config/NetworkSettings.h @@ -3,13 +3,15 @@ #include "ConfigValue.h" #include "XMLConfig.h" - -enum class NetworkService { -Nintendo, -Pretendo, -Custom, +enum class NetworkService +{ + Nintendo, + Pretendo, + Custom }; -struct NetworkConfig { + +struct NetworkConfig +{ NetworkConfig() { @@ -69,4 +71,15 @@ struct PretendoURLs { typedef XMLDataConfig<NetworkConfig, &NetworkConfig::Load, &NetworkConfig::Save> XMLNetworkConfig_t; extern XMLNetworkConfig_t n_config; -inline NetworkConfig& GetNetworkConfig() { return n_config.data();}; \ No newline at end of file +inline NetworkConfig& GetNetworkConfig() { return n_config.data();}; + +inline bool IsNetworkServiceSSLDisabled(NetworkService service) +{ + if(service == NetworkService::Nintendo) + return false; + else if(service == NetworkService::Pretendo) + return true; + else if(service == NetworkService::Custom) + return GetNetworkConfig().disablesslver.GetValue(); + return false; +} \ No newline at end of file diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 7f11d4c..505a09c 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -332,7 +332,7 @@ void CemuApp::CreateDefaultFiles(bool first_start) if (!fs::exists(countryFile)) { std::ofstream file(countryFile); - for (sint32 i = 0; i < 201; i++) + for (sint32 i = 0; i < NCrypto::GetCountryCount(); i++) { const char* countryCode = NCrypto::GetCountryAsString(i); if (boost::iequals(countryCode, "NN")) diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index e33cfbf..27ce37f 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -686,8 +686,10 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) if (!NetworkConfig::XMLExists()) m_active_service->Enable(2, false); + m_active_service->SetItemToolTip(0, _("Connect to the official Nintendo Network Service")); + m_active_service->SetItemToolTip(1, _("Connect to the Pretendo Network Service")); + m_active_service->SetItemToolTip(2, _("Connect to a custom Network Service (configured via network_services.xml)")); - m_active_service->SetToolTip(_("Connect to which Network Service")); m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); content->Add(m_active_service, 0, wxEXPAND | wxALL, 5); @@ -762,7 +764,7 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail)); wxPGChoices countries; - for (int i = 0; i < 195; ++i) + for (int i = 0; i < NCrypto::GetCountryCount(); ++i) { const auto country = NCrypto::GetCountryAsString(i); if (country && (i == 0 || !boost::equals(country, "NN"))) @@ -1948,8 +1950,6 @@ void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) { - LaunchSettings::ChangeNetworkServiceURL(m_active_service->GetSelection()); - UpdateAccountInformation(); } diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index 669a1aa..00e7992 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -31,17 +31,15 @@ #include <wx/dirdlg.h> #include <wx/notebook.h> +#include "Cafe/IOSU/legacy/iosu_crypto.h" #include "config/ActiveSettings.h" #include "gui/dialogs/SaveImport/SaveImportWindow.h" #include "Cafe/Account/Account.h" #include "Cemu/Tools/DownloadManager/DownloadManager.h" #include "gui/CemuApp.h" - #include "Cafe/TitleList/TitleList.h" - -#include "resource/embedded/resources.h" - #include "Cafe/TitleList/SaveList.h" +#include "resource/embedded/resources.h" wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent); wxDEFINE_EVENT(wxEVT_TITLE_SEARCH_COMPLETE, wxCommandEvent); @@ -155,6 +153,7 @@ wxPanel* TitleManager::CreateDownloadManagerPage() { auto* row = new wxBoxSizer(wxHORIZONTAL); +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_account = new wxChoice(panel, wxID_ANY); m_account->SetMinSize({ 250,-1 }); auto accounts = Account::GetAccounts(); @@ -172,6 +171,7 @@ wxPanel* TitleManager::CreateDownloadManagerPage() } row->Add(m_account, 0, wxALL, 5); +#endif m_connect = new wxButton(panel, wxID_ANY, _("Connect")); m_connect->Bind(wxEVT_BUTTON, &TitleManager::OnConnect, this); @@ -180,7 +180,17 @@ wxPanel* TitleManager::CreateDownloadManagerPage() sizer->Add(row, 0, wxEXPAND, 5); } +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_status_text = new wxStaticText(panel, wxID_ANY, _("Select an account and press Connect")); +#else + if(!NCrypto::HasDataForConsoleCert()) + { + m_status_text = new wxStaticText(panel, wxID_ANY, _("Valid online files are required to download eShop titles. For more information, go to the Account tab in the General Settings.")); + m_connect->Enable(false); + } + else + m_status_text = new wxStaticText(panel, wxID_ANY, _("Click on Connect to load the list of downloadable titles")); +#endif this->Bind(wxEVT_SET_TEXT, &TitleManager::OnSetStatusText, this); sizer->Add(m_status_text, 0, wxALL, 5); @@ -720,9 +730,10 @@ void TitleManager::OnSaveImport(wxCommandEvent& event) void TitleManager::InitiateConnect() { // init connection to download manager if queued +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN uint32 persistentId = (uint32)(uintptr_t)m_account->GetClientData(m_account->GetSelection()); auto& account = Account::GetAccount(persistentId); - +#endif DownloadManager* dlMgr = DownloadManager::GetInstance(); dlMgr->reset(); m_download_list->SetCurrentDownloadMgr(dlMgr); @@ -742,7 +753,15 @@ void TitleManager::InitiateConnect() TitleManager::Callback_ConnectStatusUpdate, TitleManager::Callback_AddDownloadableTitle, TitleManager::Callback_RemoveDownloadableTitle); - dlMgr->connect(account.GetAccountId(), account.GetAccountPasswordCache(), NCrypto::SEEPROM_GetRegion(), NCrypto::GetCountryAsString(account.GetCountry()), NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64); + std::string accountName; + std::array<uint8, 32> accountPassword; + std::string accountCountry; +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN + accountName = account.GetAccountId(); + accountPassword = account.GetAccountPasswordCache(); + accountCountry.assign(NCrypto::GetCountryAsString(account.GetCountry())); +#endif + dlMgr->connect(accountName, accountPassword, NCrypto::SEEPROM_GetRegion(), accountCountry, NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64); } void TitleManager::OnConnect(wxCommandEvent& event) @@ -787,7 +806,9 @@ void TitleManager::OnDisconnect(wxCommandEvent& event) void TitleManager::SetConnected(bool state) { +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_account->Enable(!state); +#endif m_connect->Enable(!state); m_show_titles->Enable(state); diff --git a/src/gui/TitleManager.h b/src/gui/TitleManager.h index 2973618..ae28404 100644 --- a/src/gui/TitleManager.h +++ b/src/gui/TitleManager.h @@ -8,6 +8,8 @@ #include "Cemu/Tools/DownloadManager/DownloadManager.h" +#define DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN 0 + class wxCheckBox; class wxStaticText; class wxListEvent; From 5be98da0ac5279a4e05eee22f24f3cb807ceb8f5 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 27 Apr 2024 15:49:49 +0200 Subject: [PATCH 074/130] OpenGL: Fix a crash when GL_VERSION is null (#1187) --- src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 28e91b8..cf134a5 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -386,7 +386,7 @@ void OpenGLRenderer::GetVendorInformation() cemuLog_log(LogType::Force, "GL_RENDERER: {}", glRendererString ? glRendererString : "unknown"); cemuLog_log(LogType::Force, "GL_VERSION: {}", glVersionString ? glVersionString : "unknown"); - if(boost::icontains(glVersionString, "Mesa")) + if(glVersionString && boost::icontains(glVersionString, "Mesa")) { m_vendor = GfxVendor::Mesa; return; From fdf239929ff923eb4b28a16fc4754202391cbc3f Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Mon, 29 Apr 2024 00:24:43 +0200 Subject: [PATCH 075/130] nsysnet: Various improvements (#1188) - Do not raise an assert for unimplemented optnames - recvfrom: src_addr and addrlen can be NULL - getsockopt: Implement SO_TYPE --- src/Cafe/OS/libs/nsysnet/nsysnet.cpp | 86 ++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index 128c19a..88bca8a 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -36,15 +36,46 @@ #define WU_SO_REUSEADDR 0x0004 #define WU_SO_KEEPALIVE 0x0008 +#define WU_SO_DONTROUTE 0x0010 +#define WU_SO_BROADCAST 0x0020 +#define WU_SO_LINGER 0x0080 +#define WU_SO_OOBINLINE 0x0100 +#define WU_SO_TCPSACK 0x0200 #define WU_SO_WINSCALE 0x0400 #define WU_SO_SNDBUF 0x1001 #define WU_SO_RCVBUF 0x1002 +#define WU_SO_SNDLOWAT 0x1003 +#define WU_SO_RCVLOWAT 0x1004 #define WU_SO_LASTERROR 0x1007 +#define WU_SO_TYPE 0x1008 +#define WU_SO_HOPCNT 0x1009 +#define WU_SO_MAXMSG 0x1010 +#define WU_SO_RXDATA 0x1011 +#define WU_SO_TXDATA 0x1012 +#define WU_SO_MYADDR 0x1013 #define WU_SO_NBIO 0x1014 #define WU_SO_BIO 0x1015 #define WU_SO_NONBLOCK 0x1016 +#define WU_SO_UNKNOWN1019 0x1019 // tcp related +#define WU_SO_UNKNOWN101A 0x101A // tcp related +#define WU_SO_UNKNOWN101B 0x101B // tcp related +#define WU_SO_NOSLOWSTART 0x4000 +#define WU_SO_RUSRBUF 0x10000 -#define WU_TCP_NODELAY 0x2004 +#define WU_TCP_ACKDELAYTIME 0x2001 +#define WU_TCP_NOACKDELAY 0x2002 +#define WU_TCP_MAXSEG 0x2003 +#define WU_TCP_NODELAY 0x2004 +#define WU_TCP_UNKNOWN 0x2005 // amount of mss received before sending an ack + +#define WU_IP_TOS 3 +#define WU_IP_TTL 4 +#define WU_IP_MULTICAST_IF 9 +#define WU_IP_MULTICAST_TTL 10 +#define WU_IP_MULTICAST_LOOP 11 +#define WU_IP_ADD_MEMBERSHIP 12 +#define WU_IP_DROP_MEMBERSHIP 13 +#define WU_IP_UNKNOWN 14 #define WU_SOL_SOCKET -1 // this constant differs from Win32 socket API @@ -548,7 +579,7 @@ void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) } else { - cemuLog_logDebug(LogType::Force, "setsockopt(): Unsupported optname 0x{:08x}", optname); + cemuLog_logDebug(LogType::Force, "setsockopt(WU_SOL_SOCKET): Unsupported optname 0x{:08x}", optname); } } else if (level == WU_IPPROTO_TCP) @@ -564,18 +595,22 @@ void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) assert_dbg(); } else - assert_dbg(); + { + cemuLog_logDebug(LogType::Force, "setsockopt(WU_IPPROTO_TCP): Unsupported optname 0x{:08x}", optname); + } } else if (level == WU_IPPROTO_IP) { hostLevel = IPPROTO_IP; - if (optname == 0xC) + if (optname == WU_IP_MULTICAST_IF || optname == WU_IP_MULTICAST_TTL || + optname == WU_IP_MULTICAST_LOOP || optname == WU_IP_ADD_MEMBERSHIP || + optname == WU_IP_DROP_MEMBERSHIP) { - // unknown + cemuLog_logDebug(LogType::Socket, "todo: setsockopt() for multicast"); } - else if( optname == 0x4 ) + else if(optname == WU_IP_TTL || optname == WU_IP_TOS) { - cemuLog_logDebug(LogType::Force, "setsockopt with unsupported opname 4 for IPPROTO_IP"); + cemuLog_logDebug(LogType::Force, "setsockopt(WU_IPPROTO_IP): Unsupported optname 0x{:08x}", optname); } else assert_dbg(); @@ -649,6 +684,16 @@ void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) *(uint32*)optval = _swapEndianU32(optvalLE); // used by Lost Reavers after some loading screens } + else if (optname == WU_SO_TYPE) + { + if (memory_readU32(optlenMPTR) != 4) + assert_dbg(); + int optvalLE = 0; + socklen_t optlenLE = 4; + memory_writeU32(optlenMPTR, 4); + *(uint32*)optval = _swapEndianU32(vs->type); + r = WU_SO_SUCCESS; + } else if (optname == WU_SO_NONBLOCK) { if (memory_readU32(optlenMPTR) != 4) @@ -661,12 +706,12 @@ void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) } else { - cemu_assert_debug(false); + cemuLog_logDebug(LogType::Force, "getsockopt(WU_SOL_SOCKET): Unsupported optname 0x{:08x}", optname); } } else { - cemu_assert_debug(false); + cemuLog_logDebug(LogType::Force, "getsockopt(): Unsupported level 0x{:08x}", level); } osLib_returnFromFunction(hCPU, r); @@ -1533,7 +1578,7 @@ void nsysnetExport_getaddrinfo(PPCInterpreter_t* hCPU) void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::Socket, "recvfrom({},0x{:08x},{},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + cemuLog_log(LogType::Socket, "recvfrom({},0x{:08x},{},0x{:x},0x{:x},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); @@ -1562,8 +1607,8 @@ void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) if (vs->isNonBlocking) requestIsNonBlocking = vs->isNonBlocking; - socklen_t fromLenHost = *fromLen; sockaddr fromAddrHost; + socklen_t fromLenHost = sizeof(fromAddrHost); sint32 wsaError = 0; while( true ) @@ -1605,9 +1650,13 @@ void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) if (r < 0) cemu_assert_debug(false); cemuLog_logDebug(LogType::Force, "recvfrom returned {} bytes", r); - *fromLen = fromLenHost; - fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); - memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + + // fromAddr and fromLen can be NULL + if (fromAddr && fromLen) { + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + } _setSockError(0); osLib_returnFromFunction(hCPU, r); @@ -1657,9 +1706,12 @@ void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) assert_dbg(); } - *fromLen = fromLenHost; - fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); - memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + // fromAddr and fromLen can be NULL + if (fromAddr && fromLen) { + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + } _translateError(r <= 0 ? -1 : 0, wsaError); From b2be3c13df58cd6108d090b892d2801a615fd60d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Apr 2024 04:19:15 +0200 Subject: [PATCH 076/130] Add example network_services.xml --- dist/network_services.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 dist/network_services.xml diff --git a/dist/network_services.xml b/dist/network_services.xml new file mode 100644 index 0000000..0c0f2e3 --- /dev/null +++ b/dist/network_services.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<content> + <networkname>CustomExample</networkname> + <disablesslverification>0</disablesslverification> + <urls> + <act>https://account.nintendo.net</act> + <ecs>https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP</ecs> + <nus>https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP</nus> + <ias>https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP</ias> + <ccsu>https://ccs.wup.shop.nintendo.net/ccs/download</ccsu> + <ccs>http://ccs.cdn.wup.shop.nintendo.net/ccs/download</ccs> + <idbe>https://idbe-wup.cdn.nintendo.net/icondata</idbe> + <boss>https://npts.app.nintendo.net/p01/tasksheet</boss> + <tagaya>https://tagaya.wup.shop.nintendo.net/tagaya/versionlist</tagaya> + <olv>https://discovery.olv.nintendo.net/v1/endpoint</olv> + </urls> +</content> \ No newline at end of file From c038e758aeac76ed55f8f92bbc22f4815cc7689a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Apr 2024 04:18:17 +0200 Subject: [PATCH 077/130] IOSU: Clean up resource on service shutdown Also set device-dependent thread name --- src/Cafe/IOSU/nn/iosu_nn_service.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index 1fb5c77..c888b4f 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -155,7 +155,9 @@ namespace iosu void IPCService::ServiceThread() { - SetThreadName("IPCService"); + std::string serviceName = m_devicePath.substr(m_devicePath.find_last_of('/') == std::string::npos ? 0 : m_devicePath.find_last_of('/') + 1); + serviceName.insert(0, "NNsvc_"); + SetThreadName(serviceName.c_str()); m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); @@ -208,6 +210,7 @@ namespace iosu IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } + IOS_DestroyMessageQueue(m_msgQueueId); m_threadInitialized = false; } }; From 1c73dc9e1b824f4618e60704b2c1e6682b749ee0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 30 Apr 2024 23:09:00 +0200 Subject: [PATCH 078/130] Implement proc_ui.rpl + stub SYSSwitchToEManual() to avoid softlocks - Full reimplementation of proc_ui.rpl with all 19 exports - Foreground/Background messages now go to the coreinit system message queue as they should (instead of using a hack where proc_ui receives them directly) - Add missing coreinit API needed by proc_ui: OSGetPFID(), OSGetUPID(), OSGetTitleID(), __OSCreateThreadType() - Use big-endian types in OSMessage - Flesh out the stubs for OSDriver_Register and OSDriver_Unregister a bit more since we need to call it from proc_ui. Similiar small tweaks to other coreinit API - Stub sysapp SYSSwitchToEManual() and _SYSSwitchToEManual() in such a way that they will trigger the expected background/foreground transition, avoiding softlocks in games that call these functions --- .gitignore | 1 + src/Cafe/OS/common/OSCommon.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit.cpp | 22 - src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h | 6 + src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.h | 5 + .../libs/coreinit/coreinit_MessageQueue.cpp | 7 + .../OS/libs/coreinit/coreinit_MessageQueue.h | 19 +- src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp | 96 ++ src/Cafe/OS/libs/coreinit/coreinit_Misc.h | 20 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 19 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 8 +- src/Cafe/OS/libs/coreinit/coreinit_Time.cpp | 7 +- src/Cafe/OS/libs/coreinit/coreinit_Time.h | 3 +- src/Cafe/OS/libs/gx2/GX2_Misc.h | 2 + src/Cafe/OS/libs/nn_olv/nn_olv.cpp | 3 +- src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 944 +++++++++++++++++- src/Cafe/OS/libs/proc_ui/proc_ui.h | 45 +- src/Cafe/OS/libs/sysapp/sysapp.cpp | 23 + src/Common/CafeString.h | 5 + 21 files changed, 1146 insertions(+), 95 deletions(-) diff --git a/.gitignore b/.gitignore index c10b38d..67a268a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ bin/sdcard/* bin/screenshots/* bin/dump/* bin/cafeLibs/* +bin/portable/* bin/keys.txt !bin/shaderCache/info.txt diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp index a441002..5297f20 100644 --- a/src/Cafe/OS/common/OSCommon.cpp +++ b/src/Cafe/OS/common/OSCommon.cpp @@ -221,5 +221,5 @@ void osLib_load() nsyskbd::nsyskbd_load(); swkbd::load(); camera::load(); - procui_load(); + proc_ui::load(); } diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index 660f874..e18d0e8 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -179,27 +179,6 @@ void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 1); } -typedef struct -{ - MPTR getDriverName; - MPTR ukn04; - MPTR onAcquiredForeground; - MPTR onReleaseForeground; - MPTR ukn10; -}OSDriverCallbacks_t; - -void coreinitExport_OSDriver_Register(PPCInterpreter_t* hCPU) -{ -#ifdef CEMU_DEBUG_ASSERT - cemuLog_log(LogType::Force, "OSDriver_Register(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); -#endif - OSDriverCallbacks_t* driverCallbacks = (OSDriverCallbacks_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); - - // todo - - osLib_returnFromFunction(hCPU, 0); -} - namespace coreinit { sint32 OSGetCoreId() @@ -379,7 +358,6 @@ void coreinit_load() coreinit::miscInit(); osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData); osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig); - osLib_addFunction("coreinit", "OSDriver_Register", coreinitExport_OSDriver_Register); // async callbacks InitializeAsyncCallback(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h index 0be8226..2a3172c 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h @@ -2,6 +2,12 @@ namespace coreinit { + enum class RplEntryReason + { + Loaded = 1, + Unloaded = 2, + }; + uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc); void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc); uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index a007f5e..0ca8fb8 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -837,7 +837,7 @@ namespace coreinit FSAsyncResult* FSGetAsyncResult(OSMessage* msg) { - return (FSAsyncResult*)memory_getPointerFromVirtualOffset(_swapEndianU32(msg->message)); + return (FSAsyncResult*)memory_getPointerFromVirtualOffset(msg->message); } sint32 __FSProcessAsyncResult(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, sint32 fsStatus, uint32 errHandling) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp index 4b14747..cff4ee2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp @@ -126,7 +126,7 @@ namespace coreinit return physicalAddr; } - void OSMemoryBarrier(PPCInterpreter_t* hCPU) + void OSMemoryBarrier() { // no-op } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h index cfb3ed0..0a212f6 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h @@ -5,4 +5,9 @@ namespace coreinit void InitializeMemory(); void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput); + + void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC); + void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size); + + void OSMemoryBarrier(); } \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp index 6e6a7bc..cbcfa4d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp @@ -3,6 +3,8 @@ namespace coreinit { + void UpdateSystemMessageQueue(); + void HandleReceivedSystemMessage(OSMessage* msg); SysAllocator<OSMessageQueue> g_systemMessageQueue; SysAllocator<OSMessage, 16> _systemMessageQueueArray; @@ -27,6 +29,9 @@ namespace coreinit bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags) { + bool isSystemMessageQueue = (msgQueue == g_systemMessageQueue); + if(isSystemMessageQueue) + UpdateSystemMessageQueue(); __OSLockScheduler(msgQueue); while (msgQueue->usedCount == (uint32be)0) { @@ -50,6 +55,8 @@ namespace coreinit if (!msgQueue->threadQueueSend.isEmpty()) msgQueue->threadQueueSend.wakeupSingleThreadWaitQueue(true); __OSUnlockScheduler(msgQueue); + if(isSystemMessageQueue) + HandleReceivedSystemMessage(msg); return true; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h index 6741ab8..35fdc3e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h @@ -3,12 +3,21 @@ namespace coreinit { + enum class SysMessageId : uint32 + { + MsgAcquireForeground = 0xFACEF000, + MsgReleaseForeground = 0xFACEBACC, + MsgExit = 0xD1E0D1E0, + HomeButtonDenied = 0xCCC0FFEE, + NetIoStartOrStop = 0xAAC0FFEE, + }; + struct OSMessage { - MPTR message; - uint32 data0; - uint32 data1; - uint32 data2; + uint32be message; + uint32be data0; + uint32be data1; + uint32be data2; }; struct OSMessageQueue @@ -36,5 +45,7 @@ namespace coreinit bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg); sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags); + OSMessageQueue* OSGetSystemMessageQueue(); + void InitializeMessageQueue(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index 2d7468c..e2b5066 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -1,5 +1,6 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" #include <pugixml.hpp> @@ -371,6 +372,23 @@ namespace coreinit return true; } + uint32 OSGetPFID() + { + return 15; // hardcoded as game + } + + uint32 OSGetUPID() + { + return OSGetPFID(); + } + + uint64 s_currentTitleId; + + uint64 OSGetTitleID() + { + return s_currentTitleId; + } + uint32 s_sdkVersion; uint32 __OSGetProcessSDKVersion() @@ -470,9 +488,78 @@ namespace coreinit return 0; } + void OSReleaseForeground() + { + cemuLog_logDebug(LogType::Force, "OSReleaseForeground not implemented"); + } + + bool s_transitionToBackground = false; + bool s_transitionToForeground = false; + + void StartBackgroundForegroundTransition() + { + s_transitionToBackground = true; + s_transitionToForeground = true; + } + + // called at the beginning of OSReceiveMessage if the queue is the system message queue + void UpdateSystemMessageQueue() + { + if(!OSIsInterruptEnabled()) + return; + cemu_assert_debug(!__OSHasSchedulerLock()); + // normally syscall 0x2E is used to get the next message + // for now we just have some preliminary logic here to allow a fake transition to background & foreground + if(s_transitionToBackground) + { + // add transition to background message + OSMessage msg{}; + msg.data0 = stdx::to_underlying(SysMessageId::MsgReleaseForeground); + msg.data1 = 0; // 1 -> System is shutting down 0 -> Begin transitioning to background + OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue(); + if(OSSendMessage(systemMessageQueue, &msg, 0)) + s_transitionToBackground = false; + return; + } + if(s_transitionToForeground) + { + // add transition to foreground message + OSMessage msg{}; + msg.data0 = stdx::to_underlying(SysMessageId::MsgAcquireForeground); + msg.data1 = 1; // ? + msg.data2 = 1; // ? + OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue(); + if(OSSendMessage(systemMessageQueue, &msg, 0)) + s_transitionToForeground = false; + return; + } + } + + // called when OSReceiveMessage returns a message from the system message queue + void HandleReceivedSystemMessage(OSMessage* msg) + { + cemu_assert_debug(!__OSHasSchedulerLock()); + cemuLog_log(LogType::Force, "Receiving message: {:08x}", (uint32)msg->data0); + } + + uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3) + { + cemuLog_logDebug(LogType::Force, "OSDriver_Register stubbed"); + return 0; + } + + uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId) + { + cemuLog_logDebug(LogType::Force, "OSDriver_Deregister stubbed"); + return 0; + } + void miscInit() { + s_currentTitleId = CafeSystem::GetForegroundTitleId(); s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion(); + s_transitionToBackground = false; + s_transitionToForeground = false; cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); cafeExportRegister("coreinit", OSReport, LogType::Placeholder); @@ -480,6 +567,10 @@ namespace coreinit cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); + + cafeExportRegister("coreinit", OSGetPFID, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetUPID, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetTitleID, LogType::Placeholder); cafeExportRegister("coreinit", __OSGetProcessSDKVersion, LogType::Placeholder); g_homeButtonMenuEnabled = true; // enabled by default @@ -489,6 +580,11 @@ namespace coreinit cafeExportRegister("coreinit", OSLaunchTitleByPathl, LogType::Placeholder); cafeExportRegister("coreinit", OSRestartGame, LogType::Placeholder); + + cafeExportRegister("coreinit", OSReleaseForeground, LogType::Placeholder); + + cafeExportRegister("coreinit", OSDriver_Register, LogType::Placeholder); + cafeExportRegister("coreinit", OSDriver_Deregister, LogType::Placeholder); } }; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h index 4a74d49..7abba92 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h @@ -2,9 +2,29 @@ namespace coreinit { + uint32 OSGetUPID(); + uint32 OSGetPFID(); + uint64 OSGetTitleID(); uint32 __OSGetProcessSDKVersion(); uint32 OSLaunchTitleByPathl(const char* path, uint32 pathLength, uint32 argc); uint32 OSRestartGame(uint32 argc, MEMPTR<char>* argv); + void OSReleaseForeground(); + + void StartBackgroundForegroundTransition(); + + struct OSDriverInterface + { + MEMPTR<void> getDriverName; + MEMPTR<void> init; + MEMPTR<void> onAcquireForeground; + MEMPTR<void> onReleaseForeground; + MEMPTR<void> done; + }; + static_assert(sizeof(OSDriverInterface) == 0x14); + + uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3); + uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId); + 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 809d7be..654e57a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -294,9 +294,9 @@ namespace coreinit __OSUnlockScheduler(); } - bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) + bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { - OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop2) - stackSize, stackSize, attr, threadType); + OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop) - stackSize, stackSize, attr, threadType); thread->context.gpr[3] = _swapEndianU32(numParam); // num arguments thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); // arguments pointer __OSSetThreadBasePriority(thread, priority); @@ -317,9 +317,15 @@ namespace coreinit return true; } - bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr) + bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr) { - return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop2, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + } + + // alias to OSCreateThreadType, similar to OSCreateThread, but with an additional parameter for the thread type + bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) + { + return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam) @@ -445,12 +451,12 @@ namespace coreinit return currentThread->specificArray[index].GetPtr(); } - void OSSetThreadName(OSThread_t* thread, char* name) + void OSSetThreadName(OSThread_t* thread, const char* name) { thread->threadName = name; } - char* OSGetThreadName(OSThread_t* thread) + const char* OSGetThreadName(OSThread_t* thread) { return thread->threadName.GetPtr(); } @@ -1371,6 +1377,7 @@ namespace coreinit { cafeExportRegister("coreinit", OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSCreateThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", __OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSExitThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetCurrentThread, LogType::CoreinitThread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index e619d5b..b401d96 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -449,7 +449,7 @@ struct OSThread_t /* +0x578 */ sint32 alarmRelatedUkn; /* +0x57C */ std::array<MEMPTR<void>, 16> specificArray; /* +0x5BC */ betype<THREAD_TYPE> type; - /* +0x5C0 */ MEMPTR<char> threadName; + /* +0x5C0 */ MEMPTR<const char> threadName; /* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? /* +0x5C8 */ uint32 userStackPointer; @@ -505,6 +505,7 @@ namespace coreinit void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size); bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); + bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType); bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam); void OSExitThread(sint32 exitValue); @@ -519,8 +520,8 @@ namespace coreinit bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority); uint32 OSGetThreadAffinity(OSThread_t* thread); - void OSSetThreadName(OSThread_t* thread, char* name); - char* OSGetThreadName(OSThread_t* thread); + void OSSetThreadName(OSThread_t* thread, const char* name); + const char* OSGetThreadName(OSThread_t* thread); sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount); sint32 OSResumeThread(OSThread_t* thread); @@ -530,6 +531,7 @@ namespace coreinit void OSSuspendThread(OSThread_t* thread); void OSSleepThread(OSThreadQueue* threadQueue); void OSWakeupThread(OSThreadQueue* threadQueue); + bool OSJoinThread(OSThread_t* thread, uint32be* exitValue); void OSTestThreadCancelInternal(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp index 5a75b40..d6fc27b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp @@ -22,10 +22,9 @@ namespace coreinit osLib_returnFromFunction(hCPU, (uint32)osTime); } - void export_OSGetTime(PPCInterpreter_t* hCPU) + uint64 OSGetTime() { - uint64 osTime = coreinit_getOSTime(); - osLib_returnFromFunction64(hCPU, osTime); + return coreinit_getOSTime(); } void export_OSGetSystemTime(PPCInterpreter_t* hCPU) @@ -360,7 +359,7 @@ namespace coreinit void InitializeTimeAndCalendar() { - osLib_addFunction("coreinit", "OSGetTime", export_OSGetTime); + cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder); osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime); osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick); osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.h b/src/Cafe/OS/libs/coreinit/coreinit_Time.h index f5dcf22..018e8eb 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.h @@ -45,7 +45,8 @@ namespace coreinit }; void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct); - + uint64 OSGetTime(); + uint64 coreinit_getOSTime(); uint64 coreinit_getTimerTick(); diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.h b/src/Cafe/OS/libs/gx2/GX2_Misc.h index e6ac801..38a728c 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Misc.h +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.h @@ -19,5 +19,7 @@ namespace GX2 void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 surfaceFormat, E_TVBUFFERMODE bufferMode); void GX2SetTVGamma(float gamma); + void GX2Invalidate(uint32 invalidationFlags, MPTR invalidationAddr, uint32 invalidationSize); + void GX2MiscInit(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp index 99c113c..1916a18 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -9,6 +9,7 @@ #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Misc.h" namespace nn { @@ -37,7 +38,7 @@ namespace nn void StubPostAppReleaseBackground(PPCInterpreter_t* hCPU) { coreinit::OSSleepTicks(ESPRESSO_TIMER_CLOCK * 2); // Sleep 2s - ProcUI_SendForegroundMessage(); + coreinit::StartBackgroundForegroundTransition(); } sint32 StubPostApp(void* pAnyPostParam) diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index 7de8691..91d15af 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -1,57 +1,905 @@ #include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" +#include "Cafe/OS/libs/coreinit/coreinit_Misc.h" +#include "Cafe/OS/libs/coreinit/coreinit_Memory.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_FG.h" +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "Cafe/OS/libs/gx2/GX2_Misc.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Common/CafeString.h" #include "proc_ui.h" -#define PROCUI_STATUS_FOREGROUND 0 -#define PROCUI_STATUS_BACKGROUND 1 -#define PROCUI_STATUS_RELEASING 2 -#define PROCUI_STATUS_EXIT 3 +// proc_ui is a utility wrapper to help apps with the transition between foreground and background +// some games (like Xenoblades Chronicles X) bypass proc_ui.rpl and listen to OSGetSystemMessageQueue() directly -uint32 ProcUIProcessMessages() +using namespace coreinit; + +namespace proc_ui { - return PROCUI_STATUS_FOREGROUND; -} + enum class ProcUICoreThreadCommand + { + AcquireForeground = 0x0, + ReleaseForeground = 0x1, + Exit = 0x2, + NetIoStart = 0x3, + NetIoStop = 0x4, + HomeButtonDenied = 0x5, + Initial = 0x6 + }; + + struct ProcUIInternalCallbackEntry + { + coreinit::OSAlarm_t alarm; + uint64be tickDelay; + MEMPTR<void> funcPtr; + MEMPTR<void> userParam; + sint32be priority; + MEMPTR<ProcUIInternalCallbackEntry> next; + }; + static_assert(sizeof(ProcUIInternalCallbackEntry) == 0x70); + + struct ProcUICallbackList + { + MEMPTR<ProcUIInternalCallbackEntry> first; + }; + static_assert(sizeof(ProcUICallbackList) == 0x4); + + std::atomic_bool s_isInitialized; + bool s_isInForeground; + bool s_isInShutdown; + bool s_isForegroundProcess; + bool s_previouslyWasBlocking; + ProcUIStatus s_currentProcUIStatus; + MEMPTR<void> s_saveCallback; // no param and no return value, set by ProcUIInit() + MEMPTR<void> s_saveCallbackEx; // with custom param and return value, set by ProcUIInitEx() + MEMPTR<void> s_saveCallbackExUserParam; + MEMPTR<coreinit::OSMessageQueue> s_systemMessageQueuePtr; + SysAllocator<coreinit::OSEvent> s_eventStateMessageReceived; + SysAllocator<coreinit::OSEvent> s_eventWaitingBeforeReleaseForeground; + SysAllocator<coreinit::OSEvent> s_eventBackgroundThreadGotMessage; + // procUI core threads + uint32 s_coreThreadStackSize; + bool s_coreThreadsCreated; + std::atomic<ProcUICoreThreadCommand> s_commandForCoreThread; + SysAllocator<OSThread_t> s_coreThreadArray[Espresso::CORE_COUNT]; + MEMPTR<void> s_coreThreadStackPerCore[Espresso::CORE_COUNT]; + SysAllocator<CafeString<22>> s_coreThread0NameBuffer; + SysAllocator<CafeString<22>> s_coreThread1NameBuffer; + SysAllocator<CafeString<22>> s_coreThread2NameBuffer; + SysAllocator<coreinit::OSEvent> s_eventCoreThreadsNewCommandReady; + SysAllocator<coreinit::OSEvent> s_eventCoreThreadsCommandDone; + SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousA; + SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousB; + SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousC; + // background thread + MEMPTR<void> s_backgroundThreadStack; + SysAllocator<OSThread_t> s_backgroundThread; + // user defined heap + MEMPTR<void> s_memoryPoolHeapPtr; + MEMPTR<void> s_memAllocPtr; + MEMPTR<void> s_memFreePtr; + // draw done release + bool s_drawDoneReleaseCalled; + // memory storage + MEMPTR<void> s_bucketStorageBasePtr; + MEMPTR<void> s_mem1StorageBasePtr; + // callbacks + ProcUICallbackList s_callbacksType0_AcquireForeground[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType1_ReleaseForeground[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType2_Exit[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType3_NetIoStart[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType4_NetIoStop[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType5_HomeButtonDenied[Espresso::CORE_COUNT]; + ProcUICallbackList* const s_CallbackTables[stdx::to_underlying(ProcUICallbackId::COUNT)] = + {s_callbacksType0_AcquireForeground, s_callbacksType1_ReleaseForeground, s_callbacksType2_Exit, s_callbacksType3_NetIoStart, s_callbacksType4_NetIoStop, s_callbacksType5_HomeButtonDenied}; + ProcUICallbackList s_backgroundCallbackList; + // driver + bool s_driverIsActive; + uint32be s_driverArgUkn1; + uint32be s_driverArgUkn2; + bool s_driverInBackground; + SysAllocator<OSDriverInterface> s_ProcUIDriver; + SysAllocator<CafeString<16>> s_ProcUIDriverName; -uint32 ProcUIInForeground(PPCInterpreter_t* hCPU) -{ - return 1; // true means application is in foreground -} + void* _AllocMem(uint32 size) + { + MEMPTR<void> r{PPCCoreCallback(s_memAllocPtr, size)}; + return r.GetPtr(); + } -struct ProcUICallback -{ - MPTR callback; - void* data; - sint32 priority; + void _FreeMem(void* ptr) + { + PPCCoreCallback(s_memFreePtr.GetMPTR(), ptr); + } + + void ClearCallbacksWithoutMemFree() + { + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) + s_CallbackTables[i][coreIndex].first = nullptr; + } + s_backgroundCallbackList.first = nullptr; + } + + void ShutdownThreads() + { + if ( !s_coreThreadsCreated) + return; + s_commandForCoreThread = ProcUICoreThreadCommand::Initial; + coreinit::OSMemoryBarrier(); + OSSignalEvent(&s_eventCoreThreadsNewCommandReady); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + coreinit::OSJoinThread(&s_coreThreadArray[coreIndex], nullptr); + for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) + { + s_CallbackTables[i][coreIndex].first = nullptr; // memory is not cleanly released? + } + } + OSResetEvent(&s_eventCoreThreadsNewCommandReady); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + _FreeMem(s_coreThreadStackPerCore[coreIndex]); + s_coreThreadStackPerCore[coreIndex] = nullptr; + } + _FreeMem(s_backgroundThreadStack); + s_backgroundThreadStack = nullptr; + s_backgroundCallbackList.first = nullptr; // memory is not cleanly released? + s_coreThreadsCreated = false; + } + + void DoCallbackChain(ProcUIInternalCallbackEntry* entry) + { + while (entry) + { + uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam); + if ( r ) + cemuLog_log(LogType::APIErrors, "ProcUI: Callback returned error {}\n", r); + entry = entry->next; + } + } + + void AlarmDoBackgroundCallback(PPCInterpreter_t* hCPU) + { + coreinit::OSAlarm_t* arg = MEMPTR<coreinit::OSAlarm_t>(hCPU->gpr[3]); + ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)arg; + uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam); + if ( r ) + cemuLog_log(LogType::APIErrors, "ProcUI: Background callback returned error {}\n", r); + osLib_returnFromFunction(hCPU, 0); // return type is void + } + + void StartBackgroundAlarms() + { + ProcUIInternalCallbackEntry* cb = s_backgroundCallbackList.first; + while(cb) + { + coreinit::OSCreateAlarm(&cb->alarm); + uint64 currentTime = coreinit::OSGetTime(); + coreinit::OSSetPeriodicAlarm(&cb->alarm, currentTime, cb->tickDelay, RPLLoader_MakePPCCallable(AlarmDoBackgroundCallback)); + cb = cb->next; + } + } + + void CancelBackgroundAlarms() + { + ProcUIInternalCallbackEntry* entry = s_backgroundCallbackList.first; + while (entry) + { + OSCancelAlarm(&entry->alarm); + entry = entry->next; + } + } + + void ProcUICoreThread(PPCInterpreter_t* hCPU) + { + uint32 coreIndex = hCPU->gpr[3]; + cemu_assert_debug(coreIndex == OSGetCoreId()); + while (true) + { + OSWaitEvent(&s_eventCoreThreadsNewCommandReady); + ProcUIInternalCallbackEntry* cbChain = nullptr; + cemuLog_logDebug(LogType::Force, "ProcUI: Core {} got command {}", coreIndex, (uint32)s_commandForCoreThread.load()); + auto cmd = s_commandForCoreThread.load(); + switch(cmd) + { + case ProcUICoreThreadCommand::Initial: + { + // signal to shut down thread + osLib_returnFromFunction(hCPU, 0); + return; + } + case ProcUICoreThreadCommand::AcquireForeground: + cbChain = s_callbacksType0_AcquireForeground[coreIndex].first; + break; + case ProcUICoreThreadCommand::ReleaseForeground: + cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first; + break; + case ProcUICoreThreadCommand::Exit: + cbChain = s_callbacksType2_Exit[coreIndex].first; + break; + case ProcUICoreThreadCommand::NetIoStart: + cbChain = s_callbacksType3_NetIoStart[coreIndex].first; + break; + case ProcUICoreThreadCommand::NetIoStop: + cbChain = s_callbacksType4_NetIoStop[coreIndex].first; + break; + case ProcUICoreThreadCommand::HomeButtonDenied: + cbChain = s_callbacksType5_HomeButtonDenied[coreIndex].first; + break; + default: + cemu_assert_suspicious(); // invalid command + } + if(cmd == ProcUICoreThreadCommand::AcquireForeground) + { + if (coreIndex == 2) + CancelBackgroundAlarms(); + cbChain = s_callbacksType0_AcquireForeground[coreIndex].first; + } + else if(cmd == ProcUICoreThreadCommand::ReleaseForeground) + { + if (coreIndex == 2) + StartBackgroundAlarms(); + cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first; + } + DoCallbackChain(cbChain); + OSWaitRendezvous(&s_coreThreadRendezvousA, 7); + if ( !coreIndex ) + { + OSInitRendezvous(&s_coreThreadRendezvousC); + OSResetEvent(&s_eventCoreThreadsNewCommandReady); + } + OSWaitRendezvous(&s_coreThreadRendezvousB, 7); + if ( !coreIndex ) + { + OSInitRendezvous(&s_coreThreadRendezvousA); + OSSignalEvent(&s_eventCoreThreadsCommandDone); + } + OSWaitRendezvous(&s_coreThreadRendezvousC, 7); + if ( !coreIndex ) + OSInitRendezvous(&s_coreThreadRendezvousB); + if (cmd == ProcUICoreThreadCommand::ReleaseForeground) + { + OSWaitEvent(&s_eventWaitingBeforeReleaseForeground); + OSReleaseForeground(); + } + } + osLib_returnFromFunction(hCPU, 0); + } + + void RecreateProcUICoreThreads() + { + ShutdownThreads(); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + s_coreThreadStackPerCore[coreIndex] = _AllocMem(s_coreThreadStackSize); + } + s_backgroundThreadStack = _AllocMem(s_coreThreadStackSize); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + __OSCreateThreadType(&s_coreThreadArray[coreIndex], RPLLoader_MakePPCCallable(ProcUICoreThread), coreIndex, nullptr, + (uint8*)s_coreThreadStackPerCore[coreIndex].GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, 16, + (1<<coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + OSResumeThread(&s_coreThreadArray[coreIndex]); + } + s_coreThread0NameBuffer->assign("{SYS ProcUI Core 0}"); + s_coreThread1NameBuffer->assign("{SYS ProcUI Core 1}"); + s_coreThread2NameBuffer->assign("{SYS ProcUI Core 2}"); + OSSetThreadName(&s_coreThreadArray[0], s_coreThread0NameBuffer->c_str()); + OSSetThreadName(&s_coreThreadArray[1], s_coreThread1NameBuffer->c_str()); + OSSetThreadName(&s_coreThreadArray[2], s_coreThread2NameBuffer->c_str()); + s_coreThreadsCreated = true; + } + + void _SubmitCommandToCoreThreads(ProcUICoreThreadCommand cmd) + { + s_commandForCoreThread = cmd; + OSMemoryBarrier(); + OSResetEvent(&s_eventCoreThreadsCommandDone); + OSSignalEvent(&s_eventCoreThreadsNewCommandReady); + OSWaitEvent(&s_eventCoreThreadsCommandDone); + } + + void ProcUIInitInternal() + { + if( s_isInitialized.exchange(true) ) + return; + if (!s_memoryPoolHeapPtr) + { + // user didn't specify a custom heap, use default heap instead + s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); + } + OSInitEvent(&s_eventStateMessageReceived, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitEvent(&s_eventCoreThreadsNewCommandReady, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitEvent(&s_eventWaitingBeforeReleaseForeground, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitEvent(&s_eventCoreThreadsCommandDone, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitRendezvous(&s_coreThreadRendezvousA); + OSInitRendezvous(&s_coreThreadRendezvousB); + OSInitRendezvous(&s_coreThreadRendezvousC); + OSInitEvent(&s_eventBackgroundThreadGotMessage, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + s_currentProcUIStatus = ProcUIStatus::Foreground; + s_drawDoneReleaseCalled = false; + s_isInForeground = true; + s_coreThreadStackSize = 0x2000; + s_systemMessageQueuePtr = coreinit::OSGetSystemMessageQueue(); + uint32 upid = coreinit::OSGetUPID(); + s_isForegroundProcess = upid == 2 || upid == 15; // either Wii U Menu or game title, both are RAMPID 7 (foreground process) + RecreateProcUICoreThreads(); + ClearCallbacksWithoutMemFree(); + } + + void ProcUIInit(MEMPTR<void> callbackReadyToRelease) + { + s_saveCallback = callbackReadyToRelease; + s_saveCallbackEx = nullptr; + s_saveCallbackExUserParam = nullptr; + ProcUIInitInternal(); + } + + void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam) + { + s_saveCallback = nullptr; + s_saveCallbackEx = callbackReadyToReleaseEx; + s_saveCallbackExUserParam = userParam; + ProcUIInitInternal(); + } + + void ProcUIShutdown() + { + if (!s_isInitialized.exchange(false)) + return; + if ( !s_isInForeground ) + CancelBackgroundAlarms(); + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + OSSetThreadPriority(&s_coreThreadArray[i], 0); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit); + ProcUIClearCallbacks(); + ShutdownThreads(); + } + + bool ProcUIIsRunning() + { + return s_isInitialized; + } + + bool ProcUIInForeground() + { + return s_isInForeground; + } + + bool ProcUIInShutdown() + { + return s_isInShutdown; + } + + void AddCallbackInternal(void* funcPtr, void* userParam, uint64 tickDelay, sint32 priority, ProcUICallbackList& callbackList) + { + if ( __OSGetProcessSDKVersion() < 21102 ) + { + // in earlier COS versions it was possible/allowed to register a callback before initializing ProcUI + s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); + } + else if ( !s_isInitialized ) + { + cemuLog_log(LogType::Force, "ProcUI: Trying to register callback before init"); + cemu_assert_suspicious(); + } + ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)_AllocMem(sizeof(ProcUIInternalCallbackEntry)); + entry->funcPtr = funcPtr; + entry->userParam = userParam; + entry->tickDelay = tickDelay; + entry->priority = priority; + ProcUIInternalCallbackEntry* cur = callbackList.first; + cur = callbackList.first; + if (!cur || cur->priority > priority) + { + // insert as the first element + entry->next = cur; + callbackList.first = entry; + } + else + { + // find the correct position to insert + while (cur->next && cur->next->priority < priority) + cur = cur->next; + entry->next = cur->next; + cur->next = entry; + } + } + + void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex) + { + if(callbackType >= ProcUICallbackId::COUNT) + { + cemuLog_log(LogType::Force, "ProcUIRegisterCallback: Invalid callback type {}", stdx::to_underlying(callbackType)); + return; + } + if(callbackType != ProcUICallbackId::AcquireForeground) + priority = -priority; + AddCallbackInternal(funcPtr, userParam, priority, 0, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]); + } + + void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority) + { + ProcUIRegisterCallbackCore(callbackType, funcPtr, userParam, priority, OSGetCoreId()); + } + + void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay) + { + AddCallbackInternal(funcPtr, userParam, 0, tickDelay, s_backgroundCallbackList); + } + + void FreeCallbackChain(ProcUICallbackList& callbackList) + { + ProcUIInternalCallbackEntry* entry = callbackList.first; + while (entry) + { + ProcUIInternalCallbackEntry* next = entry->next; + _FreeMem(entry); + entry = next; + } + callbackList.first = nullptr; + } + + void ProcUIClearCallbacks() + { + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) + { + FreeCallbackChain(s_CallbackTables[i][coreIndex]); + } + } + if (!s_isInForeground) + CancelBackgroundAlarms(); + FreeCallbackChain(s_backgroundCallbackList); + } + + void ProcUISetSaveCallback(void* funcPtr, void* userParam) + { + s_saveCallback = nullptr; + s_saveCallbackEx = funcPtr; + s_saveCallbackExUserParam = userParam; + } + + void ProcUISetCallbackStackSize(uint32 newStackSize) + { + s_coreThreadStackSize = newStackSize; + if( s_isInitialized ) + RecreateProcUICoreThreads(); + } + + uint32 ProcUICalcMemorySize(uint32 numCallbacks) + { + // each callback entry is 0x70 bytes with 0x14 bytes of allocator overhead (for ExpHeap). But for some reason proc_ui on 5.5.5 seems to reserve 0x8C0 bytes per callback? + uint32 stackReserveSize = (Espresso::CORE_COUNT + 1) * s_coreThreadStackSize; // 3 core threads + 1 message receive thread + uint32 callbackReserveSize = 0x8C0 * numCallbacks; + return stackReserveSize + callbackReserveSize + 100; + } + + void _MemAllocFromMemoryPool(PPCInterpreter_t* hCPU) + { + uint32 size = hCPU->gpr[3]; + MEMPTR<void> r = MEMAllocFromExpHeapEx((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), size, 4); + osLib_returnFromFunction(hCPU, r.GetMPTR()); + } + + void _FreeToMemoryPoolExpHeap(PPCInterpreter_t* hCPU) + { + MEMPTR<void> mem{hCPU->gpr[3]}; + MEMFreeToExpHeap((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), mem.GetPtr()); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 ProcUISetMemoryPool(void* memBase, uint32 size) + { + s_memAllocPtr = RPLLoader_MakePPCCallable(_MemAllocFromMemoryPool); + s_memFreePtr = RPLLoader_MakePPCCallable(_FreeToMemoryPoolExpHeap); + s_memoryPoolHeapPtr = MEMCreateExpHeapEx(memBase, size, MEM_HEAP_OPTION_THREADSAFE); + return s_memoryPoolHeapPtr ? 0 : -1; + } + + void ProcUISetBucketStorage(void* memBase, uint32 size) + { + MEMPTR<void> fgBase; + uint32be fgFreeSize; + OSGetForegroundBucketFreeArea((MPTR*)&fgBase, (MPTR*)&fgFreeSize); + if(fgFreeSize < size) + cemuLog_log(LogType::Force, "ProcUISetBucketStorage: Buffer size too small"); + s_bucketStorageBasePtr = memBase; + } + + void ProcUISetMEM1Storage(void* memBase, uint32 size) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + if(memBoundSize < size) + cemuLog_log(LogType::Force, "ProcUISetMEM1Storage: Buffer size too small"); + s_mem1StorageBasePtr = memBase; + } + + void ProcUIDrawDoneRelease() + { + s_drawDoneReleaseCalled = true; + } + + OSMessage g_lastMsg; + + void ProcUI_BackgroundThread_ReceiveSingleMessage(PPCInterpreter_t* hCPU) + { + // the background thread receives messages in a loop until the title is either exited or foreground is acquired + while ( true ) + { + OSReceiveMessage(s_systemMessageQueuePtr, &g_lastMsg, OS_MESSAGE_BLOCK); // blocking receive + SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)g_lastMsg.data0); + if(lastMsgId == SysMessageId::MsgExit || lastMsgId == SysMessageId::MsgAcquireForeground) + break; + else if (lastMsgId == SysMessageId::HomeButtonDenied) + { + cemu_assert_suspicious(); // Home button denied should not be sent to background app + } + else if ( lastMsgId == SysMessageId::NetIoStartOrStop ) + { + if (g_lastMsg.data1 ) + { + // NetIo start message + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + DoCallbackChain(s_callbacksType3_NetIoStart[i].first); + } + else + { + // NetIo stop message + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + DoCallbackChain(s_callbacksType4_NetIoStop[i].first); + } + } + else + { + cemuLog_log(LogType::Force, "ProcUI: BackgroundThread received invalid message 0x{:08x}", lastMsgId); + } + } + OSSignalEvent(&s_eventBackgroundThreadGotMessage); + osLib_returnFromFunction(hCPU, 0); + } + + // handle received message + // if the message is Exit this function returns false, in all other cases it returns true + bool ProcessSysMessage(OSMessage* msg) + { + SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)msg->data0); + if ( lastMsgId == SysMessageId::MsgAcquireForeground ) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Acquire Foreground message"); + s_isInShutdown = false; + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::AcquireForeground); + s_currentProcUIStatus = ProcUIStatus::Foreground; + s_isInForeground = true; + OSMemoryBarrier(); + OSSignalEvent(&s_eventStateMessageReceived); + return true; + } + else if (lastMsgId == SysMessageId::MsgExit) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Exit message"); + s_isInShutdown = true; + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit); + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + FreeCallbackChain(s_callbacksType2_Exit[i]); + s_currentProcUIStatus = ProcUIStatus::Exit; + OSMemoryBarrier(); + OSSignalEvent(&s_eventStateMessageReceived); + return 0; + } + if (lastMsgId == SysMessageId::MsgReleaseForeground) + { + if (msg->data1 != 0) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message as part of shutdown initiation"); + s_isInShutdown = true; + } + else + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message"); + } + s_currentProcUIStatus = ProcUIStatus::Releasing; + OSResetEvent(&s_eventStateMessageReceived); + // dont submit a command for the core threads yet, we need to wait for ProcUIDrawDoneRelease() + } + else if (lastMsgId == SysMessageId::HomeButtonDenied) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Home Button Denied message"); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::HomeButtonDenied); + } + else if ( lastMsgId == SysMessageId::NetIoStartOrStop ) + { + if (msg->data1 != 0) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Start message"); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStart); + } + else + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Stop message"); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStop); + } + } + else + { + cemuLog_log(LogType::Force, "ProcUI: Received unknown message 0x{:08x}", (uint32)lastMsgId); + } + return true; + } + + ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground) + { + OSMessage msg; + if (!s_isInitialized) + { + cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: ProcUI not initialized"); + cemu_assert_suspicious(); + return ProcUIStatus::Foreground; + } + if ( !isBlockingInBackground && OSGetCoreId() != 2 ) + { + cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Non-blocking call must run on core 2"); + } + if (s_previouslyWasBlocking && isBlockingInBackground ) + { + cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Cannot switch to blocking mode when in background"); + } + s_currentProcUIStatus = s_isInForeground ? ProcUIStatus::Foreground : ProcUIStatus::Background; + if (s_drawDoneReleaseCalled) + { + s_isInForeground = false; + s_currentProcUIStatus = ProcUIStatus::Background; + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::ReleaseForeground); + OSResetEvent(&s_eventWaitingBeforeReleaseForeground); + if(s_saveCallback) + PPCCoreCallback(s_saveCallback); + if(s_saveCallbackEx) + PPCCoreCallback(s_saveCallbackEx, s_saveCallbackExUserParam); + if (s_isForegroundProcess && isBlockingInBackground) + { + // start background thread + __OSCreateThreadType(&s_backgroundThread, RPLLoader_MakePPCCallable(ProcUI_BackgroundThread_ReceiveSingleMessage), + 0, nullptr, (uint8*)s_backgroundThreadStack.GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, + 16, (1<<2), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + OSResumeThread(&s_backgroundThread); + s_previouslyWasBlocking = true; + } + cemuLog_logDebug(LogType::Force, "ProcUI: Releasing foreground"); + OSSignalEvent(&s_eventWaitingBeforeReleaseForeground); + s_drawDoneReleaseCalled = false; + } + if (s_isInForeground || !isBlockingInBackground) + { + // non-blocking mode + if ( OSReceiveMessage(s_systemMessageQueuePtr, &msg, 0) ) + { + s_previouslyWasBlocking = false; + if ( !ProcessSysMessage(&msg) ) + return s_currentProcUIStatus; + // continue below, if we are now in background then ProcUIProcessMessages enters blocking mode + } + } + // blocking mode (if in background and param is true) + while (!s_isInForeground && isBlockingInBackground) + { + if ( !s_isForegroundProcess) + { + OSReceiveMessage(s_systemMessageQueuePtr, &msg, OS_MESSAGE_BLOCK); + s_previouslyWasBlocking = false; + if ( !ProcessSysMessage(&msg) ) + return s_currentProcUIStatus; + } + // this code should only run if the background thread was started? Maybe rearrange the code to make this more clear + OSWaitEvent(&s_eventBackgroundThreadGotMessage); + OSResetEvent(&s_eventBackgroundThreadGotMessage); + OSJoinThread(&s_backgroundThread, nullptr); + msg = g_lastMsg; // g_lastMsg is set by the background thread + s_previouslyWasBlocking = false; + if ( !ProcessSysMessage(&msg) ) + return s_currentProcUIStatus; + } + return s_currentProcUIStatus; + } + + ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground) + { + if (isBlockingInBackground) + { + while (s_currentProcUIStatus == ProcUIStatus::Background) + OSWaitEvent(&s_eventStateMessageReceived); + } + return s_currentProcUIStatus; + } + + const char* ProcUIDriver_GetName() + { + s_ProcUIDriverName->assign("ProcUI"); + return s_ProcUIDriverName->c_str(); + } + + void ProcUIDriver_Init(/* parameters unknown */) + { + s_driverIsActive = true; + OSMemoryBarrier(); + } + + void ProcUIDriver_OnDone(/* parameters unknown */) + { + if (s_driverIsActive) + { + ProcUIShutdown(); + s_driverIsActive = false; + OSMemoryBarrier(); + } + } + + void StoreMEM1AndFGBucket() + { + if (s_mem1StorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSBlockMove(s_mem1StorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); + } + if (s_bucketStorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSBlockMove(s_bucketStorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); + } + } + + void RestoreMEM1AndFGBucket() + { + if (s_mem1StorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSBlockMove(memBound.GetPtr(), s_mem1StorageBasePtr, memBoundSize, true); + GX2::GX2Invalidate(0x40, s_mem1StorageBasePtr.GetMPTR(), memBoundSize); + } + if (s_bucketStorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSBlockMove(memBound.GetPtr(), s_bucketStorageBasePtr, memBoundSize, true); + GX2::GX2Invalidate(0x40, memBound.GetMPTR(), memBoundSize); + } + } + + void ProcUIDriver_OnAcquiredForeground(/* parameters unknown */) + { + if (s_driverInBackground) + { + ProcUIDriver_Init(); + s_driverInBackground = false; + } + else + { + RestoreMEM1AndFGBucket(); + s_driverIsActive = true; + OSMemoryBarrier(); + } + } + + void ProcUIDriver_OnReleaseForeground(/* parameters unknown */) + { + StoreMEM1AndFGBucket(); + s_driverIsActive = false; + OSMemoryBarrier(); + } + + sint32 rpl_entry(uint32 moduleHandle, RplEntryReason reason) + { + if ( reason == RplEntryReason::Loaded ) + { + s_ProcUIDriver->getDriverName = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {MEMPTR<const char> namePtr(ProcUIDriver_GetName()); osLib_returnFromFunction(hCPU, namePtr.GetMPTR()); }); + s_ProcUIDriver->init = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_Init(); osLib_returnFromFunction(hCPU, 0); }); + s_ProcUIDriver->onAcquireForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnAcquiredForeground(); osLib_returnFromFunction(hCPU, 0); }); + s_ProcUIDriver->onReleaseForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnReleaseForeground(); osLib_returnFromFunction(hCPU, 0); }); + s_ProcUIDriver->done = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnDone(); osLib_returnFromFunction(hCPU, 0); }); + + s_driverIsActive = false; + s_driverArgUkn1 = 0; + s_driverArgUkn2 = 0; + s_driverInBackground = false; + uint32be ukn3; + OSDriver_Register(moduleHandle, 200, &s_ProcUIDriver, 0, &s_driverArgUkn1, &s_driverArgUkn2, &ukn3); + if ( ukn3 ) + { + if ( OSGetForegroundBucket(nullptr, nullptr) ) + { + ProcUIDriver_Init(); + OSMemoryBarrier(); + return 0; + } + s_driverInBackground = true; + } + OSMemoryBarrier(); + } + else if ( reason == RplEntryReason::Unloaded ) + { + ProcUIDriver_OnDone(); + OSDriver_Deregister(moduleHandle, 0); + } + return 0; + } + + void reset() + { + // set variables to their initial state as if the RPL was just loaded + s_isInitialized = false; + s_isInShutdown = false; + s_isInForeground = false; // ProcUIInForeground returns false until ProcUIInit(Ex) is called + s_isForegroundProcess = true; + s_saveCallback = nullptr; + s_saveCallbackEx = nullptr; + s_systemMessageQueuePtr = nullptr; + ClearCallbacksWithoutMemFree(); + s_currentProcUIStatus = ProcUIStatus::Foreground; + s_bucketStorageBasePtr = nullptr; + s_mem1StorageBasePtr = nullptr; + s_drawDoneReleaseCalled = false; + s_previouslyWasBlocking = false; + // core threads + s_coreThreadStackSize = 0; + s_coreThreadsCreated = false; + s_commandForCoreThread = ProcUICoreThreadCommand::Initial; + // background thread + s_backgroundThreadStack = nullptr; + // user defined heap + s_memoryPoolHeapPtr = nullptr; + s_memAllocPtr = nullptr; + s_memFreePtr = nullptr; + // driver + s_driverIsActive = false; + s_driverInBackground = false; + } + + void load() + { + reset(); + + cafeExportRegister("proc_ui", ProcUIInit, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInitEx, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIShutdown, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIIsRunning, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInShutdown, LogType::ProcUi); + + cafeExportRegister("proc_ui", ProcUIRegisterCallbackCore, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIRegisterBackgroundCallback, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIClearCallbacks, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetSaveCallback, LogType::ProcUi); + + cafeExportRegister("proc_ui", ProcUISetCallbackStackSize, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUICalcMemorySize, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetMemoryPool, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetBucketStorage, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetMEM1Storage, LogType::ProcUi); + + cafeExportRegister("proc_ui", ProcUIDrawDoneRelease, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISubProcessMessages, LogType::ProcUi); + + // manually call rpl_entry for now + rpl_entry(-1, RplEntryReason::Loaded); + } }; -std::unordered_map<uint32, ProcUICallback> 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); - cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi); - cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi); -} \ No newline at end of file diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.h b/src/Cafe/OS/libs/proc_ui/proc_ui.h index 1cd04fb..8de7bb4 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.h +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.h @@ -1,5 +1,44 @@ -void procui_load(); +namespace proc_ui +{ + enum class ProcUIStatus + { + Foreground = 0, + Background = 1, + Releasing = 2, + Exit = 3 + }; -void ProcUI_SendForegroundMessage(); -void ProcUI_SendBackgroundMessage(); \ No newline at end of file + enum class ProcUICallbackId + { + AcquireForeground = 0, + ReleaseForeground = 1, + Exit = 2, + NetIoStart = 3, + NetIoStop = 4, + HomeButtonDenied = 5, + COUNT = 6 + }; + + void ProcUIInit(MEMPTR<void> callbackReadyToRelease); + void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam); + void ProcUIShutdown(); + bool ProcUIIsRunning(); + bool ProcUIInForeground(); + bool ProcUIInShutdown(); + void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority); + void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex); + void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay); + void ProcUIClearCallbacks(); + void ProcUISetSaveCallback(void* funcPtr, void* userParam); + void ProcUISetCallbackStackSize(uint32 newStackSize); + uint32 ProcUICalcMemorySize(uint32 numCallbacks); + sint32 ProcUISetMemoryPool(void* memBase, uint32 size); + void ProcUISetBucketStorage(void* memBase, uint32 size); + void ProcUISetMEM1Storage(void* memBase, uint32 size); + void ProcUIDrawDoneRelease(); + ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground); + ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground); + + void load(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/sysapp/sysapp.cpp b/src/Cafe/OS/libs/sysapp/sysapp.cpp index 413d535..ecaa940 100644 --- a/src/Cafe/OS/libs/sysapp/sysapp.cpp +++ b/src/Cafe/OS/libs/sysapp/sysapp.cpp @@ -639,11 +639,34 @@ namespace sysapp return coreinit::OSRestartGame(argc, argv); } + struct EManualArgs + { + sysStandardArguments_t stdArgs; + uint64be titleId; + }; + static_assert(sizeof(EManualArgs) == 0x10); + + void _SYSSwitchToEManual(EManualArgs* args) + { + // the struct has the titleId at offset 8 and standard args at 0 (total size is most likely 0x10) + cemuLog_log(LogType::Force, "SYSSwitchToEManual called. Opening the manual is not supported"); + coreinit::StartBackgroundForegroundTransition(); + } + + void SYSSwitchToEManual() + { + EManualArgs args{}; + args.titleId = coreinit::OSGetTitleID(); + _SYSSwitchToEManual(&args); + } + void load() { cafeExportRegisterFunc(SYSClearSysArgs, "sysapp", "SYSClearSysArgs", LogType::Placeholder); cafeExportRegisterFunc(_SYSLaunchTitleByPathFromLauncher, "sysapp", "_SYSLaunchTitleByPathFromLauncher", LogType::Placeholder); cafeExportRegisterFunc(SYSRelaunchTitle, "sysapp", "SYSRelaunchTitle", LogType::Placeholder); + cafeExportRegister("sysapp", _SYSSwitchToEManual, LogType::Placeholder); + cafeExportRegister("sysapp", SYSSwitchToEManual, LogType::Placeholder); } } diff --git a/src/Common/CafeString.h b/src/Common/CafeString.h index 45a515b..d902d72 100644 --- a/src/Common/CafeString.h +++ b/src/Common/CafeString.h @@ -20,6 +20,11 @@ class CafeString // fixed buffer size, null-terminated, PPC char return true; } + const char* c_str() + { + return (const char*)data; + } + uint8be data[N]; }; From e7c6862e19a277d0d8828c99a6874e69eedbd802 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 1 May 2024 01:55:55 +0200 Subject: [PATCH 079/130] DownloadManager: Fix missing updates --- src/Cemu/napi/napi_version.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cemu/napi/napi_version.cpp b/src/Cemu/napi/napi_version.cpp index a1f5879..5a85dde 100644 --- a/src/Cemu/napi/napi_version.cpp +++ b/src/Cemu/napi/napi_version.cpp @@ -31,7 +31,7 @@ namespace NAPI requestUrl = NintendoURLs::TAGAYAURL; break; } - requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country)); + requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country.empty() ? "NN" : authInfo.country)); req.initate(authInfo.GetService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) @@ -63,7 +63,7 @@ namespace NAPI { NAPI_VersionList_Result result; CurlRequestHelper req; - req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country.empty() ? "NN" : authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request update list")); From 379950d185852b3c2da14b40e30a872809ad0ac2 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 1 May 2024 05:06:50 +0200 Subject: [PATCH 080/130] coreinit+nn_save: Cleanup some legacy code --- src/Cafe/OS/libs/coreinit/coreinit_FG.cpp | 20 +- src/Cafe/OS/libs/coreinit/coreinit_FG.h | 2 +- src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 243 +++++++------ src/Cafe/OS/libs/coreinit/coreinit_FS.h | 84 ++--- src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp | 10 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp | 6 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.h | 2 +- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 6 +- src/Cafe/OS/libs/nn_save/nn_save.cpp | 339 ++++++------------ src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 12 +- src/Common/MemPtr.h | 6 - src/Common/StackAllocator.h | 1 - 12 files changed, 282 insertions(+), 449 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp index 15dcd6d..b751a8f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp @@ -55,19 +55,18 @@ namespace coreinit { // return full size of foreground bucket area if (offset) - *offset = MEMPTR<void>{ (uint32)MEMORY_FGBUCKET_AREA_ADDR }; + *offset = { (MPTR)MEMORY_FGBUCKET_AREA_ADDR }; if (size) *size = MEMORY_FGBUCKET_AREA_SIZE; // return true if in foreground return true; } - bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size) + bool OSGetForegroundBucketFreeArea(MEMPTR<void>* offset, uint32be* size) { uint8* freeAreaAddr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr(); - - *offset = _swapEndianU32(memory_getVirtualOffsetFromPointer(freeAreaAddr)); - *size = _swapEndianU32(FG_BUCKET_AREA_FREE_SIZE); + *offset = freeAreaAddr; + *size = FG_BUCKET_AREA_FREE_SIZE; // return true if in foreground return (fgAddr != nullptr); } @@ -82,15 +81,6 @@ namespace coreinit osLib_returnFromFunction(hCPU, r ? 1 : 0); } - void coreinitExport_OSGetForegroundBucketFreeArea(PPCInterpreter_t* hCPU) - { - debug_printf("OSGetForegroundBucketFreeArea(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); - ppcDefineParamMPTR(areaOutput, 0); - ppcDefineParamMPTR(areaSize, 1); - bool r = OSGetForegroundBucketFreeArea((MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaSize)); - osLib_returnFromFunction(hCPU, r ? 1 : 0); - } - void InitForegroundBucket() { uint32be fgSize; @@ -194,7 +184,7 @@ namespace coreinit void InitializeFG() { osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket); - osLib_addFunction("coreinit", "OSGetForegroundBucketFreeArea", coreinitExport_OSGetForegroundBucketFreeArea); + cafeExportRegister("coreinit", OSGetForegroundBucket, LogType::CoreinitMem); osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard); } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.h b/src/Cafe/OS/libs/coreinit/coreinit_FG.h index 846001b..0c2a3ee 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FG.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.h @@ -9,7 +9,7 @@ namespace coreinit bool __OSResizeCopyData(sint32 length); bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size); - bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size); + bool OSGetForegroundBucketFreeArea(MEMPTR<void>* offset, uint32be* size); void InitForegroundBucket(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 0ca8fb8..0fc8912 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -56,7 +56,7 @@ namespace coreinit OSUnlockMutex(&s_fsGlobalMutex); } - void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody); + void _debugVerifyCommand(const char* stage, FSCmdBlockBody* fsCmdBlockBody); bool sFSInitialized = true; // this should be false but it seems like some games rely on FSInit being called before main()? Twilight Princess for example reads files before it calls FSInit bool sFSShutdown = false; @@ -194,12 +194,12 @@ namespace coreinit } // return the aligned FSClientBody struct inside a FSClient struct - FSCmdBlockBody_t* __FSGetCmdBlockBody(FSCmdBlock_t* fsCmdBlock) + FSCmdBlockBody* __FSGetCmdBlockBody(FSCmdBlock_t* fsCmdBlock) { // align pointer to 64 bytes if (fsCmdBlock == nullptr) return nullptr; - FSCmdBlockBody_t* fsCmdBlockBody = (FSCmdBlockBody_t*)(((uintptr_t)fsCmdBlock + 0x3F) & ~0x3F); + FSCmdBlockBody* fsCmdBlockBody = (FSCmdBlockBody*)(((uintptr_t)fsCmdBlock + 0x3F) & ~0x3F); fsCmdBlockBody->selfCmdBlock = fsCmdBlock; return fsCmdBlockBody; } @@ -261,8 +261,8 @@ namespace coreinit fsCmdQueueBE->numCommandsInFlight = 0; fsCmdQueueBE->numMaxCommandsInFlight = numMaxCommandsInFlight; coreinit::OSFastMutex_Init(&fsCmdQueueBE->fastMutex, nullptr); - fsCmdQueueBE->firstMPTR = _swapEndianU32(0); - fsCmdQueueBE->lastMPTR = _swapEndianU32(0); + fsCmdQueueBE->first = nullptr; + fsCmdQueueBE->last = nullptr; } void FSInit() @@ -382,74 +382,71 @@ namespace coreinit Semaphore g_semaphoreQueuedCmds; - void __FSQueueCmdByPriority(FSCmdQueue* fsCmdQueueBE, FSCmdBlockBody_t* fsCmdBlockBody, bool stopAtEqualPriority) + void __FSQueueCmdByPriority(FSCmdQueue* fsCmdQueueBE, FSCmdBlockBody* fsCmdBlockBody, bool stopAtEqualPriority) { - MPTR fsCmdBlockBodyMPTR = memory_getVirtualOffsetFromPointer(fsCmdBlockBody); - if (_swapEndianU32(fsCmdQueueBE->firstMPTR) == MPTR_NULL) + if (!fsCmdQueueBE->first) { // queue is currently empty - cemu_assert(fsCmdQueueBE->lastMPTR == MPTR_NULL); - fsCmdQueueBE->firstMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); - fsCmdQueueBE->lastMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); - fsCmdBlockBody->nextMPTR = _swapEndianU32(MPTR_NULL); - fsCmdBlockBody->previousMPTR = _swapEndianU32(MPTR_NULL); + cemu_assert(!fsCmdQueueBE->last); + fsCmdQueueBE->first = fsCmdBlockBody; + fsCmdQueueBE->last = fsCmdBlockBody; + fsCmdBlockBody->next = nullptr; + fsCmdBlockBody->previous = nullptr; return; } // iterate from last to first element as long as iterated priority is lower - FSCmdBlockBody_t* fsCmdBlockBodyItrPrev = NULL; - FSCmdBlockBody_t* fsCmdBlockBodyItr = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(fsCmdQueueBE->lastMPTR)); + FSCmdBlockBody* fsCmdBlockBodyItrPrev = nullptr; + FSCmdBlockBody* fsCmdBlockBodyItr = fsCmdQueueBE->last; while (true) { - if (fsCmdBlockBodyItr == NULL) + if (!fsCmdBlockBodyItr) { // insert at the head of the list - fsCmdQueueBE->firstMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); - fsCmdBlockBody->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItrPrev)); - fsCmdBlockBody->previousMPTR = _swapEndianU32(MPTR_NULL); - fsCmdBlockBodyItrPrev->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdQueueBE->first = fsCmdBlockBody; + fsCmdBlockBody->next = fsCmdBlockBodyItrPrev; + fsCmdBlockBody->previous = nullptr; + fsCmdBlockBodyItrPrev->previous = fsCmdBlockBody; return; } // compare priority if ((stopAtEqualPriority && fsCmdBlockBodyItr->priority >= fsCmdBlockBody->priority) || (stopAtEqualPriority == false && fsCmdBlockBodyItr->priority > fsCmdBlockBody->priority)) { // insert cmd here - if (fsCmdBlockBodyItrPrev != NULL) + if (fsCmdBlockBodyItrPrev) { - fsCmdBlockBody->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItrPrev)); + fsCmdBlockBody->next = fsCmdBlockBodyItrPrev; } else { - fsCmdBlockBody->nextMPTR = _swapEndianU32(MPTR_NULL); - fsCmdQueueBE->lastMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); + fsCmdBlockBody->next = nullptr; + fsCmdQueueBE->last = fsCmdBlockBody; } - fsCmdBlockBody->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItr)); + fsCmdBlockBody->previous = fsCmdBlockBodyItr; if (fsCmdBlockBodyItrPrev) - fsCmdBlockBodyItrPrev->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); - fsCmdBlockBodyItr->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBodyItrPrev->previous = fsCmdBlockBody; + fsCmdBlockBodyItr->next = fsCmdBlockBody; return; } // next fsCmdBlockBodyItrPrev = fsCmdBlockBodyItr; - fsCmdBlockBodyItr = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(fsCmdBlockBodyItr->previousMPTR)); + fsCmdBlockBodyItr = fsCmdBlockBodyItr->previous; } } - FSCmdBlockBody_t* __FSTakeCommandFromQueue(FSCmdQueue* cmdQueue) + FSCmdBlockBody* __FSTakeCommandFromQueue(FSCmdQueue* cmdQueue) { - FSCmdBlockBody_t* dequeuedCmd = nullptr; - if (_swapEndianU32(cmdQueue->firstMPTR) != MPTR_NULL) + if (!cmdQueue->first) + return nullptr; + // dequeue cmd + FSCmdBlockBody* dequeuedCmd = cmdQueue->first; + if (cmdQueue->first == cmdQueue->last) + cmdQueue->last = nullptr; + cmdQueue->first = dequeuedCmd->next; + dequeuedCmd->next = nullptr; + if (dequeuedCmd->next) { - dequeuedCmd = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(cmdQueue->firstMPTR)); - // dequeue cmd - if (cmdQueue->firstMPTR == cmdQueue->lastMPTR) - cmdQueue->lastMPTR = _swapEndianU32(MPTR_NULL); - cmdQueue->firstMPTR = dequeuedCmd->nextMPTR; - dequeuedCmd->nextMPTR = _swapEndianU32(MPTR_NULL); - if (_swapEndianU32(dequeuedCmd->nextMPTR) != MPTR_NULL) - { - FSCmdBlockBody_t* fsCmdBodyNext = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(dequeuedCmd->nextMPTR)); - fsCmdBodyNext->previousMPTR = _swapEndianU32(MPTR_NULL); - } + FSCmdBlockBody* fsCmdBodyNext = dequeuedCmd->next; + fsCmdBodyNext->previous = nullptr; } return dequeuedCmd; } @@ -499,7 +496,7 @@ namespace coreinit FSLockMutex(); if (cmdQueue->numCommandsInFlight < cmdQueue->numMaxCommandsInFlight) { - FSCmdBlockBody_t* dequeuedCommand = __FSTakeCommandFromQueue(cmdQueue); + FSCmdBlockBody* dequeuedCommand = __FSTakeCommandFromQueue(cmdQueue); if (dequeuedCommand) { cmdQueue->numCommandsInFlight += 1; @@ -512,7 +509,7 @@ namespace coreinit FSUnlockMutex(); } - void __FSQueueDefaultFinishFunc(FSCmdBlockBody_t* fsCmdBlockBody, FS_RESULT result) + void __FSQueueDefaultFinishFunc(FSCmdBlockBody* fsCmdBlockBody, FS_RESULT result) { switch ((FSA_CMD_OPERATION_TYPE)fsCmdBlockBody->fsaShimBuffer.operationType.value()) { @@ -594,13 +591,13 @@ namespace coreinit void export___FSQueueDefaultFinishFunc(PPCInterpreter_t* hCPU) { - ppcDefineParamPtr(cmd, FSCmdBlockBody_t, 0); + ppcDefineParamPtr(cmd, FSCmdBlockBody, 0); FS_RESULT result = (FS_RESULT)PPCInterpreter_getCallParamU32(hCPU, 1); __FSQueueDefaultFinishFunc(cmd, static_cast<FS_RESULT>(result)); osLib_returnFromFunction(hCPU, 0); } - void __FSQueueCmd(FSCmdQueue* cmdQueue, FSCmdBlockBody_t* fsCmdBlockBody, MPTR finishCmdFunc) + void __FSQueueCmd(FSCmdQueue* cmdQueue, FSCmdBlockBody* fsCmdBlockBody, MPTR finishCmdFunc) { fsCmdBlockBody->cmdFinishFuncMPTR = finishCmdFunc; FSLockMutex(); @@ -676,7 +673,7 @@ namespace coreinit return FS_RESULT::FATAL_ERROR; } - void __FSCmdSubmitResult(FSCmdBlockBody_t* fsCmdBlockBody, FS_RESULT result) + void __FSCmdSubmitResult(FSCmdBlockBody* fsCmdBlockBody, FS_RESULT result) { _debugVerifyCommand("FSCmdSubmitResult", fsCmdBlockBody); @@ -720,7 +717,7 @@ namespace coreinit void __FSAIoctlResponseCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(iosResult, 0); - ppcDefineParamPtr(cmd, FSCmdBlockBody_t, 1); + ppcDefineParamPtr(cmd, FSCmdBlockBody, 1); FSA_RESULT fsaStatus = _FSIosErrorToFSAStatus((IOS_ERROR)iosResult); @@ -754,25 +751,25 @@ namespace coreinit void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock) { memset(fsCmdBlock, 0x00, sizeof(FSCmdBlock_t)); - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A21); fsCmdBlockBody->priority = 0x10; } - void __FSAsyncToSyncInit(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSAsyncParamsNew_t* asyncParams) + void __FSAsyncToSyncInit(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSAsyncParams* asyncParams) { if (fsClient == nullptr || fsCmdBlock == nullptr || asyncParams == nullptr) assert_dbg(); - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); coreinit::OSInitMessageQueue(&fsCmdBlockBody->syncTaskMsgQueue, fsCmdBlockBody->_syncTaskMsg, 1); asyncParams->userCallback = nullptr; asyncParams->userContext = nullptr; asyncParams->ioMsgQueue = &fsCmdBlockBody->syncTaskMsgQueue; } - void __FSPrepareCmdAsyncResult(FSClientBody_t* fsClientBody, FSCmdBlockBody_t* fsCmdBlockBody, FSAsyncResult* fsCmdBlockAsyncResult, FSAsyncParamsNew_t* fsAsyncParams) + void __FSPrepareCmdAsyncResult(FSClientBody_t* fsClientBody, FSCmdBlockBody* fsCmdBlockBody, FSAsyncResult* fsCmdBlockAsyncResult, FSAsyncParams* fsAsyncParams) { - memcpy(&fsCmdBlockAsyncResult->fsAsyncParamsNew, fsAsyncParams, sizeof(FSAsyncParamsNew_t)); + memcpy(&fsCmdBlockAsyncResult->fsAsyncParamsNew, fsAsyncParams, sizeof(FSAsyncParams)); fsCmdBlockAsyncResult->fsClient = fsClientBody->selfClient; fsCmdBlockAsyncResult->fsCmdBlock = fsCmdBlockBody->selfCmdBlock; @@ -781,7 +778,7 @@ namespace coreinit fsCmdBlockAsyncResult->msgUnion.fsMsg.commandType = _swapEndianU32(8); } - sint32 __FSPrepareCmd(FSClientBody_t* fsClientBody, FSCmdBlockBody_t* fsCmdBlockBody, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSPrepareCmd(FSClientBody_t* fsClientBody, FSCmdBlockBody* fsCmdBlockBody, uint32 errHandling, FSAsyncParams* fsAsyncParams) { if (sFSInitialized == false || sFSShutdown == true) return -0x400; @@ -813,18 +810,18 @@ namespace coreinit #define _FSCmdIntro() \ FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); \ - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); \ + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); \ sint32 fsError = __FSPrepareCmd(fsClientBody, fsCmdBlockBody, errorMask, fsAsyncParams); \ if (fsError != 0) \ return fsError; - void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody) + void _debugVerifyCommand(const char* stage, FSCmdBlockBody* fsCmdBlockBody) { if (fsCmdBlockBody->asyncResult.msgUnion.fsMsg.commandType != _swapEndianU32(8)) { cemuLog_log(LogType::Force, "Corrupted FS command detected in stage {}", stage); cemuLog_log(LogType::Force, "Printing CMD block: "); - for (uint32 i = 0; i < (sizeof(FSCmdBlockBody_t) + 31) / 32; i++) + for (uint32 i = 0; i < (sizeof(FSCmdBlockBody) + 31) / 32; i++) { uint8* p = ((uint8*)fsCmdBlockBody) + i * 32; cemuLog_log(LogType::Force, "{:04x}: {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} | {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x}", @@ -845,7 +842,7 @@ namespace coreinit // a positive result (or zero) means success. Most operations return zero in case of success. Read and write operations return the number of transferred units if (fsStatus >= 0) { - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); OSMessage msg; OSReceiveMessage(&fsCmdBlockBody->syncTaskMsgQueue, &msg, OS_MESSAGE_BLOCK); _debugVerifyCommand("handleAsyncResult", fsCmdBlockBody); @@ -906,12 +903,12 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* outFileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (outFileHandle == nullptr || path == nullptr || mode == nullptr) return -0x400; - fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = &outFileHandle->fileHandle; + fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = outFileHandle; fsError = (FSStatus)__FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, 0x660, 0, 0); if (fsError != (FSStatus)FS_RESULT::SUCCESS) return fsError; @@ -919,15 +916,15 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling) + sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); - sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, fileHandle, errHandling, &asyncParams); + sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, outFileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } - sint32 FSOpenFileExAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* outFileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSOpenFileExAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandlePtr outFileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { if (openFlag != 0) { @@ -938,7 +935,7 @@ namespace coreinit _FSCmdIntro(); if (outFileHandle == nullptr || path == nullptr || mode == nullptr) return -0x400; - fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = &outFileHandle->fileHandle; + fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = outFileHandle; FSA_RESULT prepareResult = __FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, createMode, openFlag, preallocSize); if (prepareResult != FSA_RESULT::OK) @@ -948,11 +945,11 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* fileHandle, uint32 errHandling) + sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandlePtr outFileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); - sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, fileHandle, errHandling, &asyncParams); + sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, outFileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } @@ -970,7 +967,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); @@ -984,7 +981,7 @@ namespace coreinit sint32 FSCloseFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSCloseFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); @@ -1004,7 +1001,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSFlushFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSFlushFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); @@ -1018,7 +1015,7 @@ namespace coreinit sint32 FSFlushFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSFlushFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); @@ -1060,7 +1057,7 @@ namespace coreinit SysAllocator<uint8, 128, 64> _tempFSSpace; - sint32 __FSReadFileAsyncEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool usePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSReadFileAsyncEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool usePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (size == 0 || count == 0 || dest == NULL) @@ -1091,7 +1088,7 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { cemu_assert_debug(flag == 0); // todo return __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); @@ -1099,13 +1096,13 @@ namespace coreinit sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileAsync(fsClient, fsCmdBlock, dst, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { cemu_assert_debug(flag == 0); // todo sint32 fsStatus = __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); @@ -1114,7 +1111,7 @@ namespace coreinit sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileWithPosAsync(fsClient, fsCmdBlock, dst, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1154,7 +1151,7 @@ namespace coreinit 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) + sint32 __FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool useFilePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (size == 0 || count == 0 || dest == nullptr) @@ -1185,27 +1182,27 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileAsync(fsClient, fsCmdBlock, src, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1224,7 +1221,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_SetPosFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle, filePos); @@ -1237,7 +1234,7 @@ namespace coreinit sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask) { // used by games: Mario Kart 8 - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSSetPosFileAsync(fsClient, fsCmdBlock, fileHandle, filePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1254,7 +1251,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // games using this: Darksiders Warmastered Edition _FSCmdIntro(); @@ -1268,7 +1265,7 @@ namespace coreinit sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetPosFileAsync(fsClient, fsCmdBlock, fileHandle, returnedFilePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1302,7 +1299,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(dirHandleOut && path); @@ -1316,7 +1313,7 @@ namespace coreinit sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSOpenDirAsync(fsClient, fsCmdBlock, path, dirHandleOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1333,7 +1330,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_ReadDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); @@ -1344,9 +1341,9 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadDirAsync(fsClient, fsCmdBlock, dirHandle, dirEntryOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1363,7 +1360,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_CloseDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); @@ -1376,7 +1373,7 @@ namespace coreinit sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSCloseDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1396,7 +1393,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSRewindDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSRewindDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_RewindDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); @@ -1409,7 +1406,7 @@ namespace coreinit sint32 FSRewindDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRewindDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1431,7 +1428,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_AppendFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, size, count, fileHandle, 0); @@ -1444,7 +1441,7 @@ namespace coreinit sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, size, count, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1463,7 +1460,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_TruncateFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); @@ -1476,7 +1473,7 @@ namespace coreinit sint32 FSTruncateFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSTruncateFileAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1521,7 +1518,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVERenameAsync) _FSCmdIntro(); @@ -1540,7 +1537,7 @@ namespace coreinit sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRenameAsync(fsClient, fsCmdBlock, srcPath, dstPath, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1572,7 +1569,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVERemoveAsync) _FSCmdIntro(); @@ -1591,7 +1588,7 @@ namespace coreinit sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRemoveAsync(fsClient, fsCmdBlock, filePath, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1624,7 +1621,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVEMakeDirAsync) _FSCmdIntro(); @@ -1643,7 +1640,7 @@ namespace coreinit sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSMakeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1674,7 +1671,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (path == NULL) @@ -1692,7 +1689,7 @@ namespace coreinit sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSChangeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1710,7 +1707,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: Super Mario Maker _FSCmdIntro(); @@ -1727,7 +1724,7 @@ namespace coreinit sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetCwdAsync(fsClient, fsCmdBlock, dirPathOut, dirPathMaxLen, errorMask, &asyncParams); auto r = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1758,7 +1755,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); @@ -1772,7 +1769,7 @@ namespace coreinit sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSFlushQuotaAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1808,7 +1805,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(queryString && queryResult); // query string and result must not be null @@ -1822,7 +1819,7 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSGetStatAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetStatAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_STAT, statOut, errorMask, fsAsyncParams); return fsStatus; @@ -1830,7 +1827,7 @@ namespace coreinit sint32 FSGetStat(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetStatAsync(fsClient, fsCmdBlock, path, statOut, errorMask, &asyncParams); sint32 ret = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1851,7 +1848,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(statOut); // statOut must not be null @@ -1867,13 +1864,13 @@ namespace coreinit sint32 FSGetStatFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetStatFileAsync(fsClient, fsCmdBlock, fileHandle, statOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by: Wii U system settings app, Art Academy, Unity (e.g. Snoopy's Grand Adventure), Super Smash Bros sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_FREESPACE, returnedFreeSize, errorMask, fsAsyncParams); @@ -1882,7 +1879,7 @@ namespace coreinit sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetFreeSpaceSizeAsync(fsClient, fsCmdBlock, path, returnedFreeSize, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1902,7 +1899,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by Paper Monsters Recut _FSCmdIntro(); @@ -1917,7 +1914,7 @@ namespace coreinit sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSIsEofAsync(fsClient, fsCmdBlock, fileHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1925,14 +1922,14 @@ namespace coreinit void FSSetUserData(FSCmdBlock_t* fsCmdBlock, void* userData) { - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); if (fsCmdBlockBody) fsCmdBlockBody->userData = userData; } void* FSGetUserData(FSCmdBlock_t* fsCmdBlock) { - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); void* userData = nullptr; if (fsCmdBlockBody) userData = fsCmdBlockBody->userData.GetPtr(); @@ -1956,7 +1953,7 @@ namespace coreinit FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); if (!fsClientBody) return nullptr; - FSCmdBlockBody_t* cmdBlockBody = fsClientBody->currentCmdBlockBody; + FSCmdBlockBody* cmdBlockBody = fsClientBody->currentCmdBlockBody; if (!cmdBlockBody) return nullptr; return cmdBlockBody->selfCmdBlock; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.h b/src/Cafe/OS/libs/coreinit/coreinit_FS.h index 2a57f7d..bf12e33 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.h @@ -5,33 +5,23 @@ #include "Cafe/IOSU/fsa/iosu_fsa.h" #include "coreinit_MessageQueue.h" -typedef struct -{ - uint32be fileHandle; -} FSFileHandleDepr_t; - +typedef MEMPTR<betype<FSFileHandle2>> FSFileHandlePtr; typedef MEMPTR<betype<FSDirHandle2>> FSDirHandlePtr; typedef uint32 FSAClientHandle; -typedef struct +struct FSAsyncParams { MEMPTR<void> userCallback; MEMPTR<void> userContext; MEMPTR<coreinit::OSMessageQueue> ioMsgQueue; -} FSAsyncParamsNew_t; - -static_assert(sizeof(FSAsyncParamsNew_t) == 0xC); - -typedef struct -{ - MPTR userCallback; // 0x96C - MPTR userContext; - MPTR ioMsgQueue; -} FSAsyncParams_t; // legacy struct. Replace with FSAsyncParamsNew_t +}; +static_assert(sizeof(FSAsyncParams) == 0xC); namespace coreinit { + struct FSCmdBlockBody; + struct FSCmdQueue { enum class QUEUE_FLAG : uint32 @@ -40,8 +30,8 @@ namespace coreinit CANCEL_ALL = (1 << 4), }; - /* +0x00 */ MPTR firstMPTR; - /* +0x04 */ MPTR lastMPTR; + /* +0x00 */ MEMPTR<FSCmdBlockBody> first; + /* +0x04 */ MEMPTR<FSCmdBlockBody> last; /* +0x08 */ OSFastMutex fastMutex; /* +0x34 */ MPTR dequeueHandlerFuncMPTR; /* +0x38 */ uint32be numCommandsInFlight; @@ -108,7 +98,7 @@ namespace coreinit uint8 ukn1460[0x10]; uint8 ukn1470[0x10]; FSCmdQueue fsCmdQueue; - /* +0x14C4 */ MEMPTR<struct FSCmdBlockBody_t> currentCmdBlockBody; // set to currently active cmd + /* +0x14C4 */ MEMPTR<struct FSCmdBlockBody> currentCmdBlockBody; // set to currently active cmd uint32 ukn14C8; uint32 ukn14CC; uint8 ukn14D0[0x10]; @@ -128,7 +118,7 @@ namespace coreinit struct FSAsyncResult { - /* +0x00 */ FSAsyncParamsNew_t fsAsyncParamsNew; + /* +0x00 */ FSAsyncParams fsAsyncParamsNew; // fs message storage struct FSMessage @@ -159,7 +149,7 @@ namespace coreinit uint8 ukn0[0x14]; struct { - MEMPTR<uint32be> handlePtr; + MEMPTR<betype<FSResHandle>> handlePtr; } cmdOpenFile; struct { @@ -205,7 +195,7 @@ namespace coreinit static_assert(sizeof(FSCmdBlockReturnValues_t) == 0x14); - struct FSCmdBlockBody_t + struct FSCmdBlockBody { iosu::fsa::FSAShimBuffer fsaShimBuffer; /* +0x0938 */ MEMPTR<FSClientBody_t> fsClientBody; @@ -213,9 +203,8 @@ namespace coreinit /* +0x0940 */ uint32be cancelState; // bitmask. Bit 0 -> If set command has been canceled FSCmdBlockReturnValues_t returnValues; // link for cmd queue - MPTR nextMPTR; // points towards FSCmdQueue->first - MPTR previousMPTR; // points towards FSCmdQueue->last - + MEMPTR<FSCmdBlockBody> next; + MEMPTR<FSCmdBlockBody> previous; /* +0x960 */ betype<FSA_RESULT> lastFSAStatus; uint32 ukn0964; /* +0x0968 */ uint8 errHandling; // return error flag mask @@ -235,7 +224,6 @@ namespace coreinit uint32 ukn9FC; }; - static_assert(sizeof(FSAsyncParams_t) == 0xC); static_assert(sizeof(FSCmdBlock_t) == 0xA80); #define FSA_CMD_FLAG_SET_POS (1 << 0) @@ -251,7 +239,7 @@ namespace coreinit }; // internal interface - sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams); + sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParams* fsAsyncParams); // coreinit exports FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling); @@ -260,52 +248,52 @@ namespace coreinit void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock); - sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling, FSAsyncParamsNew_t* asyncParams); - sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling); + sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling, FSAsyncParams* asyncParams); + sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling); - sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask); - sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask); - 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 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask); - sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask); - sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParams* fsAsyncParams); 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 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask); - sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParams* 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 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* 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); + sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask); - sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask); - sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask); - sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); - sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); - sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); + sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); + sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask); - sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp index dc82f77..83658f3 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp @@ -128,7 +128,7 @@ namespace coreinit { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); MEMPTR<void> bucket; uint32be bucketSize; @@ -257,7 +257,7 @@ namespace coreinit { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); MEMPTR<void> bucket; uint32be bucketSize; @@ -593,16 +593,16 @@ namespace coreinit { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); mem1Heap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); - OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); memFGHeap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); } MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(2, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(2, &memBound, &memBoundSize); mem2Heap = MEMDefaultHeap_Init(memBound.GetPtr(), (uint32)memBoundSize); // set DynLoad allocators diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp index cff4ee2..80ec212 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp @@ -131,7 +131,7 @@ namespace coreinit // no-op } - void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput) + void OSGetMemBound(sint32 memType, MEMPTR<void>* offsetOutput, uint32be* sizeOutput) { MPTR memAddr = MPTR_NULL; uint32 memSize = 0; @@ -195,9 +195,9 @@ namespace coreinit cemu_assert_debug(false); } if (offsetOutput) - *offsetOutput = _swapEndianU32(memAddr); + *offsetOutput = memAddr; if (sizeOutput) - *sizeOutput = _swapEndianU32(memSize); + *sizeOutput = memSize; } void InitializeMemory() diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h index 0a212f6..62c9f13 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h @@ -4,7 +4,7 @@ namespace coreinit { void InitializeMemory(); - void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput); + void OSGetMemBound(sint32 memType, MEMPTR<void>* offsetOutput, uint32be* sizeOutput); void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC); void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size); diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 0268c7d..7a8eacb 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -1401,12 +1401,10 @@ void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) } case CURLINFO_CONTENT_TYPE: { - //cemuLog_logDebug(LogType::Force, "CURLINFO_CONTENT_TYPE not supported"); - //*(uint32*)parameter.GetPtr() = MPTR_NULL; char* contentType = nullptr; result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &contentType); _updateGuestString(curl.GetPtr(), curl->info_contentType, contentType); - *(uint32*)parameter.GetPtr() = curl->info_contentType.GetMPTRBE(); + *(MEMPTR<char>*)parameter.GetPtr() = curl->info_contentType; break; } case CURLINFO_REDIRECT_URL: @@ -1414,7 +1412,7 @@ void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) char* redirectUrl = nullptr; result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &redirectUrl); _updateGuestString(curl.GetPtr(), curl->info_redirectUrl, redirectUrl); - *(uint32*)parameter.GetPtr() = curl->info_redirectUrl.GetMPTRBE(); + *(MEMPTR<char>*)parameter.GetPtr() = curl->info_redirectUrl; break; } default: diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 05e4943..518e419 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -320,7 +320,7 @@ namespace save return SAVE_STATUS_OK; } - SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -331,7 +331,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -340,7 +340,7 @@ namespace save return result; } - SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -351,7 +351,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -361,7 +361,7 @@ namespace save return result; } - SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -372,7 +372,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else @@ -383,7 +383,7 @@ namespace save return result; } - SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -394,7 +394,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, hFile, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -404,7 +404,7 @@ namespace save return result; } - SAVEStatus SAVEOpenFileOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + SAVEStatus SAVEOpenFileOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { if (strcmp(mode, "r") != 0) return (SAVEStatus)(FS_RESULT::PERMISSION_ERROR); @@ -418,7 +418,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) - result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, hFile, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -428,26 +428,10 @@ namespace save return result; } - void export_SAVEOpenFileOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 9); - - const SAVEStatus result = SAVEOpenFileOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParamsNew_t asyncParams; + FSAsyncParams asyncParams; asyncParams.ioMsgQueue = nullptr; asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); @@ -456,7 +440,7 @@ namespace save param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetPointer(); - SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, &asyncParams); + SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { coreinit_suspendThread(currentThread, 1000); @@ -467,113 +451,31 @@ namespace save return status; } - void export_SAVEOpenFileOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - - const SAVEStatus result = SAVEOpenFileOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, asyncParams); + return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncParams); } - void export_SAVEOpenFileOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) + SAVEStatus SAVEOpenFileOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(mode, const char, 5); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 8); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEOpenFileOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) - { - //peterBreak(); - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, hFile, errHandling); + return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - void export_SAVEOpenFileOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(mode, const char, 5); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) - { - //peterBreak(); - - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, asyncParams); - } - - void export_SAVEOpenFileOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 9); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + SAVEStatus SAVEOpenFileOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, hFile, errHandling); + return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncParams); } - void export_SAVEOpenFileOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + SAVEStatus SAVEOpenFileOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -583,9 +485,8 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - // usually a pointer with '\0' instead of nullptr, but it's basically the same if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) - result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -595,7 +496,7 @@ namespace save return result; } - SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -606,7 +507,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParamsNew_t*)asyncParams); // FSGetStatAsync(...) + result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -616,7 +517,7 @@ namespace save return result; } - SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -627,7 +528,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == (FSStatus)FS_RESULT::SUCCESS) - result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParamsNew_t*)asyncParams); // FSGetStatAsync(...) + result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -637,25 +538,25 @@ namespace save return result; } - SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEGetStatOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEGetStatOtherDemoApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherDemoApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); @@ -682,14 +583,14 @@ namespace save SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -722,7 +623,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVEGetFreeSpaceSizeAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSizeAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); @@ -743,7 +644,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVERemoveAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -752,14 +653,14 @@ namespace save SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -784,7 +685,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -798,7 +699,7 @@ namespace save { char fullNewPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParams*)asyncParams); } } else @@ -817,7 +718,7 @@ namespace save ppcDefineParamMEMPTR(oldPath, const char, 3); ppcDefineParamMEMPTR(newPath, const char, 4); ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); const SAVEStatus result = SAVERenameAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVERenameAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, result); @@ -855,7 +756,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); const SAVEStatus result = SAVEOpenDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEOpenDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), @@ -866,14 +767,14 @@ namespace save SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -901,7 +802,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -911,7 +812,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -929,7 +830,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 4); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); const SAVEStatus result = SAVEOpenDirOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplicationAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), @@ -940,14 +841,14 @@ namespace save SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -976,7 +877,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); @@ -991,7 +892,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 4); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); const SAVEStatus result = SAVEOpenDirOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1017,7 +918,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); @@ -1033,7 +934,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 5); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1067,7 +968,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVEMakeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEMakeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); @@ -1077,14 +978,14 @@ namespace save SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1110,26 +1011,10 @@ namespace save osLib_returnFromFunction(hCPU, result); } - void export_SAVEOpenFileAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(mode, const char, 4); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 7); - - const SAVEStatus result = SAVEOpenFileAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenFileAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParamsNew_t asyncParams; + FSAsyncParams asyncParams; asyncParams.ioMsgQueue = nullptr; asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); @@ -1138,7 +1023,7 @@ namespace save param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetPointer(); - SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, hFile, errHandling, &asyncParams); + SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { coreinit_suspendThread(currentThread, 1000); @@ -1149,21 +1034,6 @@ namespace save return status; } - void export_SAVEOpenFile(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(mode, const char, 4); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenFile(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEOpenFile(0x{:08x}, 0x{:08x}, {:x}, {}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - void export_SAVEInitSaveDir(PPCInterpreter_t* hCPU) { ppcDefineParamU8(accountSlot, 0); @@ -1180,7 +1050,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamMEMPTR(stat, FSStat_t, 4); ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); const SAVEStatus result = SAVEGetStatAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEGetStatAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); @@ -1190,14 +1060,14 @@ namespace save SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1233,7 +1103,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 5); ppcDefineParamMEMPTR(stat, FSStat_t, 6); ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEGetStatOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1242,14 +1112,14 @@ namespace save SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1286,7 +1156,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 4); ppcDefineParamMEMPTR(stat, FSStat_t, 5); ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEGetStatOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1294,8 +1164,6 @@ namespace save SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { - //peterBreak(); - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } @@ -1326,7 +1194,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 5); ppcDefineParamMEMPTR(stat, FSStat_t, 6); ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1397,7 +1265,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1407,7 +1275,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSChangeDirAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSChangeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -1423,7 +1291,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVEChangeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEChangeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); osLib_returnFromFunction(hCPU, result); @@ -1432,14 +1300,14 @@ namespace save SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1464,7 +1332,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1475,7 +1343,7 @@ namespace save char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) { - result = coreinit::FSFlushQuotaAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSFlushQuotaAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); // if(OSGetUPID != 0xF) UpdateSaveTimeStamp(persistentId); } @@ -1493,7 +1361,7 @@ namespace save ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); ppcDefineParamU8(accountSlot, 2); ppcDefineParamU32(errHandling, 3); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 4); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 4); const SAVEStatus result = SAVEFlushQuotaAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEFlushQuotaAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); osLib_returnFromFunction(hCPU, result); @@ -1502,14 +1370,14 @@ namespace save SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1553,10 +1421,14 @@ namespace save osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplication", export_SAVEGetStatOtherNormalApplication); osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariation", export_SAVEGetStatOtherNormalApplicationVariation); - osLib_addFunction("nn_save", "SAVEOpenFile", export_SAVEOpenFile); - osLib_addFunction("nn_save", "SAVEOpenFileOtherApplication", export_SAVEOpenFileOtherApplication); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplication", export_SAVEOpenFileOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationVariation", export_SAVEOpenFileOtherNormalApplicationVariation); + cafeExportRegister("nn_save", SAVEOpenFile, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariationAsync, LogType::Save); osLib_addFunction("nn_save", "SAVEOpenDir", export_SAVEOpenDir); osLib_addFunction("nn_save", "SAVEOpenDirOtherApplication", export_SAVEOpenDirOtherApplication); @@ -1578,11 +1450,6 @@ namespace save osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationAsync", export_SAVEGetStatOtherNormalApplicationAsync); osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariationAsync", export_SAVEGetStatOtherNormalApplicationVariationAsync); - osLib_addFunction("nn_save", "SAVEOpenFileAsync", export_SAVEOpenFileAsync); - osLib_addFunction("nn_save", "SAVEOpenFileOtherApplicationAsync", export_SAVEOpenFileOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationAsync", export_SAVEOpenFileOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationVariationAsync", export_SAVEOpenFileOtherNormalApplicationVariationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirAsync", export_SAVEOpenDirAsync); osLib_addFunction("nn_save", "SAVEOpenDirOtherApplicationAsync", export_SAVEOpenDirOtherApplicationAsync); osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync); diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index 91d15af..5560568 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -511,7 +511,7 @@ namespace proc_ui { MEMPTR<void> fgBase; uint32be fgFreeSize; - OSGetForegroundBucketFreeArea((MPTR*)&fgBase, (MPTR*)&fgFreeSize); + OSGetForegroundBucketFreeArea(&fgBase, &fgFreeSize); if(fgFreeSize < size) cemuLog_log(LogType::Force, "ProcUISetBucketStorage: Buffer size too small"); s_bucketStorageBasePtr = memBase; @@ -521,7 +521,7 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); if(memBoundSize < size) cemuLog_log(LogType::Force, "ProcUISetMEM1Storage: Buffer size too small"); s_mem1StorageBasePtr = memBase; @@ -751,14 +751,14 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); OSBlockMove(s_mem1StorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); } if (s_bucketStorageBasePtr) { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); OSBlockMove(s_bucketStorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); } } @@ -769,7 +769,7 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); OSBlockMove(memBound.GetPtr(), s_mem1StorageBasePtr, memBoundSize, true); GX2::GX2Invalidate(0x40, s_mem1StorageBasePtr.GetMPTR(), memBoundSize); } @@ -777,7 +777,7 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); OSBlockMove(memBound.GetPtr(), s_bucketStorageBasePtr, memBoundSize, true); GX2::GX2Invalidate(0x40, memBound.GetMPTR(), memBoundSize); } diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index de787cc..b2362d0 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -136,16 +136,10 @@ public: C* GetPtr() const { return (C*)(GetPtr()); } constexpr uint32 GetMPTR() const { return m_value.value(); } - constexpr uint32 GetRawValue() const { return m_value.bevalue(); } // accesses value using host-endianness - constexpr const uint32be& GetBEValue() const { return m_value; } constexpr bool IsNull() const { return m_value == 0; } - constexpr uint32 GetMPTRBE() const { return m_value.bevalue(); } - - uint32be* GetBEPtr() { return &m_value; } - private: uint32be m_value; }; diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h index a69b7aa..1dc52d5 100644 --- a/src/Common/StackAllocator.h +++ b/src/Common/StackAllocator.h @@ -28,7 +28,6 @@ public: T* GetPointer() const { return m_ptr; } uint32 GetMPTR() const { return MEMPTR<T>(m_ptr).GetMPTR(); } - uint32 GetMPTRBE() const { return MEMPTR<T>(m_ptr).GetMPTRBE(); } T* operator&() { return GetPointer(); } explicit operator T*() const { return GetPointer(); } From c11d83e9d8980bdc8978001583ddaa5ca0b6529b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 3 May 2024 02:41:05 +0200 Subject: [PATCH 081/130] coreinit: Implement MCP_GetTitleId --- src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp index 14d7a64..330663a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -415,6 +415,12 @@ namespace coreinit return 0; } + uint32 MCP_GetTitleId(uint32 mcpHandle, uint64be* outTitleId) + { + *outTitleId = CafeSystem::GetForegroundTitleId(); + return 0; + } + void InitializeMCP() { osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open); @@ -442,6 +448,8 @@ namespace coreinit cafeExportRegister("coreinit", MCP_RightCheckLaunchable, LogType::Placeholder); cafeExportRegister("coreinit", MCP_GetEcoSettings, LogType::Placeholder); + + cafeExportRegister("coreinit", MCP_GetTitleId, LogType::Placeholder); } } From 1b5c885621c8a2e6057cc6c6c0bd557f6da5a327 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 3 May 2024 02:41:39 +0200 Subject: [PATCH 082/130] nn_acp: Implement ACPGetTitleMetaXml --- src/Cafe/OS/libs/nn_acp/nn_acp.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp index 61640ae..37ea471 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp @@ -289,6 +289,18 @@ namespace acp osLib_returnFromFunction(hCPU, acpRequest->returnCode); } + uint32 ACPGetTitleMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml) + { + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; + acpRequest->ptr = acpMetaXml; + acpRequest->titleId = titleId; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + return acpRequest->returnCode; + } + void export_ACPIsOverAgeEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(age, 0); @@ -341,6 +353,7 @@ namespace acp osLib_addFunction("nn_acp", "ACPGetTitleMetaDirByDevice", export_ACPGetTitleMetaDirByDevice); osLib_addFunction("nn_acp", "ACPGetTitleMetaXmlByDevice", export_ACPGetTitleMetaXmlByDevice); + cafeExportRegister("nn_acp", ACPGetTitleMetaXml, LogType::Placeholder); cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder); From 041f29a914b0e0ef88b4dad863cf71a6fdcca84f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 3 May 2024 02:43:51 +0200 Subject: [PATCH 083/130] nn_act: Implement GetTimeZoneId placeholder --- src/Cafe/OS/libs/nn_act/nn_act.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 2a9f61b..af53edd 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -5,6 +5,7 @@ #include "nn_act.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/CafeSystem.h" +#include "Common/CafeString.h" sint32 numAccounts = 1; @@ -140,6 +141,14 @@ namespace act return 0; } + nnResult GetTimeZoneId(CafeString<65>* outTimezoneId) + { + // return a placeholder timezone id for now + // in the future we should emulated this correctly and read the timezone from the account via IOSU + outTimezoneId->assign("Europe/London"); + return 0; + } + sint32 g_initializeCount = 0; // inc in Initialize and dec in Finalize uint32 Initialize() { @@ -162,7 +171,6 @@ namespace act NN_ERROR_CODE errCode = NNResultToErrorCode(*nnResult, NN_RESULT_MODULE_NN_ACT); return errCode; } - } } @@ -691,6 +699,8 @@ void nnAct_load() osLib_addFunction("nn_act", "GetPersistentIdEx__Q2_2nn3actFUc", nnActExport_GetPersistentIdEx); // country osLib_addFunction("nn_act", "GetCountry__Q2_2nn3actFPc", nnActExport_GetCountry); + // timezone + cafeExportRegisterFunc(nn::act::GetTimeZoneId, "nn_act", "GetTimeZoneId__Q2_2nn3actFPc", LogType::Placeholder); // parental osLib_addFunction("nn_act", "EnableParentalControlCheck__Q2_2nn3actFb", nnActExport_EnableParentalControlCheck); From a16c37f0c5b2435a829fc5348c66297d9c762347 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 4 May 2024 07:05:59 +0200 Subject: [PATCH 084/130] coreinit: Rework thread creation New implementation is much closer to console behavior. For example we didn't align the stack which would cause crashes in the Miiverse applet --- src/Cafe/HW/Latte/Core/LatteThread.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit.cpp | 10 +- src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp | 4 +- src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 315 ++++++++++++++---- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 69 ++-- src/Cafe/OS/libs/nsysnet/nsysnet.cpp | 4 +- src/Cafe/OS/libs/snd_core/ax_ist.cpp | 2 +- .../ExceptionHandler/ExceptionHandler.cpp | 2 +- .../DebugPPCThreadsWindow.cpp | 4 +- 10 files changed, 297 insertions(+), 117 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index a23bd5b..8874ecf 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -187,7 +187,7 @@ int Latte_ThreadEntry() rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0) { LatteGPUState.allowFramebufferSizeOptimization = false; - cemuLog_log(LogType::Force, "Graphic pack {} prevents rendertarget size optimization.", pack->GetName()); + cemuLog_log(LogType::Force, "Graphic pack \"{}\" prevents rendertarget size optimization. This warning can be ignored and is intended for graphic pack developers", pack->GetName()); break; } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index e18d0e8..49d232f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -35,12 +35,12 @@ #include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" -CoreinitSharedData* gCoreinitData = NULL; +CoreinitSharedData* gCoreinitData = nullptr; sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) { - uint32 stackMinAddr = _swapEndianU32(thread->stackEnd); - uint32 stackMaxAddr = _swapEndianU32(thread->stackBase); + uint32 stackMinAddr = thread->stackEnd.GetMPTR(); + uint32 stackMaxAddr = thread->stackBase.GetMPTR(); sint32 score = 0; uint32 currentStackPtr = sp; @@ -95,8 +95,8 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp) // print stack trace uint32 currentStackPtr = highestScoreSP; - uint32 stackMinAddr = _swapEndianU32(thread->stackEnd); - uint32 stackMaxAddr = _swapEndianU32(thread->stackBase); + uint32 stackMinAddr = thread->stackEnd.GetMPTR(); + uint32 stackMaxAddr = thread->stackBase.GetMPTR(); for (sint32 i = 0; i < 20; i++) { uint32 nextStackPtr = memory_readU32(currentStackPtr); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp index 5699e3e..e2864fb 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp @@ -22,7 +22,7 @@ namespace coreinit MPTR _iob_lock[GHS_FOPEN_MAX]; uint16be __gh_FOPEN_MAX; MEMPTR<void> ghs_environ; - uint32 ghs_Errno; // exposed by __gh_errno_ptr() or via 'errno' data export + uint32 ghs_Errno; // exposed as 'errno' data export }; SysAllocator<GHSAccessibleData> g_ghs_data; @@ -159,7 +159,7 @@ namespace coreinit void* __gh_errno_ptr() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); - return ¤tThread->context.error; + return ¤tThread->context.ghs_errno; } void* __get_eh_store_globals() diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp index be3cb30..12d83af 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp @@ -204,7 +204,7 @@ namespace coreinit // and a message queue large enough to hold the maximum number of commands (IPC_NUM_RESOURCE_BUFFERS) OSInitMessageQueue(gIPCThreadMsgQueue.GetPtr() + coreIndex, _gIPCThreadSemaphoreStorage.GetPtr() + coreIndex * IPC_NUM_RESOURCE_BUFFERS, IPC_NUM_RESOURCE_BUFFERS); OSThread_t* ipcThread = gIPCThread.GetPtr() + coreIndex; - OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + __OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); sprintf((char*)_gIPCThreadNameStorage.GetPtr()+coreIndex*0x18, "{SYS IPC Core %d}", coreIndex); OSSetThreadName(ipcThread, (char*)_gIPCThreadNameStorage.GetPtr() + coreIndex * 0x18); OSResumeThread(ipcThread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 654e57a..533360a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -215,14 +215,171 @@ namespace coreinit hCPU->spr.LR = lr; hCPU->gpr[3] = r3; hCPU->gpr[4] = r4; - hCPU->instructionPointer = _swapEndianU32(currentThread->entrypoint); + hCPU->instructionPointer = currentThread->entrypoint.GetMPTR(); } void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU); - void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType) + void __OSInitContext(OSContext_t* ctx, MEMPTR<void> initialIP, MEMPTR<void> initialStackPointer) + { + ctx->SetContextMagic(); + ctx->gpr[0] = 0; // r0 is left uninitialized on console? + for(auto& it : ctx->gpr) + it = 0; + ctx->gpr[1] = _swapEndianU32(initialStackPointer.GetMPTR()); + ctx->gpr[2] = _swapEndianU32(RPLLoader_GetSDA2Base()); + ctx->gpr[13] = _swapEndianU32(RPLLoader_GetSDA1Base()); + ctx->srr0 = initialIP.GetMPTR(); + ctx->cr = 0; + ctx->ukn0A8 = 0; + ctx->ukn0AC = 0; + ctx->gqr[0] = 0; + ctx->gqr[1] = 0; + ctx->gqr[2] = 0; + ctx->gqr[3] = 0; + ctx->gqr[4] = 0; + ctx->gqr[5] = 0; + ctx->gqr[6] = 0; + ctx->gqr[7] = 0; + ctx->dsi_dar = 0; + ctx->srr1 = 0x9032; + ctx->xer = 0; + ctx->dsi_dsisr = 0; + ctx->upir = 0; + ctx->boostCount = 0; + ctx->state = 0; + for(auto& it : ctx->coretime) + it = 0; + ctx->starttime = 0; + ctx->ghs_errno = 0; + ctx->upmc1 = 0; + ctx->upmc2 = 0; + ctx->upmc3 = 0; + ctx->upmc4 = 0; + ctx->ummcr0 = 0; + ctx->ummcr1 = 0; + } + + void __OSThreadInit(OSThread_t* thread, MEMPTR<void> entrypoint, uint32 argInt, MEMPTR<void> argPtr, MEMPTR<void> stackTop, uint32 stackSize, sint32 priority, uint32 upirCoreIndex, OSThread_t::THREAD_TYPE threadType) + { + thread->effectivePriority = priority; + thread->type = threadType; + thread->basePriority = priority; + thread->SetThreadMagic(); + thread->id = 0x8000; + thread->waitAlarm = nullptr; + thread->entrypoint = entrypoint; + thread->quantumTicks = 0; + if(entrypoint) + { + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + thread->suspendCounter = 1; + } + else + { + thread->state = OSThread_t::THREAD_STATE::STATE_NONE; + thread->suspendCounter = 0; + } + thread->exitValue = (uint32)-1; + thread->requestFlags = OSThread_t::REQUEST_FLAG_BIT::REQUEST_FLAG_NONE; + thread->pendingSuspend = 0; + thread->suspendResult = 0xFFFFFFFF; + thread->coretimeSumQuantumStart = 0; + thread->deallocatorFunc = nullptr; + thread->cleanupCallback = nullptr; + thread->waitingForFastMutex = nullptr; + thread->stateFlags = 0; + thread->waitingForMutex = nullptr; + memset(&thread->crt, 0, sizeof(thread->crt)); + static_assert(sizeof(thread->crt) == 0x1D8); + thread->tlsBlocksMPTR = 0; + thread->numAllocatedTLSBlocks = 0; + thread->tlsStatus = 0; + OSInitThreadQueueEx(&thread->joinQueue, thread); + OSInitThreadQueueEx(&thread->suspendQueue, thread); + thread->mutexQueue.ukn08 = thread; + thread->mutexQueue.ukn0C = 0; + thread->mutexQueue.tail = nullptr; + thread->mutexQueue.head = nullptr; + thread->ownedFastMutex.next = nullptr; + thread->ownedFastMutex.prev = nullptr; + thread->contendedFastMutex.next = nullptr; + thread->contendedFastMutex.prev = nullptr; + + MEMPTR<void> alignedStackTop{MEMPTR<void>(stackTop).GetMPTR() & 0xFFFFFFF8}; + MEMPTR<uint32be> alignedStackTop32{alignedStackTop}; + alignedStackTop32[-1] = 0; + alignedStackTop32[-2] = 0; + + __OSInitContext(&thread->context, MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(threadEntry)), (void*)(alignedStackTop32.GetPtr() - 2)); + thread->stackBase = stackTop; // without alignment + thread->stackEnd = ((uint8*)stackTop.GetPtr() - stackSize); + thread->context.upir = upirCoreIndex; + thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); + thread->context.gpr[3] = _swapEndianU32(argInt); + thread->context.gpr[4] = _swapEndianU32(argPtr.GetMPTR()); + + *(uint32be*)((uint8*)stackTop.GetPtr() - stackSize) = 0xDEADBABE; + thread->alarmRelatedUkn = 0; + for(auto& it : thread->specificArray) + it = nullptr; + thread->context.fpscr.fpscr = 4; + for(sint32 i=0; i<32; i++) + { + thread->context.fp_ps0[i] = 0.0; + thread->context.fp_ps1[i] = 0.0; + } + thread->context.gqr[2] = 0x40004; + thread->context.gqr[3] = 0x50005; + thread->context.gqr[4] = 0x60006; + thread->context.gqr[5] = 0x70007; + + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + thread->context.coretime[i] = 0; + + // currentRunQueue and waitQueueLink is not initialized by COS and instead overwritten without validation + // since we already have integrity checks in other functions, lets initialize it here + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + thread->currentRunQueue[i] = nullptr; + thread->waitQueueLink.prev = nullptr; + thread->waitQueueLink.next = nullptr; + + thread->wakeTimeRelatedUkn2 = 0; + thread->wakeUpCount = 0; + thread->wakeUpTime = 0; + thread->wakeTimeRelatedUkn1 = 0x7FFFFFFFFFFFFFFF; + thread->quantumTicks = 0; + thread->coretimeSumQuantumStart = 0; + thread->totalCycles = 0; + + for(auto& it : thread->padding68C) + it = 0; + } + + void SetThreadAffinityToCore(OSThread_t* thread, uint32 coreIndex) + { + cemu_assert_debug(coreIndex < 3); + thread->attr &= ~(OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE0 | OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE1 | OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE2 | OSThread_t::ATTR_BIT::ATTR_UKN_010); + thread->context.affinity &= 0xFFFFFFF8; + if (coreIndex == 0) + { + thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE0; + thread->context.affinity |= (1<<0); + } + else if (coreIndex == 1) + { + thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE1; + thread->context.affinity |= (1<<1); + } + else // if (coreIndex == 2) + { + thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE2; + thread->context.affinity |= (1<<2); + } + } + + void __OSCreateThreadOnActiveThreadWorkaround(OSThread_t* thread) { - cemu_assert_debug(thread != nullptr); // make thread struct mandatory. Caller can always use SysAllocator __OSLockScheduler(); bool isThreadStillActive = __OSIsThreadActive(thread); if (isThreadStillActive) @@ -248,84 +405,97 @@ namespace coreinit } cemu_assert_debug(__OSIsThreadActive(thread) == false); __OSUnlockScheduler(); - memset(thread, 0x00, sizeof(OSThread_t)); - // init signatures - 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); - __OSSetThreadBasePriority(thread, 0); - __OSUpdateThreadEffectivePriority(thread); - // untested, but seems to work (Batman Arkham City uses these values to calculate the stack size for duplicated threads) - thread->stackBase = _swapEndianU32(stackLowerBaseAddr + stackSize); // these fields are quite important and lots of games rely on them being accurate (Examples: Darksiders 2, SMW3D, Batman Arkham City) - thread->stackEnd = _swapEndianU32(stackLowerBaseAddr); - // init stackpointer - thread->context.gpr[GPR_SP] = _swapEndianU32(stackLowerBaseAddr + stackSize - 0x20); // how many free bytes should there be at the beginning of the stack? - // init misc stuff - thread->attr = affinityMask; - thread->context.setAffinity(affinityMask); - 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; - thread->context.gqr[1] = 0x00000000; - thread->context.gqr[2] = 0x00040004; - thread->context.gqr[3] = 0x00050005; - thread->context.gqr[4] = 0x00060006; - thread->context.gqr[5] = 0x00070007; - thread->context.gqr[6] = 0x00000000; - thread->context.gqr[7] = 0x00000000; - // init r2 (SDA2) and r3 (SDA) - thread->context.gpr[2] = _swapEndianU32(RPLLoader_GetSDA2Base()); - thread->context.gpr[13] = _swapEndianU32(RPLLoader_GetSDA1Base()); - // GHS related thread init? + } - __OSLockScheduler(); - // if entrypoint is non-zero then put the thread on the active list and suspend it - if (entryPoint != MPTR_NULL) + bool __OSCreateThreadInternal2(OSThread_t* thread, MEMPTR<void> entrypoint, uint32 argInt, MEMPTR<void> argPtr, MEMPTR<void> stackBase, uint32 stackSize, sint32 priority, uint32 attrBits, OSThread_t::THREAD_TYPE threadType) + { + __OSCreateThreadOnActiveThreadWorkaround(thread); + OSThread_t* currentThread = OSGetCurrentThread(); + if (priority < 0 || priority >= 32) { - thread->suspendCounter = 1; - __OSActivateThread(thread); - thread->state = OSThread_t::THREAD_STATE::STATE_READY; + cemuLog_log(LogType::APIErrors, "OSCreateThreadInternal: Thread priority must be in range 0-31"); + return false; + } + if (threadType == OSThread_t::THREAD_TYPE::TYPE_IO) + { + priority = priority + 0x20; + } + else if (threadType == OSThread_t::THREAD_TYPE::TYPE_APP) + { + priority = priority + 0x40; + } + if(attrBits >= 0x20 || stackBase == nullptr || stackSize == 0) + { + cemuLog_logDebug(LogType::APIErrors, "OSCreateThreadInternal: Invalid attributes, stack base or size"); + return false; + } + uint32 im = OSDisableInterrupts(); + __OSLockScheduler(thread); + + uint32 coreIndex = PPCInterpreter_getCurrentInstance() ? OSGetCoreId() : 1; + __OSThreadInit(thread, entrypoint, argInt, argPtr, stackBase, stackSize, priority, coreIndex, threadType); + thread->threadName = nullptr; + thread->context.affinity = attrBits & 7; + thread->attr = attrBits; + if ((attrBits & 7) == 0) // if no explicit affinity is given, use the current core + SetThreadAffinityToCore(thread, OSGetCoreId()); + if(currentThread) + { + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + { + thread->dsiCallback[i] = currentThread->dsiCallback[i]; + thread->isiCallback[i] = currentThread->isiCallback[i]; + thread->programCallback[i] = currentThread->programCallback[i]; + thread->perfMonCallback[i] = currentThread->perfMonCallback[i]; + thread->alignmentExceptionCallback[i] = currentThread->alignmentExceptionCallback[i]; + } + thread->context.srr1 = thread->context.srr1 | (currentThread->context.srr1 & 0x900); + thread->context.fpscr.fpscr = thread->context.fpscr.fpscr | (currentThread->context.fpscr.fpscr & 0xF8); } else - thread->suspendCounter = 0; - __OSUnlockScheduler(); + { + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + { + thread->dsiCallback[i] = 0; + thread->isiCallback[i] = 0; + thread->programCallback[i] = 0; + thread->perfMonCallback[i] = 0; + thread->alignmentExceptionCallback[i] = nullptr; + } + } + if (entrypoint) + { + thread->id = 0x8000; + __OSActivateThread(thread); // also handles adding the thread to g_activeThreadQueue + } + __OSUnlockScheduler(thread); + OSRestoreInterrupts(im); + // recompile entry point function + if (entrypoint) + PPCRecompiler_recompileIfUnvisited(entrypoint.GetMPTR()); + return true; } bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { - OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop) - stackSize, stackSize, attr, threadType); - thread->context.gpr[3] = _swapEndianU32(numParam); // num arguments - thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); // arguments pointer - __OSSetThreadBasePriority(thread, priority); - __OSUpdateThreadEffectivePriority(thread); - // set affinity - uint8 affinityMask = 0; - affinityMask = attr & 0x7; - // if no core is selected -> set current one - if (affinityMask == 0) - affinityMask |= (1 << PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance())); - // set attr - // todo: Support for other attr bits - thread->attr = (affinityMask & 0xFF) | (attr & OSThread_t::ATTR_BIT::ATTR_DETACHED); - thread->context.setAffinity(affinityMask); - // recompile entry point function - if (entryPoint != MPTR_NULL) - PPCRecompiler_recompileIfUnvisited(entryPoint); - return true; + if(threadType != OSThread_t::THREAD_TYPE::TYPE_APP && threadType != OSThread_t::THREAD_TYPE::TYPE_IO) + { + cemuLog_logDebug(LogType::APIErrors, "OSCreateThreadType: Invalid thread type"); + cemu_assert_suspicious(); + return false; + } + return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr) { - return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); } - // alias to OSCreateThreadType, similar to OSCreateThread, but with an additional parameter for the thread type + // similar to OSCreateThreadType, but can be used to create any type of thread bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { - return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); + return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam) @@ -352,7 +522,7 @@ namespace coreinit // set thread state // todo - this should fully reinitialize the thread? - thread->entrypoint = _swapEndianU32(funcAddress); + thread->entrypoint = funcAddress; thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry); thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); thread->context.gpr[3] = _swapEndianU32(numParam); @@ -378,10 +548,10 @@ namespace coreinit OSThread_t* currentThread = coreinit::OSGetCurrentThread(); // thread cleanup callback - if (!currentThread->cleanupCallback2.IsNull()) + if (currentThread->cleanupCallback) { currentThread->stateFlags = _swapEndianU32(_swapEndianU32(currentThread->stateFlags) | 0x00000001); - PPCCoreCallback(currentThread->cleanupCallback2.GetMPTR(), currentThread, _swapEndianU32(currentThread->stackEnd)); + PPCCoreCallback(currentThread->cleanupCallback.GetMPTR(), currentThread, currentThread->stackEnd); } // cpp exception cleanup if (gCoreinitData->__cpp_exception_cleanup_ptr != 0 && currentThread->crt.eh_globals != nullptr) @@ -602,7 +772,10 @@ namespace coreinit sint32 previousSuspendCount = thread->suspendCounter; cemu_assert_debug(previousSuspendCount >= 0); if (previousSuspendCount == 0) + { + cemuLog_log(LogType::APIErrors, "OSResumeThread: Resuming thread 0x{:08x} which isn't suspended", MEMPTR<OSThread_t>(thread).GetMPTR()); return 0; + } thread->suspendCounter = previousSuspendCount - resumeCount; if (thread->suspendCounter < 0) thread->suspendCounter = 0; @@ -732,8 +905,8 @@ namespace coreinit void* OSSetThreadCleanupCallback(OSThread_t* thread, void* cleanupCallback) { __OSLockScheduler(); - void* previousFunc = thread->cleanupCallback2.GetPtr(); - thread->cleanupCallback2 = cleanupCallback; + void* previousFunc = thread->cleanupCallback.GetPtr(); + thread->cleanupCallback = cleanupCallback; __OSUnlockScheduler(); return previousFunc; } @@ -1341,7 +1514,7 @@ namespace coreinit void __OSQueueThreadDeallocation(OSThread_t* thread) { uint32 coreIndex = OSGetCoreId(); - TerminatorThread::DeallocatorQueueEntry queueEntry(thread, memory_getPointerFromVirtualOffset(_swapEndianU32(thread->stackEnd)), thread->deallocatorFunc); + TerminatorThread::DeallocatorQueueEntry queueEntry(thread, thread->stackEnd, thread->deallocatorFunc); s_terminatorThreads[coreIndex].queueDeallocators.push(queueEntry); OSSignalSemaphoreInternal(s_terminatorThreads[coreIndex].semaphoreQueuedDeallocators.GetPtr(), false); // do not reschedule here! Current thread must not be interrupted otherwise deallocator will run too early } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index b401d96..fdbcfea 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -2,9 +2,6 @@ #include "Cafe/HW/Espresso/Const.h" #include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" -#define OS_CONTEXT_MAGIC_0 'OSCo' -#define OS_CONTEXT_MAGIC_1 'ntxt' - struct OSThread_t; struct OSContextRegFPSCR_t @@ -16,6 +13,9 @@ struct OSContextRegFPSCR_t struct OSContext_t { + static constexpr uint32 OS_CONTEXT_MAGIC_0 = 0x4f53436f; // "OSCo" + static constexpr uint32 OS_CONTEXT_MAGIC_1 = 0x6e747874; // "ntxt" + /* +0x000 */ betype<uint32> magic0; /* +0x004 */ betype<uint32> magic1; /* +0x008 */ uint32 gpr[32]; @@ -36,24 +36,29 @@ struct OSContext_t /* +0x1BC */ uint32 gqr[8]; // GQR/UGQR /* +0x1DC */ uint32be upir; // set to current core index /* +0x1E0 */ uint64be fp_ps1[32]; - /* +0x2E0 */ uint64 uknTime2E0; - /* +0x2E8 */ uint64 uknTime2E8; - /* +0x2F0 */ uint64 uknTime2F0; - /* +0x2F8 */ uint64 uknTime2F8; - /* +0x300 */ uint32 error; // returned by __gh_errno_ptr() (used by socketlasterr) + /* +0x2E0 */ uint64be coretime[3]; + /* +0x2F8 */ uint64be starttime; + /* +0x300 */ uint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr) /* +0x304 */ uint32be affinity; - /* +0x308 */ uint32 ukn0308; - /* +0x30C */ uint32 ukn030C; - /* +0x310 */ uint32 ukn0310; - /* +0x314 */ uint32 ukn0314; - /* +0x318 */ uint32 ukn0318; - /* +0x31C */ uint32 ukn031C; + /* +0x308 */ uint32be upmc1; + /* +0x30C */ uint32be upmc2; + /* +0x310 */ uint32be upmc3; + /* +0x314 */ uint32be upmc4; + /* +0x318 */ uint32be ummcr0; + /* +0x31C */ uint32be ummcr1; bool checkMagic() { return magic0 == (uint32)OS_CONTEXT_MAGIC_0 && magic1 == (uint32)OS_CONTEXT_MAGIC_1; } + void SetContextMagic() + { + magic0 = OS_CONTEXT_MAGIC_0; + magic1 = OS_CONTEXT_MAGIC_1; + } + + bool hasCoreAffinitySet(uint32 coreIndex) const { return (((uint32)affinity >> coreIndex) & 1) != 0; @@ -361,6 +366,8 @@ namespace coreinit struct OSThread_t { + static constexpr uint32 MAGIC_THREAD = 0x74487244; // "tHrD" + enum class THREAD_TYPE : uint32 { TYPE_DRIVER = 0, @@ -383,7 +390,7 @@ struct OSThread_t ATTR_AFFINITY_CORE1 = 0x2, ATTR_AFFINITY_CORE2 = 0x4, ATTR_DETACHED = 0x8, - // more flags? + ATTR_UKN_010 = 0x10, }; enum REQUEST_FLAG_BIT : uint32 @@ -404,23 +411,21 @@ struct OSThread_t return 0; } - void SetMagic() + void SetThreadMagic() { - context.magic0 = OS_CONTEXT_MAGIC_0; - context.magic1 = OS_CONTEXT_MAGIC_1; - magic = 'tHrD'; + magic = MAGIC_THREAD; } bool IsValidMagic() const { - return magic == 'tHrD' && context.magic0 == OS_CONTEXT_MAGIC_0 && context.magic1 == OS_CONTEXT_MAGIC_1; + return magic == MAGIC_THREAD && context.magic0 == OSContext_t::OS_CONTEXT_MAGIC_0 && context.magic1 == OSContext_t::OS_CONTEXT_MAGIC_1; } /* +0x000 */ OSContext_t context; - /* +0x320 */ uint32be magic; // 'tHrD' + /* +0x320 */ uint32be magic; // "tHrD" (0x74487244) /* +0x324 */ betype<THREAD_STATE> state; /* +0x325 */ uint8 attr; - /* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads. Seems like this is always set to 0x8000 ? + /* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads /* +0x328 */ betype<sint32> suspendCounter; /* +0x32C */ sint32be effectivePriority; // effective priority (lower is higher) /* +0x330 */ sint32be basePriority; // base priority (lower is higher) @@ -440,21 +445,21 @@ struct OSThread_t /* +0x38C */ coreinit::OSThreadLink activeThreadChain; // queue of active threads (g_activeThreadQueue) - /* +0x394 */ MPTR stackBase; // upper limit of stack - /* +0x398 */ MPTR stackEnd; // lower limit of stack + /* +0x394 */ MEMPTR<void> stackBase; // upper limit of stack + /* +0x398 */ MEMPTR<void> stackEnd; // lower limit of stack - /* +0x39C */ MPTR entrypoint; + /* +0x39C */ MEMPTR<void> entrypoint; /* +0x3A0 */ crt_t crt; /* +0x578 */ sint32 alarmRelatedUkn; /* +0x57C */ std::array<MEMPTR<void>, 16> specificArray; /* +0x5BC */ betype<THREAD_TYPE> type; /* +0x5C0 */ MEMPTR<const char> threadName; - /* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? + /* +0x5C4 */ MEMPTR<void> waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? /* +0x5C8 */ uint32 userStackPointer; - /* +0x5CC */ MEMPTR<void> cleanupCallback2; + /* +0x5CC */ MEMPTR<void> cleanupCallback; /* +0x5D0 */ MEMPTR<void> deallocatorFunc; /* +0x5D4 */ uint32 stateFlags; // 0x5D4 | various flags? Controls if canceling/suspension is allowed (at cancel points) or not? If 1 -> Cancel/Suspension not allowed, if 0 -> Cancel/Suspension allowed @@ -480,19 +485,21 @@ struct OSThread_t /* +0x660 */ uint32 ukn660; + // todo - some of the members towards the end of the struct were only added in later COS versions. Figure out the mapping between version and members + // TLS /* +0x664 */ uint16 numAllocatedTLSBlocks; /* +0x666 */ sint16 tlsStatus; /* +0x668 */ MPTR tlsBlocksMPTR; - + /* +0x66C */ MEMPTR<coreinit::OSFastMutex> waitingForFastMutex; /* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; /* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; + /* +0x680 */ MEMPTR<void> alignmentExceptionCallback[Espresso::CORE_COUNT]; - /* +0x680 */ uint32 padding680[28 / 4]; + /* +0x68C */ uint32 padding68C[20 / 4]; }; - -static_assert(sizeof(OSThread_t) == 0x6A0-4); // todo - determine correct size +static_assert(sizeof(OSThread_t) == 0x6A0); namespace coreinit { diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index 88bca8a..dd7c918 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -117,10 +117,10 @@ void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); // 0 -> Success } -uint32* __gh_errno_ptr() +static uint32be* __gh_errno_ptr() { OSThread_t* osThread = coreinit::OSGetCurrentThread(); - return &osThread->context.error; + return &osThread->context.ghs_errno; } void _setSockError(sint32 errCode) diff --git a/src/Cafe/OS/libs/snd_core/ax_ist.cpp b/src/Cafe/OS/libs/snd_core/ax_ist.cpp index 30cbdbb..17f247e 100644 --- a/src/Cafe/OS/libs/snd_core/ax_ist.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_ist.cpp @@ -963,7 +963,7 @@ namespace snd_core OSInitMessageQueue(__AXIstThreadMsgQueue.GetPtr(), __AXIstThreadMsgArray.GetPtr(), 0x10); // create thread uint8 istThreadAttr = 0; - coreinit::OSCreateThreadType(__AXIstThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(AXIst_ThreadEntry), 0, &__AXIstThreadMsgQueue, __AXIstThreadStack.GetPtr() + 0x4000, 0x4000, 14, istThreadAttr, OSThread_t::THREAD_TYPE::TYPE_DRIVER); + coreinit::__OSCreateThreadType(__AXIstThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(AXIst_ThreadEntry), 0, &__AXIstThreadMsgQueue, __AXIstThreadStack.GetPtr() + 0x4000, 0x4000, 14, istThreadAttr, OSThread_t::THREAD_TYPE::TYPE_DRIVER); coreinit::OSResumeThread(__AXIstThread.GetPtr()); } diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp index 5fefc8c..b6755fd 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -155,7 +155,7 @@ void ExceptionHandler_LogGeneralInfo() const char* threadName = "NULL"; if (!threadItrBE->threadName.IsNull()) threadName = threadItrBE->threadName.GetPtr(); - sprintf(dumpLine, "%08x Ent %08x IP %08x LR %08x %-9s Aff %d%d%d Pri %2d Name %s", threadItrMPTR, _swapEndianU32(threadItrBE->entrypoint), threadItrBE->context.srr0, _swapEndianU32(threadItrBE->context.lr), threadStateStr, (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, effectivePriority, threadName); + sprintf(dumpLine, "%08x Ent %08x IP %08x LR %08x %-9s Aff %d%d%d Pri %2d Name %s", threadItrMPTR, threadItrBE->entrypoint.GetMPTR(), threadItrBE->context.srr0, _swapEndianU32(threadItrBE->context.lr), threadStateStr, (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, effectivePriority, threadName); // write line to log CrashLog_WriteLine(dumpLine); } diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index bd71942..dfbaf76 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -195,10 +195,10 @@ void DebugPPCThreadsWindow::RefreshThreadList() m_thread_list->InsertItem(item); m_thread_list->SetItemData(item, (long)threadItrMPTR); // entry point - sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->entrypoint)); + sprintf(tempStr, "%08X", cafeThread->entrypoint.GetMPTR()); m_thread_list->SetItem(i, 1, tempStr); // stack base (low) - sprintf(tempStr, "%08X - %08X", _swapEndianU32(cafeThread->stackEnd), _swapEndianU32(cafeThread->stackBase)); + sprintf(tempStr, "%08X - %08X", cafeThread->stackEnd.GetMPTR(), cafeThread->stackBase.GetMPTR()); m_thread_list->SetItem(i, 2, tempStr); // pc RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0); From 91a010fbdd023b3cac85f455fb3c32de3d2c3784 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 4 May 2024 08:05:10 +0200 Subject: [PATCH 085/130] proc_ui: Fix crash due to incorrect version handling Resolves a crash in NEX Remix --- src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 3 +++ src/Cafe/TitleList/TitleInfo.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index 5560568..dd9a460 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -391,6 +391,9 @@ namespace proc_ui { cemuLog_log(LogType::Force, "ProcUI: Trying to register callback before init"); cemu_assert_suspicious(); + // this shouldn't happen but lets set the memory pointers anyway to prevent a crash in case the user has incorrect meta info + s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)_AllocMem(sizeof(ProcUIInternalCallbackEntry)); entry->funcPtr = funcPtr; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 6d21929..2f29581 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -563,7 +563,7 @@ bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData) 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); + m_parsedAppXml->sdk_version = (uint32)std::stoull(child.text().as_string(), nullptr, 10); } return true; } From 48d2a8371b3b35b2a4439e1475c694856728f4ec Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 01:27:39 +0200 Subject: [PATCH 086/130] sndcore: Write log message instead of asserting in AXSetDeviceRemixMatrix Fixes a crash in Watch Dogs due to the non-debug assert --- src/Cafe/OS/libs/snd_core/ax_ist.cpp | 33 ++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Cafe/OS/libs/snd_core/ax_ist.cpp b/src/Cafe/OS/libs/snd_core/ax_ist.cpp index 17f247e..2ea27cb 100644 --- a/src/Cafe/OS/libs/snd_core/ax_ist.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_ist.cpp @@ -218,21 +218,36 @@ namespace snd_core // validate parameters if (deviceId == AX_DEV_TV) { - cemu_assert(inputChannelCount <= AX_TV_CHANNEL_COUNT); - cemu_assert(outputChannelCount == 1 || outputChannelCount == 2 || outputChannelCount == 6); + if(inputChannelCount > AX_TV_CHANNEL_COUNT) + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Input channel count must be smaller or equal to 6 for TV device"); + return -7; + } + if(outputChannelCount != 1 && outputChannelCount != 2 && outputChannelCount != 6) + { + // seems like Watch Dogs uses 4 as outputChannelCount for some reason? + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Output channel count must be 1, 2 or 6 for TV device"); + return -8; + } } else if (deviceId == AX_DEV_DRC) { - cemu_assert(inputChannelCount <= AX_DRC_CHANNEL_COUNT); - cemu_assert(outputChannelCount == 1 || outputChannelCount == 2 || outputChannelCount == 4); - } - else if (deviceId == AX_DEV_RMT) - { - cemu_assert(false); + if(inputChannelCount > AX_DRC_CHANNEL_COUNT) + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Input channel count must be smaller or equal to 4 for DRC device"); + return -7; + } + if(outputChannelCount != 1 && outputChannelCount != 2 && outputChannelCount != 4) + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Output channel count must be 1, 2 or 4 for DRC device"); + return -8; + } } else + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Only TV (0) and DRC (1) device are supported"); return -1; - + } auto matrices = g_remix_matrices.GetPtr(); // test if we already have an entry and just need to update the matrix data From a744670486cf27e14dd884d3a1b2ee04dc05a8cb Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 01:28:08 +0200 Subject: [PATCH 087/130] coreinit: Add export for OSGetForegroundBucketFreeArea --- src/Cafe/OS/libs/coreinit/coreinit_FG.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp index b751a8f..e22c3eb 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp @@ -185,6 +185,7 @@ namespace coreinit { osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket); cafeExportRegister("coreinit", OSGetForegroundBucket, LogType::CoreinitMem); + cafeExportRegister("coreinit", OSGetForegroundBucketFreeArea, LogType::CoreinitMem); osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard); } } From f28043e0e969f5ff5e8ad1e5eea8964ebf6f2523 Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Sat, 4 May 2024 16:34:36 -0700 Subject: [PATCH 088/130] Linux/Mac Auto-Updater (#1145) --- .github/workflows/build.yml | 20 ++++++------ src/CMakeLists.txt | 1 + src/gui/CemuUpdateWindow.cpp | 56 +++++++++++++++++++++++++++----- src/gui/GeneralSettings2.cpp | 10 +++--- src/gui/GettingStartedDialog.cpp | 7 ++-- src/gui/MainWindow.cpp | 8 +++-- src/resource/update.sh | 8 +++++ 7 files changed, 82 insertions(+), 28 deletions(-) create mode 100755 src/resource/update.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58a8508..d188b4a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: "Checkout repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" fetch-depth: 0 @@ -91,7 +91,7 @@ jobs: run: mv bin/Cemu_release bin/Cemu - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-linux-x64 @@ -102,9 +102,9 @@ jobs: needs: build-ubuntu steps: - name: Checkout Upstream Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 path: bin @@ -121,7 +121,7 @@ jobs: dist/linux/appimage.sh - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cemu-appimage-x64 path: artifacts @@ -130,7 +130,7 @@ jobs: runs-on: windows-2022 steps: - name: "Checkout repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" @@ -200,7 +200,7 @@ jobs: run: Rename-Item bin/Cemu_release.exe Cemu.exe - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-windows-x64 @@ -210,7 +210,7 @@ jobs: runs-on: macos-12 steps: - name: "Checkout repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" @@ -289,14 +289,14 @@ jobs: mv bin/Cemu_release.app bin/Cemu_app/Cemu.app mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist - chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu + chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh} ln -s /Applications bin/Cemu_app/Applications hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app" hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg rm bin/tmp.dmg - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-macos-x64 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7442e37..1b78b1f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,6 +98,7 @@ if (MACOS_BUNDLE) add_custom_command (TARGET CemuBin POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" + COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() diff --git a/src/gui/CemuUpdateWindow.cpp b/src/gui/CemuUpdateWindow.cpp index 91394ee..445c7c1 100644 --- a/src/gui/CemuUpdateWindow.cpp +++ b/src/gui/CemuUpdateWindow.cpp @@ -12,6 +12,11 @@ #include <wx/msgdlg.h> #include <wx/stdpaths.h> +#ifndef BOOST_OS_WINDOWS +#include <unistd.h> +#include <sys/stat.h> +#endif + #include <curl/curl.h> #include <zip.h> #include <boost/tokenizer.hpp> @@ -105,11 +110,11 @@ bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& auto* curl = curl_easy_init(); urlStr.append(_curlUrlEscape(curl, BUILD_VERSION_STRING)); #if BOOST_OS_LINUX - urlStr.append("&platform=linux"); + urlStr.append("&platform=linux_appimage_x86"); #elif BOOST_OS_WINDOWS urlStr.append("&platform=windows"); #elif BOOST_OS_MACOS - urlStr.append("&platform=macos_x86"); + urlStr.append("&platform=macos_bundle_x86"); #elif #error Name for current platform is missing @@ -407,7 +412,13 @@ void CemuUpdateWindow::WorkerThread() if (!exists(tmppath)) create_directory(tmppath); +#if BOOST_OS_WINDOWS const auto update_file = tmppath / L"update.zip"; +#elif BOOST_OS_LINUX + const auto update_file = tmppath / L"Cemu.AppImage"; +#elif BOOST_OS_MACOS + const auto update_file = tmppath / L"cemu.dmg"; +#endif if (DownloadCemuZip(url, update_file)) { auto* event = new wxCommandEvent(wxEVT_RESULT); @@ -427,6 +438,7 @@ void CemuUpdateWindow::WorkerThread() // extract std::string cemuFolderName; +#if BOOST_OS_WINDOWS if (!ExtractUpdate(update_file, tmppath, cemuFolderName)) { cemuLog_log(LogType::Force, "Extracting Cemu zip failed"); @@ -437,7 +449,7 @@ void CemuUpdateWindow::WorkerThread() cemuLog_log(LogType::Force, "Cemu folder not found in zip"); break; } - +#endif const auto expected_path = tmppath / cemuFolderName; if (exists(expected_path)) { @@ -472,6 +484,7 @@ void CemuUpdateWindow::WorkerThread() // apply update fs::path exePath = ActiveSettings::GetExecutablePath(); +#if BOOST_OS_WINDOWS std::wstring target_directory = exePath.parent_path().generic_wstring(); if (target_directory[target_directory.size() - 1] == '/') target_directory = target_directory.substr(0, target_directory.size() - 1); // remove trailing / @@ -480,8 +493,19 @@ void CemuUpdateWindow::WorkerThread() const auto exec = ActiveSettings::GetExecutablePath(); const auto target_exe = fs::path(exec).replace_extension("exe.backup"); fs::rename(exec, target_exe); - m_restartFile = exec; - + m_restartFile = exec; +#elif BOOST_OS_LINUX + const char* appimage_path = std::getenv("APPIMAGE"); + const auto target_exe = fs::path(appimage_path).replace_extension("AppImage.backup"); + const char* filePath = update_file.c_str(); + mode_t permissions = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + fs::rename(appimage_path, target_exe); + m_restartFile = appimage_path; + chmod(filePath, permissions); + wxString wxAppPath = wxString::FromUTF8(appimage_path); + wxCopyFile (wxT("/tmp/cemu_update/Cemu.AppImage"), wxAppPath); +#endif +#if BOOST_OS_WINDOWS const auto index = expected_path.wstring().size(); int counter = 0; for (const auto& it : fs::recursive_directory_iterator(expected_path)) @@ -516,7 +540,7 @@ void CemuUpdateWindow::WorkerThread() wxQueueEvent(this, event); } } - +#endif auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt(m_gaugeMaxValue); wxQueueEvent(this, event); @@ -565,8 +589,24 @@ void CemuUpdateWindow::OnClose(wxCloseEvent& event) exit(0); } -#else - cemuLog_log(LogType::Force, "unimplemented - restart on update"); +#elif BOOST_OS_LINUX + if (m_restartRequired && !m_restartFile.empty() && fs::exists(m_restartFile)) + { + const char* appimage_path = std::getenv("APPIMAGE"); + execlp(appimage_path, appimage_path, (char *)NULL); + + exit(0); + } +#elif BOOST_OS_MACOS + if (m_restartRequired) + { + const auto tmppath = fs::temp_directory_path() / L"cemu_update/Cemu.dmg"; + fs::path exePath = ActiveSettings::GetExecutablePath().parent_path(); + const auto apppath = exePath / L"update.sh"; + execlp("sh", "sh", apppath.c_str(), NULL); + + exit(0); + } #endif } diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 27ce37f..dab3098 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -166,9 +166,11 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); second_row->Add(m_auto_update, 0, botflag, 5); -#if BOOST_OS_LINUX || BOOST_OS_MACOS - m_auto_update->Disable(); -#endif +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_auto_update->Disable(); + } +#endif second_row->AddSpacer(10); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); @@ -2055,4 +2057,4 @@ wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error default: return "no error"; } -} \ No newline at end of file +} diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp index 91cc3a1..bfd206b 100644 --- a/src/gui/GettingStartedDialog.cpp +++ b/src/gui/GettingStartedDialog.cpp @@ -146,10 +146,11 @@ wxPanel* GettingStartedDialog::CreatePage2() m_update = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); option_sizer->Add(m_update, 0, wxALL, 5); -#if BOOST_OS_LINUX || BOOST_OS_MACOS - m_update->Disable(); +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_update->Disable(); + } #endif - sizer->Add(option_sizer, 1, wxEXPAND, 5); page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index da57870..e8103f9 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2292,9 +2292,11 @@ void MainWindow::RecreateMenu() // help menu wxMenu* helpMenu = new wxMenu(); m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates")); -#if BOOST_OS_LINUX || BOOST_OS_MACOS - m_check_update_menu->Enable(false); -#endif +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_check_update_menu->Enable(false); + } +#endif helpMenu->Append(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, _("&Getting started")); helpMenu->AppendSeparator(); helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu")); diff --git a/src/resource/update.sh b/src/resource/update.sh new file mode 100755 index 0000000..5ff2216 --- /dev/null +++ b/src/resource/update.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +APP=$(cd "$(dirname "0")"/;pwd) +hdiutil attach $TMPDIR/cemu_update/cemu.dmg +cp -rf /Volumes/Cemu/Cemu.app "$APP" +hdiutil detach /Volumes/Cemu/ + +open -n -a "$APP/Cemu.app" From dc480ac00bc6367f9272c490fbf2a7e4cacee218 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 May 2024 02:35:01 +0200 Subject: [PATCH 089/130] Add support for WUHB file format (#1190) --- src/Cafe/CMakeLists.txt | 4 + src/Cafe/Filesystem/WUHB/RomFSStructs.h | 40 ++++ src/Cafe/Filesystem/WUHB/WUHBReader.cpp | 224 ++++++++++++++++++++++ src/Cafe/Filesystem/WUHB/WUHBReader.h | 45 +++++ src/Cafe/Filesystem/fsc.h | 3 + src/Cafe/Filesystem/fscDeviceWuhb.cpp | 151 +++++++++++++++ src/Cafe/TitleList/TitleInfo.cpp | 82 ++++++++ src/Cafe/TitleList/TitleInfo.h | 3 + src/Cafe/TitleList/TitleList.cpp | 3 +- src/gui/MainWindow.cpp | 6 +- src/gui/components/wxGameList.cpp | 10 + src/gui/components/wxTitleManagerList.cpp | 5 + src/gui/components/wxTitleManagerList.h | 1 + src/util/helpers/helpers.cpp | 41 ++++ src/util/helpers/helpers.h | 2 + 15 files changed, 617 insertions(+), 3 deletions(-) create mode 100644 src/Cafe/Filesystem/WUHB/RomFSStructs.h create mode 100644 src/Cafe/Filesystem/WUHB/WUHBReader.cpp create mode 100644 src/Cafe/Filesystem/WUHB/WUHBReader.h create mode 100644 src/Cafe/Filesystem/fscDeviceWuhb.cpp diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index d64a599..851854f 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(CemuCafe Filesystem/fscDeviceRedirect.cpp Filesystem/fscDeviceWua.cpp Filesystem/fscDeviceWud.cpp + Filesystem/fscDeviceWuhb.cpp Filesystem/fsc.h Filesystem/FST/FST.cpp Filesystem/FST/FST.h @@ -18,6 +19,9 @@ add_library(CemuCafe Filesystem/FST/KeyCache.h Filesystem/WUD/wud.cpp Filesystem/WUD/wud.h + Filesystem/WUHB/RomFSStructs.h + Filesystem/WUHB/WUHBReader.cpp + Filesystem/WUHB/WUHBReader.h GamePatch.cpp GamePatch.h GameProfile/GameProfile.cpp diff --git a/src/Cafe/Filesystem/WUHB/RomFSStructs.h b/src/Cafe/Filesystem/WUHB/RomFSStructs.h new file mode 100644 index 0000000..59ef503 --- /dev/null +++ b/src/Cafe/Filesystem/WUHB/RomFSStructs.h @@ -0,0 +1,40 @@ +#pragma once + +struct romfs_header_t +{ + uint32 header_magic; + uint32be header_size; + uint64be dir_hash_table_ofs; + uint64be dir_hash_table_size; + uint64be dir_table_ofs; + uint64be dir_table_size; + uint64be file_hash_table_ofs; + uint64be file_hash_table_size; + uint64be file_table_ofs; + uint64be file_table_size; + uint64be file_partition_ofs; +}; + +struct romfs_direntry_t +{ + uint32be parent; + uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling") + uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child") + uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file") + uint32be hash; + uint32be name_size; + std::string name; +}; + +struct romfs_fentry_t +{ + uint32be parent; + uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling") + uint64be offset; + uint64be size; + uint32be hash; + uint32be name_size; + std::string name; +}; + +#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF diff --git a/src/Cafe/Filesystem/WUHB/WUHBReader.cpp b/src/Cafe/Filesystem/WUHB/WUHBReader.cpp new file mode 100644 index 0000000..e7a4c9b --- /dev/null +++ b/src/Cafe/Filesystem/WUHB/WUHBReader.cpp @@ -0,0 +1,224 @@ +#include "WUHBReader.h" +WUHBReader* WUHBReader::FromPath(const fs::path& path) +{ + FileStream* fileIn{FileStream::openFile2(path)}; + if (!fileIn) + return nullptr; + + WUHBReader* ret = new WUHBReader(fileIn); + if (!ret->CheckMagicValue()) + { + delete ret; + return nullptr; + } + + if (!ret->ReadHeader()) + { + delete ret; + return nullptr; + } + + return ret; +} + +static const romfs_direntry_t fallbackDirEntry{ + .parent = ROMFS_ENTRY_EMPTY, + .listNext = ROMFS_ENTRY_EMPTY, + .dirListHead = ROMFS_ENTRY_EMPTY, + .fileListHead = ROMFS_ENTRY_EMPTY, + .hash = ROMFS_ENTRY_EMPTY, + .name_size = 0, + .name = "" +}; +static const romfs_fentry_t fallbackFileEntry{ + .parent = ROMFS_ENTRY_EMPTY, + .listNext = ROMFS_ENTRY_EMPTY, + .offset = 0, + .size = 0, + .hash = ROMFS_ENTRY_EMPTY, + .name_size = 0, + .name = "" +}; +template<bool File> +const WUHBReader::EntryType<File>& WUHBReader::GetFallback() +{ + if constexpr (File) + return fallbackFileEntry; + else + return fallbackDirEntry; +} + +template<bool File> +WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const +{ + auto fallback = GetFallback<File>(); + if(offset == ROMFS_ENTRY_EMPTY) + return fallback; + + const char* typeName = File ? "fentry" : "direntry"; + EntryType<File> ret; + if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size)) + { + cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName); + return fallback; + } + + // read the entry + m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset); + auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name)); + if (read != offsetof(EntryType<File>, name)) + { + cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset); + return fallback; + } + + // read the name + ret.name.resize(ret.name_size); + read = m_fileIn->readData(ret.name.data(), ret.name_size); + if (read != ret.name_size) + { + cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName); + return fallback; + } + + return ret; +} + +romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const +{ + return GetEntry<false>(offset); +} +romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const +{ + return GetEntry<true>(offset); +} + +uint64 WUHBReader::GetFileSize(uint32 entryOffset) const +{ + return GetFileEntry(entryOffset).size; +} + +uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const +{ + const auto fileEntry = GetFileEntry(entryOffset); + if (fileOffset >= fileEntry.size) + return 0; + const uint64 readAmount = std::min(length, fileEntry.size - fileOffset); + const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset; + m_fileIn->SetPosition(wuhbOffset); + return m_fileIn->readData(buffer, readAmount); +} + +uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const +{ + const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size); + const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs); + + const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32); + const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32); + + m_fileIn->SetPosition(hash_table_entry_offset); + uint32 tableOffset; + if (!m_fileIn->readU32(tableOffset)) + { + cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset); + return ROMFS_ENTRY_EMPTY; + } + + return uint32be::from_bevalue(tableOffset); +} + +template<bool T> +bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const +{ + for (;;) + { + if (entryOffset == ROMFS_ENTRY_EMPTY) + return false; + auto entry = GetEntry<T>(entryOffset); + + if (entry.name == targetName) + return true; + entryOffset = entry.hash; + } + return false; +} + +uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const +{ + uint32 currentEntryOffset = 0; + auto look = [&](const fs::path& part, bool lookInFileHT) { + const auto partString = part.string(); + currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT); + if (lookInFileHT) + return SearchHashList<true>(currentEntryOffset, part); + else + return SearchHashList<false>(currentEntryOffset, part); + }; + // look for the root entry + if (!look("", false)) + return ROMFS_ENTRY_EMPTY; + + auto it = path.begin(); + while (it != path.end()) + { + fs::path part = *it; + ++it; + // no need to recurse after trailing forward slash (e.g. directory/) + if (part.empty() && !isFile) + break; + // skip leading forward slash + if (part == "/") + continue; + + // if the lookup target is a file and this is the last iteration, look in the file hash table instead. + if (!look(part, it == path.end() && isFile)) + return ROMFS_ENTRY_EMPTY; + } + return currentEntryOffset; +} +bool WUHBReader::CheckMagicValue() const +{ + uint8 magic[4]; + m_fileIn->SetPosition(0); + int read = m_fileIn->readData(magic, 4); + if (read != 4) + { + cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers"); + return false; + } + static_assert(sizeof(magic) == s_headerMagicValue.size()); + return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0; +} +bool WUHBReader::ReadHeader() +{ + m_fileIn->SetPosition(0); + auto read = m_fileIn->readData(&m_header, sizeof(m_header)); + auto readSuccess = read == sizeof(m_header); + if (!readSuccess) + cemuLog_log(LogType::Force, "Failed to read WUHB header"); + return readSuccess; +} +unsigned char WUHBReader::NormalizeChar(unsigned char c) +{ + if (c >= 'a' && c <= 'z') + { + return c + 'A' - 'a'; + } + else + { + return c; + } +} +uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len) +{ + cemu_assert(path != nullptr || path_len == 0); + uint32 hash = parent ^ 123456789; + for (uint32 i = 0; i < path_len; i++) + { + hash = (hash >> 5) | (hash << 27); + hash ^= NormalizeChar(path[start + i]); + } + + return hash; +} diff --git a/src/Cafe/Filesystem/WUHB/WUHBReader.h b/src/Cafe/Filesystem/WUHB/WUHBReader.h new file mode 100644 index 0000000..9187f05 --- /dev/null +++ b/src/Cafe/Filesystem/WUHB/WUHBReader.h @@ -0,0 +1,45 @@ +#pragma once +#include <Common/FileStream.h> +#include "RomFSStructs.h" +class WUHBReader +{ + public: + static WUHBReader* FromPath(const fs::path& path); + + romfs_direntry_t GetDirEntry(uint32 offset) const; + romfs_fentry_t GetFileEntry(uint32 offset) const; + + uint64 GetFileSize(uint32 entryOffset) const; + + uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const; + + uint32 Lookup(const std::filesystem::path& path, bool isFile) const; + + private: + WUHBReader(FileStream* file) + : m_fileIn(file) + { + cemu_assert_debug(file != nullptr); + }; + WUHBReader() = delete; + + romfs_header_t m_header; + std::unique_ptr<FileStream> m_fileIn; + constexpr static std::string_view s_headerMagicValue = "WUHB"; + bool ReadHeader(); + bool CheckMagicValue() const; + + static inline unsigned char NormalizeChar(unsigned char c); + static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len); + + template<bool File> + using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>; + template<bool File> + static const EntryType<File>& GetFallback(); + template<bool File> + EntryType<File> GetEntry(uint32 offset) const; + + template<bool T> + bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const; + uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const; +}; diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h index 09c1f50..a3df2af 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -204,6 +204,9 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination // wua device bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority); +// wuhb device +bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority); + // hostFS device bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority); diff --git a/src/Cafe/Filesystem/fscDeviceWuhb.cpp b/src/Cafe/Filesystem/fscDeviceWuhb.cpp new file mode 100644 index 0000000..5e8e648 --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceWuhb.cpp @@ -0,0 +1,151 @@ +#include "Filesystem/WUHB/WUHBReader.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/Filesystem/FST/FST.h" + +class FSCDeviceWuhbFileCtx : public FSCVirtualFile +{ + public: + FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType) + : m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType) + { + cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY); + if (fscType == FSC_TYPE_DIRECTORY) + { + romfs_direntry_t entry = reader->GetDirEntry(entryOffset); + m_dirIterOffset = entry.dirListHead; + m_fileIterOffset = entry.fileListHead; + } + } + sint32 fscGetType() override + { + return m_fscType; + } + uint64 fscQueryValueU64(uint32 id) override + { + if (m_fscType == FSC_TYPE_FILE) + { + if (id == FSC_QUERY_SIZE) + return m_wuhbReader->GetFileSize(m_entryOffset); + else if (id == FSC_QUERY_WRITEABLE) + return 0; // WUHB images are read-only + else + cemu_assert_error(); + } + else + { + cemu_assert_unimplemented(); + } + return 0; + } + uint32 fscWriteData(void* buffer, uint32 size) override + { + cemu_assert_error(); + return 0; + } + uint32 fscReadData(void* buffer, uint32 size) override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer); + m_seek += read; + return read; + } + void fscSetSeek(uint64 seek) override + { + m_seek = seek; + } + uint64 fscGetSeek() override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + return m_seek; + } + void fscSetFileLength(uint64 endOffset) override + { + cemu_assert_error(); + } + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (m_dirIterOffset != ROMFS_ENTRY_EMPTY) + { + romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset); + m_dirIterOffset = entry.listNext; + if(entry.name_size > 0) + { + dirEntry->isDirectory = true; + dirEntry->isFile = false; + dirEntry->fileSize = 0; + std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH); + return true; + } + } + if (m_fileIterOffset != ROMFS_ENTRY_EMPTY) + { + romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset); + m_fileIterOffset = entry.listNext; + if(entry.name_size > 0) + { + dirEntry->isDirectory = false; + dirEntry->isFile = true; + dirEntry->fileSize = entry.size; + std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH); + return true; + } + } + + return false; + } + + private: + WUHBReader* m_wuhbReader{}; + uint32 m_fscType; + uint32 m_entryOffset = ROMFS_ENTRY_EMPTY; + uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY; + uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY; + uint64 m_seek = 0; +}; + +class fscDeviceWUHB : public fscDeviceC +{ + FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + WUHBReader* reader = (WUHBReader*)ctx; + cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported + + bool isFile; + uint32 table_offset = ROMFS_ENTRY_EMPTY; + + if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + table_offset = reader->Lookup(path, false); + isFile = false; + } + if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) + { + table_offset = reader->Lookup(path, true); + isFile = true; + } + + if (table_offset == ROMFS_ENTRY_EMPTY) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + + *fscStatus = FSC_STATUS_OK; + return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY); + } + + // singleton + public: + static fscDeviceWUHB& instance() + { + static fscDeviceWUHB _instance; + return _instance; + } +}; + +bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority) +{ + return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK; +} diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 2f29581..1213105 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -1,9 +1,12 @@ #include "TitleInfo.h" #include "Cafe/Filesystem/fscDeviceHostFS.h" +#include "Cafe/Filesystem/WUHB/WUHBReader.h" #include "Cafe/Filesystem/FST/FST.h" #include "pugixml.hpp" #include "Common/FileStream.h" #include <zarchive/zarchivereader.h> +#include "util/IniParser/IniParser.h" +#include "util/crypto/crc32.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" @@ -97,6 +100,7 @@ TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo) m_isValid = false; if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS && cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE && + cachedInfo.titleDataFormat != TitleDataFormat::WUHB && cachedInfo.titleDataFormat != TitleDataFormat::WUD && cachedInfo.titleDataFormat != TitleDataFormat::NUS && cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE) @@ -245,6 +249,16 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF delete zar; return foundBase; } + else if (boost::iends_with(filenameStr, ".wuhb")) + { + std::unique_ptr<WUHBReader> reader{WUHBReader::FromPath(path)}; + if(reader) + { + formatOut = TitleDataFormat::WUHB; + pathOut = path; + return true; + } + } // note: Since a Wii U archive file (.wua) contains multiple titles we shouldn't auto-detect them here // instead TitleInfo has a second constructor which takes a subpath // unable to determine type by extension, check contents @@ -436,6 +450,23 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, return false; } } + else if (m_titleFormat == TitleDataFormat::WUHB) + { + if (!m_wuhbreader) + { + m_wuhbreader = WUHBReader::FromPath(m_fullPath); + if (!m_wuhbreader) + return false; + } + bool r = FSCDeviceWUHB_Mount(virtualPath, subfolder, m_wuhbreader, mountPriority); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + delete m_wuhbreader; + m_wuhbreader = nullptr; + return false; + } + } else { cemu_assert_unimplemented(); @@ -467,6 +498,12 @@ void TitleInfo::Unmount(std::string_view virtualPath) if (m_mountpoints.empty()) m_zarchive = nullptr; } + if (m_wuhbreader) + { + cemu_assert_debug(m_titleFormat == TitleDataFormat::WUHB); + delete m_wuhbreader; + m_wuhbreader = nullptr; + } } return; } @@ -502,6 +539,20 @@ bool TitleInfo::ParseXmlInfo() auto xmlData = fsc_extractFile(fmt::format("{}meta/meta.xml", mountPath).c_str()); if(xmlData) m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size()); + + if(!m_parsedMetaXml) + { + // meta/meta.ini (WUHB) + auto iniData = fsc_extractFile(fmt::format("{}meta/meta.ini", mountPath).c_str()); + if (iniData) + m_parsedMetaXml = ParseAromaIni(*iniData); + if(m_parsedMetaXml) + { + m_parsedCosXml = new ParsedCosXml{.argstr = "root.rpx"}; + m_parsedAppXml = new ParsedAppXml{m_parsedMetaXml->m_title_id, 0, 0, 0, 0}; + } + } + // code/app.xml xmlData = fsc_extractFile(fmt::format("{}code/app.xml", mountPath).c_str()); if(xmlData) @@ -539,6 +590,34 @@ bool TitleInfo::ParseXmlInfo() return true; } +ParsedMetaXml* TitleInfo::ParseAromaIni(std::span<unsigned char> content) +{ + IniParser parser{content}; + while (parser.NextSection() && parser.GetCurrentSectionName() != "menu") + continue; + if (parser.GetCurrentSectionName() != "menu") + return nullptr; + + auto parsed = std::make_unique<ParsedMetaXml>(); + + const auto author = parser.FindOption("author"); + if (author) + parsed->m_publisher[(size_t)CafeConsoleLanguage::EN] = *author; + + const auto longName = parser.FindOption("longname"); + if (longName) + parsed->m_long_name[(size_t)CafeConsoleLanguage::EN] = *longName; + + const auto shortName = parser.FindOption("shortname"); + if (shortName) + parsed->m_short_name[(size_t)CafeConsoleLanguage::EN] = *shortName; + + auto checksumInput = std::string{*author}.append(*longName).append(*shortName); + parsed->m_title_id = (0x0005000Full<<32) | crc32_calc(checksumInput.data(), checksumInput.length()); + + return parsed.release(); +} + bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData) { pugi::xml_document app_doc; @@ -695,6 +774,9 @@ std::string TitleInfo::GetPrintPath() const case TitleDataFormat::WIIU_ARCHIVE: tmp.append(" [WUA]"); break; + case TitleDataFormat::WUHB: + tmp.append(" [WUHB]"); + break; default: break; } diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index e9347db..fa5b9c8 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -127,6 +127,7 @@ public: WUD = 2, // WUD or WUX WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua) NUS = 4, // NUS format. Directory with .app files, title.tik and title.tmd + WUHB = 5, // error INVALID_STRUCTURE = 0, }; @@ -265,6 +266,7 @@ private: bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut); void CalcUID(); void SetInvalidReason(InvalidReason reason); + ParsedMetaXml* ParseAromaIni(std::span<unsigned char> content); bool ParseAppXml(std::vector<uint8>& appXmlData); bool m_isValid{ false }; @@ -277,6 +279,7 @@ private: std::vector<std::pair<sint32, std::string>> m_mountpoints; class FSTVolume* m_wudVolume{}; class ZArchiveReader* m_zarchive{}; + class WUHBReader* m_wuhbreader{}; // xml info bool m_hasParsedXmlFiles{ false }; ParsedMetaXml* m_parsedMetaXml{}; diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index c288dd1..7b75fac 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -342,7 +342,8 @@ bool _IsKnownFileNameOrExtension(const fs::path& path) fileExtension == ".wud" || fileExtension == ".wux" || fileExtension == ".iso" || - fileExtension == ".wua"; + fileExtension == ".wua" || + fileExtension == ".wuhb"; // note: To detect extracted titles with RPX we rely on the presence of the content,code,meta directory structure } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index e8103f9..c34c547 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -643,16 +643,18 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) if (menuId == MAINFRAME_MENU_ID_FILE_LOAD) { const auto wildcard = formatWxString( - "{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf;title.tmd" + "{}|*.wud;*.wux;*.wua;*.wuhb;*.iso;*.rpx;*.elf;title.tmd" "|{}|*.wud;*.wux;*.iso" "|{}|title.tmd" "|{}|*.wua" + "|{}|*.wuhb" "|{}|*.rpx;*.elf" "|{}|*", - _("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"), + _("All Wii U files (*.wud, *.wux, *.wua, *.wuhb, *.iso, *.rpx, *.elf)"), _("Wii U image (*.wud, *.wux, *.iso, *.wad)"), _("Wii U NUS content"), _("Wii U archive (*.wua)"), + _("Wii U homebrew bundle (*.wuhb)"), _("Wii U executable (*.rpx, *.elf)"), _("All files (*.*)") ); diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index d7c9a4f..eedfde5 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1230,6 +1230,16 @@ void wxGameList::AsyncWorkerThread() if(!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE)) continue; auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str()); + // try iconTex.tga.gz + if (!tgaData) + { + tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga.gz").c_str()); + if (tgaData) + { + auto decompressed = zlibDecompress(*tgaData, 70*1024); + std::swap(tgaData, decompressed); + } + } bool iconSuccessfullyLoaded = false; if (tgaData && tgaData->size() > 16) { diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index c02bffb..e8efb06 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -948,6 +948,8 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu return _("NUS"); case wxTitleManagerList::EntryFormat::WUA: return _("WUA"); + case wxTitleManagerList::EntryFormat::WUHB: + return _("WUHB"); } return ""; } @@ -1022,6 +1024,9 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt case TitleInfo::TitleDataFormat::WIIU_ARCHIVE: entryFormat = EntryFormat::WUA; break; + case TitleInfo::TitleDataFormat::WUHB: + entryFormat = EntryFormat::WUHB; + break; case TitleInfo::TitleDataFormat::HOST_FS: default: entryFormat = EntryFormat::Folder; diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 14721c5..2780a9c 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -44,6 +44,7 @@ public: WUD, NUS, WUA, + WUHB, }; // sort by column, if -1 will sort by last column or default (=titleid) diff --git a/src/util/helpers/helpers.cpp b/src/util/helpers/helpers.cpp index 7e22e9f..bac2d44 100644 --- a/src/util/helpers/helpers.cpp +++ b/src/util/helpers/helpers.cpp @@ -11,6 +11,8 @@ #include <boost/random/uniform_int.hpp> +#include <zlib.h> + #if BOOST_OS_WINDOWS #include <TlHelp32.h> @@ -437,3 +439,42 @@ std::string GenerateRandomString(const size_t length, const std::string_view cha return result; } + +std::optional<std::vector<uint8>> zlibDecompress(const std::vector<uint8>& compressed, size_t sizeHint) +{ + int err; + std::vector<uint8> decompressed; + size_t outWritten = 0; + size_t bytesPerIteration = sizeHint; + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.avail_in = compressed.size(); + stream.next_in = (Bytef*)compressed.data(); + err = inflateInit2(&stream, 32); // 32 is a zlib magic value to enable header detection + if (err != Z_OK) + return {}; + + do + { + decompressed.resize(decompressed.size() + bytesPerIteration); + const auto availBefore = decompressed.size() - outWritten; + stream.avail_out = availBefore; + stream.next_out = decompressed.data() + outWritten; + err = inflate(&stream, Z_NO_FLUSH); + if (!(err == Z_OK || err == Z_STREAM_END)) + { + inflateEnd(&stream); + return {}; + } + outWritten += availBefore - stream.avail_out; + bytesPerIteration *= 2; + } + while (err != Z_STREAM_END); + + inflateEnd(&stream); + decompressed.resize(stream.total_out); + + return decompressed; +} diff --git a/src/util/helpers/helpers.h b/src/util/helpers/helpers.h index 09b80fe..1edc2e1 100644 --- a/src/util/helpers/helpers.h +++ b/src/util/helpers/helpers.h @@ -257,3 +257,5 @@ bool IsWindows81OrGreater(); bool IsWindows10OrGreater(); fs::path GetParentProcess(); + +std::optional<std::vector<uint8>> zlibDecompress(const std::vector<uint8>& compressed, size_t sizeHint = 32*1024); From 70afe3a03342f3f89fb45089fd23cc1b4dffbe45 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 09:11:08 +0200 Subject: [PATCH 090/130] nlibcurl: Use separte logging type --- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 14 +++++++------- src/Cemu/Logging/CemuLogging.h | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 7a8eacb..a992665 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -1505,18 +1505,18 @@ CURLcode curl_global_init_mem(uint32 flags, MEMPTR<curl_malloc_callback> malloc_ void load() { - cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::Force); - cafeExportRegister("nlibcurl", curl_global_init, LogType::Force); + cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::nlibcurl); + cafeExportRegister("nlibcurl", curl_global_init, LogType::nlibcurl); - cafeExportRegister("nlibcurl", curl_slist_append, LogType::Force); - cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::Force); + cafeExportRegister("nlibcurl", curl_slist_append, LogType::nlibcurl); + cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_strerror", export_curl_easy_strerror); osLib_addFunction("nlibcurl", "curl_share_init", export_curl_share_init); osLib_addFunction("nlibcurl", "curl_share_setopt", export_curl_share_setopt); osLib_addFunction("nlibcurl", "curl_share_cleanup", export_curl_share_cleanup); - cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::Force); + cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_multi_init", export_curl_multi_init); osLib_addFunction("nlibcurl", "curl_multi_add_handle", export_curl_multi_add_handle); osLib_addFunction("nlibcurl", "curl_multi_perform", export_curl_multi_perform); @@ -1527,11 +1527,11 @@ void load() osLib_addFunction("nlibcurl", "curl_multi_cleanup", export_curl_multi_cleanup); osLib_addFunction("nlibcurl", "curl_multi_timeout", export_curl_multi_timeout); - cafeExportRegister("nlibcurl", curl_easy_init, LogType::Force); + cafeExportRegister("nlibcurl", curl_easy_init, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_reset", export_curl_easy_reset); osLib_addFunction("nlibcurl", "curl_easy_setopt", export_curl_easy_setopt); osLib_addFunction("nlibcurl", "curl_easy_getinfo", export_curl_easy_getinfo); - cafeExportRegister("nlibcurl", curl_easy_perform, LogType::Force); + cafeExportRegister("nlibcurl", curl_easy_perform, LogType::nlibcurl); diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 44e8936..8fbb318 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -41,6 +41,7 @@ enum class LogType : sint32 TextureReadback = 29, ProcUi = 39, + nlibcurl = 41, PRUDP = 40, }; From dd3ed5650983180ed71640567c588bd21bb43564 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 10:05:35 +0200 Subject: [PATCH 091/130] nn_save: Fix inverted condition preventing accessing other title's saves --- src/Cafe/OS/libs/nn_save/nn_save.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 518e419..09d4413 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -118,11 +118,11 @@ namespace save return false; } - SAVEStatus GetAbsoluteFullPathOtherApplication(uint32 persistentId, uint64 titleId, const char* subDir, char* outPath) + FS_RESULT GetAbsoluteFullPathOtherApplication(uint32 persistentId, uint64 titleId, const char* subDir, char* outPath) { uint32be applicationBox; if(acp::ACPGetApplicationBox(&applicationBox, titleId) != acp::ACPStatus::SUCCESS) - return (FSStatus)FS_RESULT::NOT_FOUND; + return FS_RESULT::NOT_FOUND; sint32 written = 0; if(applicationBox == 3) @@ -151,13 +151,13 @@ namespace save cemu_assert_unimplemented(); } else - return (FSStatus)FS_RESULT::NOT_FOUND; + return FS_RESULT::NOT_FOUND; if (written < SAVE_MAX_PATH_SIZE - 1) - return (FSStatus)FS_RESULT::SUCCESS; + return FS_RESULT::SUCCESS; cemu_assert_suspicious(); - return (FSStatus)(FS_RESULT::FATAL_ERROR); + return FS_RESULT::FATAL_ERROR; } typedef struct @@ -417,7 +417,7 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else @@ -527,7 +527,7 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == (FSStatus)FS_RESULT::SUCCESS) + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else @@ -811,7 +811,7 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else From bf37a8281e2dee8b7b9dc04478b99d7a8310ff0b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 14:06:26 +0200 Subject: [PATCH 092/130] CI: Update action versions --- .github/workflows/deploy_experimental_release.yml | 8 ++++---- .github/workflows/deploy_stable_release.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy_experimental_release.yml b/.github/workflows/deploy_experimental_release.yml index 3bf86db..a8c5ec5 100644 --- a/.github/workflows/deploy_experimental_release.yml +++ b/.github/workflows/deploy_experimental_release.yml @@ -15,22 +15,22 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 path: cemu-bin-linux-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-appimage-x64 path: cemu-appimage-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-windows-x64 path: cemu-bin-windows-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-macos-x64 path: cemu-bin-macos-x64 diff --git a/.github/workflows/deploy_stable_release.yml b/.github/workflows/deploy_stable_release.yml index 5be3141..fd339e7 100644 --- a/.github/workflows/deploy_stable_release.yml +++ b/.github/workflows/deploy_stable_release.yml @@ -17,22 +17,22 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 path: cemu-bin-linux-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-appimage-x64 path: cemu-appimage-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-windows-x64 path: cemu-bin-windows-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-macos-x64 path: cemu-bin-macos-x64 From bd13d4bdc30b608770f9f7cb7c5ec44f6687f329 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 17:05:11 +0200 Subject: [PATCH 093/130] nn_act: Make AcquireToken gracefully fail in offline mode + refactor --- src/Cafe/IOSU/legacy/iosu_act.cpp | 500 ++++++++++++++++++----------- src/Cafe/IOSU/legacy/iosu_act.h | 2 +- src/Cafe/OS/libs/nn_act/nn_act.cpp | 10 +- 3 files changed, 310 insertions(+), 202 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 4285668..a115d6f 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -21,14 +21,18 @@ using namespace iosu::kernel; +using NexToken = NAPI::ACTNexToken; +static_assert(sizeof(NexToken) == 0x25C); + struct { bool isInitialized; + std::mutex actMutex; }iosuAct = { }; // account manager -typedef struct +struct actAccountData_t { bool isValid; // options @@ -49,7 +53,12 @@ typedef struct // Mii FFLData_t miiData; uint16le miiNickname[ACT_NICKNAME_LENGTH]; -}actAccountData_t; + + bool IsNetworkAccount() const + { + return isNetworkAccount; // todo - IOSU only checks if accountId is not empty? + } +}; #define IOSU_ACT_ACCOUNT_MAX_COUNT (0xC) @@ -159,161 +168,11 @@ uint32 iosuAct_getAccountIdOfCurrentAccount() // IOSU act API interface -namespace iosu -{ - namespace act - { - uint8 getCurrentAccountSlot() - { - return 1; - } - - bool getPrincipalId(uint8 slot, uint32* principalId) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - *principalId = 0; - return false; - } - *principalId = _actAccountData[accountIndex].principalId; - return true; - } - - bool getAccountId(uint8 slot, char* accountId) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - *accountId = '\0'; - return false; - } - strcpy(accountId, _actAccountData[accountIndex].accountId); - return true; - } - - // returns empty string if invalid - std::string getAccountId2(uint8 slot) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - return {}; - return {_actAccountData[accountIndex].accountId}; - } - - bool getMii(uint8 slot, FFLData_t* fflData) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - return false; - } - memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); - return true; - } - - // return screenname in little-endian wide characters - bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - screenname[0] = '\0'; - return false; - } - for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) - { - screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i]; - } - return true; - } - - bool getCountryIndex(uint8 slot, uint32* countryIndex) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - *countryIndex = 0; - return false; - } - *countryIndex = _actAccountData[accountIndex].countryIndex; - return true; - } - - bool GetPersistentId(uint8 slot, uint32* persistentId) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if(!_actAccountData[accountIndex].isValid) - { - *persistentId = 0; - return false; - } - *persistentId = _actAccountData[accountIndex].persistentId; - return true; - } - - class ActService : public iosu::nn::IPCService - { - public: - ActService() : iosu::nn::IPCService("/dev/act") {} - - nnResult ServiceCall(uint32 serviceId, void* request, void* response) override - { - 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); - } - }; - - ActService gActService; - - void Initialize() - { - gActService.Start(); - } - - void Stop() - { - gActService.Stop(); - } - } -} - - -// IOSU act IO - -typedef struct -{ - /* +0x00 */ uint32be ukn00; - /* +0x04 */ uint32be ukn04; - /* +0x08 */ uint32be ukn08; - /* +0x0C */ uint32be subcommandCode; - /* +0x10 */ uint8 ukn10; - /* +0x11 */ uint8 ukn11; - /* +0x12 */ uint8 ukn12; - /* +0x13 */ uint8 accountSlot; - /* +0x14 */ uint32be unique; // is this command specific? -}cmdActRequest00_t; - -typedef struct -{ - uint32be returnCode; - uint8 transferableIdBase[8]; -}cmdActGetTransferableIDResult_t; - -#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4 -#define ACT_SUBCMD_INITIALIZE 0x14 - -#define _cancelIfAccountDoesNotExist() \ -if (_actAccountData[accountIndex].isValid == false) \ -{ \ - /* account does not exist*/ \ - ioctlReturnValue = 0; \ - actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \ - actCemuRequest->resultU64.u64 = 0; \ - iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \ - continue; \ -} +static const auto ACTResult_Ok = 0; +static const auto ACTResult_InvalidValue = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12F00); // 0xC0712F00 +static const auto ACTResult_OutOfRange = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12D80); // 0xC0712D80 +static const auto ACTResult_AccountDoesNotExist = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST); // 0xA071F480 +static const auto ACTResult_NotANetworkAccount = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0x1FE80); // 0xA071FE80 nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec) { @@ -518,6 +377,291 @@ nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec) return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); } +namespace iosu +{ + namespace act + { + uint8 getCurrentAccountSlot() + { + return 1; + } + + actAccountData_t* GetAccountBySlotNo(uint8 slotNo) + { + // only call this while holding actMutex + uint8 accIndex; + if(slotNo == iosu::act::ACT_SLOT_CURRENT) + { + accIndex = getCurrentAccountSlot() - 1; + cemu_assert_debug(accIndex >= 0 && accIndex < IOSU_ACT_ACCOUNT_MAX_COUNT); + } + else if(slotNo > 0 && slotNo <= IOSU_ACT_ACCOUNT_MAX_COUNT) + accIndex = slotNo - 1; + else + { + return nullptr; + } + if(!_actAccountData[accIndex].isValid) + return nullptr; + return &_actAccountData[accIndex]; + } + + // has ownership of account data + // while any thread has a LockedAccount in non-null state no other thread can access the account data + class LockedAccount + { + public: + LockedAccount(uint8 slotNo) + { + iosuAct.actMutex.lock(); + m_account = GetAccountBySlotNo(slotNo); + if(!m_account) + iosuAct.actMutex.unlock(); + } + + ~LockedAccount() + { + if(m_account) + iosuAct.actMutex.unlock(); + } + + void Release() + { + if(m_account) + iosuAct.actMutex.unlock(); + m_account = nullptr; + } + + actAccountData_t* operator->() + { + return m_account; + } + + actAccountData_t& operator*() + { + return *m_account; + } + + LockedAccount(const LockedAccount&) = delete; + LockedAccount& operator=(const LockedAccount&) = delete; + + operator bool() const { return m_account != nullptr; } + + private: + actAccountData_t* m_account{nullptr}; + }; + + bool getPrincipalId(uint8 slot, uint32* principalId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *principalId = 0; + return false; + } + *principalId = _actAccountData[accountIndex].principalId; + return true; + } + + bool getAccountId(uint8 slot, char* accountId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *accountId = '\0'; + return false; + } + strcpy(accountId, _actAccountData[accountIndex].accountId); + return true; + } + + // returns empty string if invalid + std::string getAccountId2(uint8 slot) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + return {}; + return {_actAccountData[accountIndex].accountId}; + } + + bool getMii(uint8 slot, FFLData_t* fflData) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + return false; + } + memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); + return true; + } + + // return screenname in little-endian wide characters + bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + screenname[0] = '\0'; + return false; + } + for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) + { + screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i]; + } + return true; + } + + bool getCountryIndex(uint8 slot, uint32* countryIndex) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *countryIndex = 0; + return false; + } + *countryIndex = _actAccountData[accountIndex].countryIndex; + return true; + } + + bool GetPersistentId(uint8 slot, uint32* persistentId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if(!_actAccountData[accountIndex].isValid) + { + *persistentId = 0; + return false; + } + *persistentId = _actAccountData[accountIndex].persistentId; + return true; + } + + nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen) + { + if (accountSlot != ACT_SLOT_CURRENT) + return ACTResult_InvalidValue; + LockedAccount account(accountSlot); + if (!account) + return ACTResult_AccountDoesNotExist; + if (!account->IsNetworkAccount()) + return ACTResult_NotANetworkAccount; + cemu_assert_debug(ActiveSettings::IsOnlineEnabled()); + if (tokenLen != sizeof(NexToken)) + return ACTResult_OutOfRange; + + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, titleId, titleVersion, serverId); + if (nexTokenResult.isValid()) + { + memcpy(tokenOut, &nexTokenResult.nexToken, sizeof(NexToken)); + return ACTResult_Ok; + } + else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR) + { + nnResult returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError); + cemu_assert_debug((returnCode&0x80000000) != 0); + return returnCode; + } + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); + } + + nnResult AcquireIndependentServiceToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, std::string_view clientId, uint8* tokenOut, uint32 tokenLen) + { + static constexpr size_t IndependentTokenMaxLength = 512+1; // 512 bytes + null terminator + if(accountSlot != ACT_SLOT_CURRENT) + return ACTResult_InvalidValue; + LockedAccount account(accountSlot); + if (!account) + return ACTResult_AccountDoesNotExist; + if (!account->IsNetworkAccount()) + return ACTResult_NotANetworkAccount; + cemu_assert_debug(ActiveSettings::IsOnlineEnabled()); + if (tokenLen < IndependentTokenMaxLength) + return ACTResult_OutOfRange; + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + account.Release(); + NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, titleId, titleVersion, clientId); + uint32 returnCode = 0; + if (tokenResult.isValid()) + { + for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)IndependentTokenMaxLength); i++) + { + tokenOut[i] = tokenResult.token[i]; + tokenOut[i + 1] = '\0'; + } + returnCode = 0; + } + else + { + returnCode = 0x80000000; // todo - proper error codes + } + return returnCode; + } + + class ActService : public iosu::nn::IPCService + { + public: + ActService() : iosu::nn::IPCService("/dev/act") {} + + nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + { + 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); + } + }; + + ActService gActService; + + void Initialize() + { + gActService.Start(); + } + + void Stop() + { + gActService.Stop(); + } + } +} + + +// IOSU act IO + +typedef struct +{ + /* +0x00 */ uint32be ukn00; + /* +0x04 */ uint32be ukn04; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be subcommandCode; + /* +0x10 */ uint8 ukn10; + /* +0x11 */ uint8 ukn11; + /* +0x12 */ uint8 ukn12; + /* +0x13 */ uint8 accountSlot; + /* +0x14 */ uint32be unique; // is this command specific? +}cmdActRequest00_t; + +typedef struct +{ + uint32be returnCode; + uint8 transferableIdBase[8]; +}cmdActGetTransferableIDResult_t; + +#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4 +#define ACT_SUBCMD_INITIALIZE 0x14 + +#define _cancelIfAccountDoesNotExist() \ +if (_actAccountData[accountIndex].isValid == false) \ +{ \ + /* account does not exist*/ \ + ioctlReturnValue = 0; \ + actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \ + actCemuRequest->resultU64.u64 = 0; \ + iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \ + continue; \ +} + int iosuAct_thread() { SetThreadName("iosuAct_thread"); @@ -674,47 +818,13 @@ int iosuAct_thread() } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIRENEXTOKEN) { - NAPI::AuthInfo authInfo; - NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); - NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId); - uint32 returnCode = 0; - if (nexTokenResult.isValid()) - { - *(NAPI::ACTNexToken*)actCemuRequest->resultBinary.binBuffer = nexTokenResult.nexToken; - returnCode = NN_RESULT_SUCCESS; - } - else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR) - { - returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError); - cemu_assert_debug((returnCode&0x80000000) != 0); - } - else - { - returnCode = nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); - } - actCemuRequest->setACTReturnCode(returnCode); + nnResult r = iosu::act::AcquireNexToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId, actCemuRequest->resultBinary.binBuffer, sizeof(NexToken)); + actCemuRequest->setACTReturnCode(r); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREINDEPENDENTTOKEN) { - NAPI::AuthInfo authInfo; - NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); - NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId); - - uint32 returnCode = 0; - if (tokenResult.isValid()) - { - for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)200); i++) - { - actCemuRequest->resultBinary.binBuffer[i] = tokenResult.token[i]; - actCemuRequest->resultBinary.binBuffer[i + 1] = '\0'; - } - returnCode = 0; - } - else - { - returnCode = 0x80000000; // todo - proper error codes - } - actCemuRequest->setACTReturnCode(returnCode); + nnResult r = iosu::act::AcquireIndependentServiceToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId, actCemuRequest->resultBinary.binBuffer, sizeof(actCemuRequest->resultBinary.binBuffer)); + actCemuRequest->setACTReturnCode(r); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREPIDBYNNID) { diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index d60966d..8ed408a 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -53,7 +53,7 @@ namespace iosu std::string getAccountId2(uint8 slot); - const uint8 ACT_SLOT_CURRENT = 0xFE; + static constexpr uint8 ACT_SLOT_CURRENT = 0xFE; void Initialize(); void Stop(); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index af53edd..f490ff1 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -114,6 +114,7 @@ namespace act { memset(token, 0, sizeof(independentServiceToken_t)); actPrepareRequest(); + actRequest->accountSlot = iosu::act::ACT_SLOT_CURRENT; actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; actRequest->titleId = CafeSystem::GetForegroundTitleId(); actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); @@ -611,6 +612,7 @@ void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) ppcDefineParamU32(serverId, 1); memset(token, 0, sizeof(nexServiceToken_t)); actPrepareRequest(); + actRequest->accountSlot = iosu::act::ACT_SLOT_CURRENT; actRequest->requestCode = IOSU_ARC_ACQUIRENEXTOKEN; actRequest->titleId = CafeSystem::GetForegroundTitleId(); actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); @@ -627,10 +629,8 @@ void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) void nnActExport_AcquireIndependentServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(token, independentServiceToken_t, 0); - ppcDefineParamMEMPTR(serviceToken, const char, 1); - 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()); + ppcDefineParamMEMPTR(clientId, const char, 1); + uint32 result = nn::act::AcquireIndependentServiceToken(token.GetPtr(), clientId.GetPtr(), 0); osLib_returnFromFunction(hCPU, result); } @@ -640,7 +640,6 @@ void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) ppcDefineParamMEMPTR(clientId, const char, 1); ppcDefineParamU32(cacheDurationInSeconds, 2); uint32 result = nn::act::AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); - cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireIndependentServiceToken2"); osLib_returnFromFunction(hCPU, result); } @@ -648,7 +647,6 @@ void nnActExport_AcquireEcServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(pEcServiceToken, independentServiceToken_t, 0); uint32 result = nn::act::AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); - cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireEcServiceToken"); osLib_returnFromFunction(hCPU, result); } From 7d6d4173549a55070683feac33afaad038383813 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Mon, 6 May 2024 02:27:30 +0100 Subject: [PATCH 094/130] Input: Improve setting of dpd_enable_fg (#1127) --- src/input/api/Controller.h | 2 ++ src/input/api/ControllerState.h | 6 ++++++ src/input/api/DSU/DSUController.cpp | 7 +++++++ src/input/api/DSU/DSUController.h | 1 + src/input/api/Wiimote/NativeWiimoteController.cpp | 5 +++++ src/input/api/Wiimote/NativeWiimoteController.h | 1 + src/input/api/Wiimote/WiimoteControllerProvider.cpp | 6 ++++++ src/input/api/Wiimote/WiimoteControllerProvider.h | 2 ++ src/input/emulated/EmulatedController.cpp | 11 +++++++++++ src/input/emulated/EmulatedController.h | 1 + src/input/emulated/WPADController.cpp | 12 +++++++++--- 11 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/input/api/Controller.h b/src/input/api/Controller.h index 8b1c8e6..e247519 100644 --- a/src/input/api/Controller.h +++ b/src/input/api/Controller.h @@ -2,6 +2,7 @@ #include "input/InputManager.h" #include "input/motion/MotionSample.h" +#include "input/api/ControllerState.h" namespace pugi { @@ -118,6 +119,7 @@ public: virtual bool has_position() { return false; } virtual glm::vec2 get_position() { return {}; } virtual glm::vec2 get_prev_position() { return {}; } + virtual PositionVisibility GetPositionVisibility() {return PositionVisibility::NONE;}; virtual bool has_rumble() { return false; } virtual void start_rumble() {} diff --git a/src/input/api/ControllerState.h b/src/input/api/ControllerState.h index ce79a1e..65bfec9 100644 --- a/src/input/api/ControllerState.h +++ b/src/input/api/ControllerState.h @@ -3,6 +3,12 @@ #include <glm/vec2.hpp> #include "util/helpers/fspinlock.h" +enum class PositionVisibility { + NONE = 0, + FULL = 1, + PARTIAL = 2 +}; + // helper class for storing and managing button press states in a thread-safe manner struct ControllerButtonState { diff --git a/src/input/api/DSU/DSUController.cpp b/src/input/api/DSU/DSUController.cpp index f134440..082f7e3 100644 --- a/src/input/api/DSU/DSUController.cpp +++ b/src/input/api/DSU/DSUController.cpp @@ -93,6 +93,13 @@ glm::vec2 DSUController::get_prev_position() return {}; } +PositionVisibility DSUController::GetPositionVisibility() +{ + const auto state = m_provider->get_prev_state(m_index); + + return (state.data.tpad1.active || state.data.tpad2.active) ? PositionVisibility::FULL : PositionVisibility::NONE; +} + std::string DSUController::get_button_name(uint64 button) const { switch (button) diff --git a/src/input/api/DSU/DSUController.h b/src/input/api/DSU/DSUController.h index 801f609..e6e2936 100644 --- a/src/input/api/DSU/DSUController.h +++ b/src/input/api/DSU/DSUController.h @@ -32,6 +32,7 @@ public: bool has_position() override; glm::vec2 get_position() override; glm::vec2 get_prev_position() override; + PositionVisibility GetPositionVisibility() override; std::string get_button_name(uint64 button) const override; diff --git a/src/input/api/Wiimote/NativeWiimoteController.cpp b/src/input/api/Wiimote/NativeWiimoteController.cpp index 3f9e82a..9aa56d9 100644 --- a/src/input/api/Wiimote/NativeWiimoteController.cpp +++ b/src/input/api/Wiimote/NativeWiimoteController.cpp @@ -98,6 +98,11 @@ glm::vec2 NativeWiimoteController::get_prev_position() const auto state = m_provider->get_state(m_index); return state.ir_camera.m_prev_position; } +PositionVisibility NativeWiimoteController::GetPositionVisibility() +{ + const auto state = m_provider->get_state(m_index); + return state.ir_camera.m_positionVisibility; +} bool NativeWiimoteController::has_low_battery() { diff --git a/src/input/api/Wiimote/NativeWiimoteController.h b/src/input/api/Wiimote/NativeWiimoteController.h index ed3caa0..8e9c077 100644 --- a/src/input/api/Wiimote/NativeWiimoteController.h +++ b/src/input/api/Wiimote/NativeWiimoteController.h @@ -40,6 +40,7 @@ public: bool has_position() override; glm::vec2 get_position() override; glm::vec2 get_prev_position() override; + PositionVisibility GetPositionVisibility() override; bool has_motion() override { return true; } bool has_rumble() override { return true; } diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 5aac3fe..c80f3fb 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -766,14 +766,20 @@ void WiimoteControllerProvider::calculate_ir_position(WiimoteState& wiimote_stat ir.middle = ir.position; ir.distance = glm::length(ir.dots[indices.first].pos - ir.dots[indices.second].pos); ir.indices = indices; + ir.m_positionVisibility = PositionVisibility::FULL; } else if (ir.dots[indices.first].visible) { ir.position = ir.middle + (ir.dots[indices.first].pos - ir.prev_dots[indices.first].pos); + ir.m_positionVisibility = PositionVisibility::PARTIAL; } else if (ir.dots[indices.second].visible) { ir.position = ir.middle + (ir.dots[indices.second].pos - ir.prev_dots[indices.second].pos); + ir.m_positionVisibility = PositionVisibility::PARTIAL; + } + else { + ir.m_positionVisibility = PositionVisibility::NONE; } } diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h index 40fe878..7629b64 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -5,6 +5,7 @@ #include "input/api/Wiimote/WiimoteMessages.h" #include "input/api/ControllerProvider.h" +#include "input/api/ControllerState.h" #include <list> #include <variant> @@ -61,6 +62,7 @@ public: std::array<IRDot, 4> dots{}, prev_dots{}; glm::vec2 position{}, m_prev_position{}; + PositionVisibility m_positionVisibility; glm::vec2 middle {}; float distance = 0; std::pair<sint32, sint32> indices{ 0,1 }; diff --git a/src/input/emulated/EmulatedController.cpp b/src/input/emulated/EmulatedController.cpp index e254db3..ad9b6ac 100644 --- a/src/input/emulated/EmulatedController.cpp +++ b/src/input/emulated/EmulatedController.cpp @@ -207,6 +207,17 @@ glm::vec2 EmulatedController::get_prev_position() const return {}; } +PositionVisibility EmulatedController::GetPositionVisibility() const +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + if (controller->has_position()) + return controller->GetPositionVisibility(); + } + return PositionVisibility::NONE; +} + void EmulatedController::add_controller(std::shared_ptr<ControllerBase> controller) { controller->connect(); diff --git a/src/input/emulated/EmulatedController.h b/src/input/emulated/EmulatedController.h index b7bd8c6..907be07 100644 --- a/src/input/emulated/EmulatedController.h +++ b/src/input/emulated/EmulatedController.h @@ -67,6 +67,7 @@ public: bool has_position() const; glm::vec2 get_position() const; glm::vec2 get_prev_position() const; + PositionVisibility GetPositionVisibility() const; void add_controller(std::shared_ptr<ControllerBase> controller); void remove_controller(const std::shared_ptr<ControllerBase>& controller); diff --git a/src/input/emulated/WPADController.cpp b/src/input/emulated/WPADController.cpp index 819596a..2eae0f8 100644 --- a/src/input/emulated/WPADController.cpp +++ b/src/input/emulated/WPADController.cpp @@ -1,3 +1,4 @@ +#include <api/Controller.h> #include "input/emulated/WPADController.h" #include "input/emulated/ClassicController.h" @@ -308,10 +309,13 @@ void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat) status.mpls.dir.Z.z = attitude[8]; } } - - if (has_position()) + auto visibility = GetPositionVisibility(); + if (has_position() && visibility != PositionVisibility::NONE) { - status.dpd_valid_fg = 1; + if (visibility == PositionVisibility::FULL) + status.dpd_valid_fg = 2; + else + status.dpd_valid_fg = -1; const auto position = get_position(); @@ -324,6 +328,8 @@ void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat) status.vec.y = delta.y; status.speed = glm::length(delta); } + else + status.dpd_valid_fg = 0; switch (type()) { From 065fb7eb58855ec2d8c009f2dfabc3e815b91915 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 6 May 2024 09:15:36 +0200 Subject: [PATCH 095/130] coreinit: Add reschedule special case to avoid a deadlock Fixes Just Dance 2019 locking up on boot --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 533360a..fbf498d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -763,6 +763,11 @@ namespace coreinit uint32 coreIndex = OSGetCoreId(); if (!newThread->context.hasCoreAffinitySet(coreIndex)) return false; + // special case: if current and new thread are running only on the same core then reschedule even if priority is equal + // this resolves a deadlock in Just Dance 2019 where one thread would always reacquire the same mutex within it's timeslice, blocking another thread on the same core from acquiring it + if ((1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) + return true; + // otherwise reschedule if new thread has higher priority return newThread->effectivePriority < currentThread->effectivePriority; } From 3f8722f0a6789065f709daa3d6a636e2334b3bad Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 6 May 2024 18:18:42 +0200 Subject: [PATCH 096/130] Track online-enable and network-service settings per-account instead of globally --- src/config/ActiveSettings.cpp | 12 ++- src/config/CemuConfig.cpp | 58 +++++++++++++- src/config/CemuConfig.h | 10 ++- src/config/NetworkSettings.cpp | 9 ++- src/config/NetworkSettings.h | 4 +- src/gui/GeneralSettings2.cpp | 137 ++++++++++++++++++++------------- src/gui/GeneralSettings2.h | 4 +- src/gui/MainWindow.cpp | 32 -------- 8 files changed, 164 insertions(+), 102 deletions(-) diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 81662ab..07e6f16 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -131,7 +131,12 @@ uint32 ActiveSettings::GetPersistentId() bool ActiveSettings::IsOnlineEnabled() { - return GetConfig().account.online_enabled && Account::GetAccount(GetPersistentId()).IsValidOnlineAccount() && HasRequiredOnlineFiles(); + if(!Account::GetAccount(GetPersistentId()).IsValidOnlineAccount()) + return false; + if(!HasRequiredOnlineFiles()) + return false; + NetworkService networkService = static_cast<NetworkService>(GetConfig().GetAccountNetworkService(GetPersistentId())); + return networkService == NetworkService::Nintendo || networkService == NetworkService::Pretendo || networkService == NetworkService::Custom; } bool ActiveSettings::HasRequiredOnlineFiles() @@ -139,8 +144,9 @@ bool ActiveSettings::HasRequiredOnlineFiles() return s_has_required_online_files; } -NetworkService ActiveSettings::GetNetworkService() { - return static_cast<NetworkService>(GetConfig().account.active_service.GetValue()); +NetworkService ActiveSettings::GetNetworkService() +{ + return GetConfig().GetAccountNetworkService(GetPersistentId()); } bool ActiveSettings::DumpShadersEnabled() diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index e4be97a..4f1736e 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -328,8 +328,22 @@ void CemuConfig::Load(XMLConfigParser& parser) // account auto acc = parser.get("Account"); account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); - account.online_enabled = acc.get("OnlineEnabled", account.online_enabled); - account.active_service = acc.get("ActiveService",account.active_service); + // legacy online settings, we only parse these for upgrading purposes + account.legacy_online_enabled = acc.get("OnlineEnabled", account.legacy_online_enabled); + account.legacy_active_service = acc.get("ActiveService",account.legacy_active_service); + // per-account online setting + auto accService = parser.get("AccountService"); + account.service_select.clear(); + for (auto element = accService.get("SelectedService"); element.valid(); element = accService.get("SelectedService", element)) + { + uint32 persistentId = element.get_attribute<uint32>("PersistentId", 0); + sint32 serviceIndex = element.get_attribute<sint32>("Service", 0); + NetworkService networkService = static_cast<NetworkService>(serviceIndex); + if (persistentId < Account::kMinPersistendId) + continue; + if(networkService == NetworkService::Offline || networkService == NetworkService::Nintendo || networkService == NetworkService::Pretendo || networkService == NetworkService::Custom) + account.service_select.emplace(persistentId, networkService); + } // debug auto debug = parser.get("Debug"); #if BOOST_OS_WINDOWS @@ -512,8 +526,17 @@ void CemuConfig::Save(XMLConfigParser& parser) // account auto acc = config.set("Account"); acc.set("PersistentId", account.m_persistent_id.GetValue()); - acc.set("OnlineEnabled", account.online_enabled.GetValue()); - acc.set("ActiveService",account.active_service.GetValue()); + // legacy online mode setting + acc.set("OnlineEnabled", account.legacy_online_enabled.GetValue()); + acc.set("ActiveService",account.legacy_active_service.GetValue()); + // per-account online setting + auto accService = config.set("AccountService"); + for(auto& it : account.service_select) + { + auto entry = accService.set("SelectedService"); + entry.set_attribute("PersistentId", it.first); + entry.set_attribute("Service", static_cast<sint32>(it.second)); + } // debug auto debug = config.set("Debug"); #if BOOST_OS_WINDOWS @@ -609,3 +632,30 @@ void CemuConfig::AddRecentNfcFile(std::string_view file) while (recent_nfc_files.size() > kMaxRecentEntries) recent_nfc_files.pop_back(); } + +NetworkService CemuConfig::GetAccountNetworkService(uint32 persistentId) +{ + auto it = account.service_select.find(persistentId); + if (it != account.service_select.end()) + { + NetworkService serviceIndex = it->second; + // make sure the returned service is valid + if (serviceIndex != NetworkService::Offline && + serviceIndex != NetworkService::Nintendo && + serviceIndex != NetworkService::Pretendo && + serviceIndex != NetworkService::Custom) + return NetworkService::Offline; + if( static_cast<NetworkService>(serviceIndex) == NetworkService::Custom && !NetworkConfig::XMLExists() ) + return NetworkService::Offline; // custom is selected but no custom config exists + return serviceIndex; + } + // if not found, return the legacy value + if(!account.legacy_online_enabled) + return NetworkService::Offline; + return static_cast<NetworkService>(account.legacy_active_service.GetValue() + 1); // +1 because "Offline" now takes index 0 +} + +void CemuConfig::SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex) +{ + account.service_select[persistentId] = serviceIndex; +} diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 9f1e798..cab7a1a 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -8,6 +8,8 @@ #include <wx/language.h> #include <wx/intl.h> +enum class NetworkService; + struct GameEntry { GameEntry() = default; @@ -483,8 +485,9 @@ struct CemuConfig struct { ConfigValueBounds<uint32> m_persistent_id{ Account::kMinPersistendId, Account::kMinPersistendId, 0xFFFFFFFF }; - ConfigValue<bool> online_enabled{false}; - ConfigValue<int> active_service{0}; + ConfigValue<bool> legacy_online_enabled{false}; + ConfigValue<int> legacy_active_service{0}; + std::unordered_map<uint32, NetworkService> service_select; // per-account service index. Key is persistentId }account{}; // input @@ -509,6 +512,9 @@ struct CemuConfig bool GetGameListCustomName(uint64 titleId, std::string& customName); void SetGameListCustomName(uint64 titleId, std::string customName); + NetworkService GetAccountNetworkService(uint32 persistentId); + void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); + private: GameEntry* GetGameEntryByTitleId(uint64 titleId); GameEntry* CreateGameEntry(uint64 titleId); diff --git a/src/config/NetworkSettings.cpp b/src/config/NetworkSettings.cpp index b086d0a..42dc999 100644 --- a/src/config/NetworkSettings.cpp +++ b/src/config/NetworkSettings.cpp @@ -34,14 +34,15 @@ void NetworkConfig::Load(XMLConfigParser& parser) bool NetworkConfig::XMLExists() { + static std::optional<bool> s_exists; // caches result of fs::exists + if(s_exists.has_value()) + return *s_exists; std::error_code ec; if (!fs::exists(ActiveSettings::GetConfigPath("network_services.xml"), ec)) { - if (static_cast<NetworkService>(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) - { - GetConfig().account.active_service = 0; - } + s_exists = false; return false; } + s_exists = true; return true; } \ No newline at end of file diff --git a/src/config/NetworkSettings.h b/src/config/NetworkSettings.h index be31118..6e114a0 100644 --- a/src/config/NetworkSettings.h +++ b/src/config/NetworkSettings.h @@ -5,9 +5,11 @@ enum class NetworkService { + Offline, Nintendo, Pretendo, - Custom + Custom, + COUNT = Custom }; struct NetworkConfig diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index dab3098..c0b5494 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -683,18 +683,6 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) content->Add(m_delete_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); m_delete_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountDelete, this); - wxString choices[] = { _("Nintendo"), _("Pretendo"), _("Custom") }; - m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 3, wxRA_SPECIFY_COLS); - if (!NetworkConfig::XMLExists()) - m_active_service->Enable(2, false); - - m_active_service->SetItemToolTip(0, _("Connect to the official Nintendo Network Service")); - m_active_service->SetItemToolTip(1, _("Connect to the Pretendo Network Service")); - m_active_service->SetItemToolTip(2, _("Connect to a custom Network Service (configured via network_services.xml)")); - - m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); - content->Add(m_active_service, 0, wxEXPAND | wxALL, 5); - box_sizer->Add(content, 1, wxEXPAND, 5); online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); @@ -704,17 +692,33 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) m_active_account->Enable(false); m_create_account->Enable(false); m_delete_account->Enable(false); + } + } + + + { + wxString choices[] = { _("Offline"), _("Nintendo"), _("Pretendo"), _("Custom") }; + m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 4, wxRA_SPECIFY_COLS); + if (!NetworkConfig::XMLExists()) + m_active_service->Enable(3, false); + + m_active_service->SetItemToolTip(0, _("Online functionality disabled for this account")); + m_active_service->SetItemToolTip(1, _("Connect to the official Nintendo Network Service")); + m_active_service->SetItemToolTip(2, _("Connect to the Pretendo Network Service")); + m_active_service->SetItemToolTip(3, _("Connect to a custom Network Service (configured via network_services.xml)")); + + m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); + online_panel_sizer->Add(m_active_service, 0, wxEXPAND | wxALL, 5); + + if (CafeSystem::IsTitleRunning()) + { m_active_service->Enable(false); } } - + { - auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online settings")); + auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online play requirements")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); - - m_online_enabled = new wxCheckBox(box, wxID_ANY, _("Enable online mode")); - m_online_enabled->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnOnlineEnable, this); - box_sizer->Add(m_online_enabled, 0, wxEXPAND | wxALL, 5); auto* row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); @@ -873,6 +877,14 @@ GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) DisableSettings(game_launched); } +uint32 GeneralSettings2::GetSelectedAccountPersistentId() +{ + const auto active_account = m_active_account->GetSelection(); + if (active_account == wxNOT_FOUND) + return GetConfig().account.m_persistent_id.GetInitValue(); + return dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); +} + void GeneralSettings2::StoreConfig() { auto* app = (CemuApp*)wxTheApp; @@ -1038,14 +1050,7 @@ void GeneralSettings2::StoreConfig() config.notification.friends = m_friends_data->GetValue(); // account - const auto active_account = m_active_account->GetSelection(); - if (active_account == wxNOT_FOUND) - config.account.m_persistent_id = config.account.m_persistent_id.GetInitValue(); - else - config.account.m_persistent_id = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); - - config.account.online_enabled = m_online_enabled->GetValue(); - config.account.active_service = m_active_service->GetSelection(); + config.account.m_persistent_id = GetSelectedAccountPersistentId(); // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); @@ -1371,14 +1376,13 @@ void GeneralSettings2::UpdateAccountInformation() { m_account_grid->SetSplitterPosition(100); - m_online_status->SetLabel(_("At least one issue has been found")); - const auto selection = m_active_account->GetSelection(); if(selection == wxNOT_FOUND) { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); ResetAccountInformation(); + m_online_status->SetLabel(_("No account selected")); return; } @@ -1404,11 +1408,26 @@ void GeneralSettings2::UpdateAccountInformation() index = 0; country_property->SetChoiceSelection(index); - const bool online_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); - if (online_valid) + const bool online_fully_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); + if (ActiveSettings::HasRequiredOnlineFiles()) + { + if(account.IsValidOnlineAccount()) + m_online_status->SetLabel(_("Selected account is a valid online account")); + else + m_online_status->SetLabel(_("Selected account is not linked to a NNID or PNID")); + } + else + { + if(NCrypto::OTP_IsPresent() != NCrypto::SEEPROM_IsPresent()) + m_online_status->SetLabel(_("OTP.bin or SEEPROM.bin is missing")); + else if(NCrypto::OTP_IsPresent() && NCrypto::SEEPROM_IsPresent()) + m_online_status->SetLabel(_("OTP and SEEPROM present but no certificate files were found")); + else + m_online_status->SetLabel(_("Online play is not set up. Follow the guide below to get started")); + } + + if(online_fully_valid) { - - m_online_status->SetLabel(_("Your account is a valid online account")); m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_CHECK_YES).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() | wxBORDER_NONE); } @@ -1417,7 +1436,28 @@ void GeneralSettings2::UpdateAccountInformation() m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); } - + + // enable/disable network service field depending on online requirements + m_active_service->Enable(online_fully_valid && !CafeSystem::IsTitleRunning()); + if(online_fully_valid) + { + NetworkService service = GetConfig().GetAccountNetworkService(account.GetPersistentId()); + m_active_service->SetSelection(static_cast<int>(service)); + // set the config option here for the selected service + // this will guarantee that it's actually written to settings.xml + // allowing us to eventually get rid of the legacy option in the (far) future + GetConfig().SetAccountSelectedService(account.GetPersistentId(), service); + } + else + { + m_active_service->SetSelection(0); // force offline + } + wxString tmp = _("Network service"); + tmp.append(" ("); + tmp.append(wxString::FromUTF8(boost::nowide::narrow(account.GetMiiName()))); + tmp.append(")"); + m_active_service->SetLabel(tmp); + // refresh pane size m_account_grid->InvalidateBestSize(); //m_account_grid->GetParent()->FitInside(); @@ -1663,9 +1703,8 @@ void GeneralSettings2::ApplyConfig() break; } } - - m_online_enabled->SetValue(config.account.online_enabled); - m_active_service->SetSelection(config.account.active_service); + m_active_service->SetSelection((int)config.GetAccountNetworkService(ActiveSettings::GetPersistentId())); + UpdateAccountInformation(); // debug @@ -1673,20 +1712,6 @@ void GeneralSettings2::ApplyConfig() m_gdb_port->SetValue(config.gdb_port.GetValue()); } -void GeneralSettings2::OnOnlineEnable(wxCommandEvent& event) -{ - event.Skip(); - if (!m_online_enabled->GetValue()) - return; - - // show warning if player enables online mode - const auto result = wxMessageBox(_("Please be aware that online mode lets you connect to OFFICIAL servers and therefore there is a risk of getting banned.\nOnly proceed if you are willing to risk losing online access with your Wii U and/or NNID."), - _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); - if (result == wxNO) - m_online_enabled->SetValue(false); -} - - void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event) { IAudioAPI::AudioAPI api; @@ -1952,6 +1977,9 @@ void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) { + auto& config = GetConfig(); + uint32 peristentId = GetSelectedAccountPersistentId(); + config.SetAccountSelectedService(peristentId, static_cast<NetworkService>(m_active_service->GetSelection())); UpdateAccountInformation(); } @@ -2005,12 +2033,12 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) err << _("The following error(s) have been found:") << '\n'; if (validator.otp == OnlineValidator::FileState::Missing) - err << _("otp.bin missing in Cemu root directory") << '\n'; + err << _("otp.bin missing in Cemu directory") << '\n'; else if(validator.otp == OnlineValidator::FileState::Corrupted) err << _("otp.bin is invalid") << '\n'; if (validator.seeprom == OnlineValidator::FileState::Missing) - err << _("seeprom.bin missing in Cemu root directory") << '\n'; + err << _("seeprom.bin missing in Cemu directory") << '\n'; else if(validator.seeprom == OnlineValidator::FileState::Corrupted) err << _("seeprom.bin is invalid") << '\n'; @@ -2045,9 +2073,10 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) { - switch (error) { + switch (error) + { case OnlineAccountError::kNoAccountId: - return _("AccountId missing (The account is not connected to a NNID)"); + return _("AccountId missing (The account is not connected to a NNID/PNID)"); case OnlineAccountError::kNoPasswordCached: return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kPasswordCacheEmpty: diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index 2846af3..b34c922 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -71,7 +71,6 @@ private: wxButton* m_create_account, * m_delete_account; wxChoice* m_active_account; wxRadioBox* m_active_service; - wxCheckBox* m_online_enabled; wxCollapsiblePane* m_account_information; wxPropertyGrid* m_account_grid; wxBitmapButton* m_validate_online; @@ -99,10 +98,11 @@ private: void OnMLCPathSelect(wxCommandEvent& event); void OnMLCPathChar(wxKeyEvent& event); void OnShowOnlineValidator(wxCommandEvent& event); - void OnOnlineEnable(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); + uint32 GetSelectedAccountPersistentId(); + // updates cemu audio devices void UpdateAudioDevice(); // refreshes audio device list for dropdown diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c34c547..097d506 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -948,38 +948,6 @@ void MainWindow::OnAccountSelect(wxCommandEvent& event) g_config.Save(); } -//void MainWindow::OnConsoleRegion(wxCommandEvent& event) -//{ -// switch (event.GetId()) -// { -// case MAINFRAME_MENU_ID_OPTIONS_REGION_AUTO: -// GetConfig().console_region = ConsoleRegion::Auto; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_JPN: -// GetConfig().console_region = ConsoleRegion::JPN; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_USA: -// GetConfig().console_region = ConsoleRegion::USA; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_EUR: -// GetConfig().console_region = ConsoleRegion::EUR; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_CHN: -// GetConfig().console_region = ConsoleRegion::CHN; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_KOR: -// GetConfig().console_region = ConsoleRegion::KOR; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_TWN: -// GetConfig().console_region = ConsoleRegion::TWN; -// break; -// default: -// cemu_assert_debug(false); -// } -// -// g_config.Save(); -//} - void MainWindow::OnConsoleLanguage(wxCommandEvent& event) { switch (event.GetId()) From 10d553e1c9ba0b669ee8d4543741eea14725ce24 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Tue, 7 May 2024 11:56:28 +0200 Subject: [PATCH 097/130] zlib125: Implement `deflateInit_` (#1194) --- src/Cafe/OS/libs/zlib125/zlib125.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Cafe/OS/libs/zlib125/zlib125.cpp b/src/Cafe/OS/libs/zlib125/zlib125.cpp index 25df6a9..aec6e8c 100644 --- a/src/Cafe/OS/libs/zlib125/zlib125.cpp +++ b/src/Cafe/OS/libs/zlib125/zlib125.cpp @@ -213,6 +213,32 @@ void zlib125Export_inflateReset2(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, r); } +void zlib125Export_deflateInit_(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(level, 1); + ppcDefineParamStr(version, 2); + ppcDefineParamS32(streamsize, 3); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs, false); + + // setup internal memory allocator if requested + if (zstream->zalloc == nullptr) + zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); + if (zstream->zfree == nullptr) + zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); + + if (streamsize != sizeof(z_stream_ppc2)) + assert_dbg(); + + sint32 r = deflateInit_(&hzs, level, version, sizeof(z_stream)); + + zlib125_setupUpdateZStream(&hzs, zstream); + + osLib_returnFromFunction(hCPU, r); +} + void zlib125Export_deflateInit2_(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); @@ -345,6 +371,7 @@ namespace zlib osLib_addFunction("zlib125", "inflateReset", zlib125Export_inflateReset); osLib_addFunction("zlib125", "inflateReset2", zlib125Export_inflateReset2); + osLib_addFunction("zlib125", "deflateInit_", zlib125Export_deflateInit_); osLib_addFunction("zlib125", "deflateInit2_", zlib125Export_deflateInit2_); osLib_addFunction("zlib125", "deflateBound", zlib125Export_deflateBound); osLib_addFunction("zlib125", "deflate", zlib125Export_deflate); From b2a6cccc89fd42b63bb718c8e9743cb52fca9008 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Thu, 9 May 2024 12:12:34 +0200 Subject: [PATCH 098/130] nn_act: Implement GetTransferableId (#1197) --- src/Cafe/OS/libs/nn_act/nn_act.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index f490ff1..f9e7435 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -308,6 +308,22 @@ void nnActExport_GetPrincipalIdEx(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); // ResultSuccess } +void nnActExport_GetTransferableId(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(unique, 0); + + cemuLog_logDebug(LogType::Force, "nn_act.GetTransferableId(0x{:08x})", hCPU->gpr[3]); + + uint64 transferableId; + uint32 r = nn::act::GetTransferableIdEx(&transferableId, unique, iosu::act::ACT_SLOT_CURRENT); + if (NN_RESULT_IS_FAILURE(r)) + { + transferableId = 0; + } + + osLib_returnFromFunction64(hCPU, _swapEndianU64(transferableId)); +} + void nnActExport_GetTransferableIdEx(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(transferableId, uint64, 0); @@ -691,6 +707,7 @@ void nnAct_load() osLib_addFunction("nn_act", "GetPrincipalId__Q2_2nn3actFv", nnActExport_GetPrincipalId); osLib_addFunction("nn_act", "GetPrincipalIdEx__Q2_2nn3actFPUiUc", nnActExport_GetPrincipalIdEx); // transferable id + osLib_addFunction("nn_act", "GetTransferableId__Q2_2nn3actFUi", nnActExport_GetTransferableId); osLib_addFunction("nn_act", "GetTransferableIdEx__Q2_2nn3actFPULUiUc", nnActExport_GetTransferableIdEx); // persistent id osLib_addFunction("nn_act", "GetPersistentId__Q2_2nn3actFv", nnActExport_GetPersistentId); From 97d8cf4ba330ed671a9b40d8aaab740d7bcbeffb Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Fri, 10 May 2024 09:32:06 +0200 Subject: [PATCH 099/130] vcpkg: Update libraries (#1198) --- dependencies/vcpkg | 2 +- vcpkg.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/vcpkg b/dependencies/vcpkg index 53bef89..cbf4a66 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit 53bef8994c541b6561884a8395ea35715ece75db +Subproject commit cbf4a6641528cee6f172328984576f51698de726 diff --git a/vcpkg.json b/vcpkg.json index 48742b4..b27a709 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "cemu", "version-string": "1.0", - "builtin-baseline": "53bef8994c541b6561884a8395ea35715ece75db", + "builtin-baseline": "cbf4a6641528cee6f172328984576f51698de726", "dependencies": [ "pugixml", "zlib", From cf41c3b136ab7272e6801991d081c9d2c69c7143 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 10 May 2024 09:33:32 +0200 Subject: [PATCH 100/130] CI: Use submodule commit of vcpkg --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d188b4a..a2342c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,6 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} @@ -138,7 +137,6 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} @@ -218,7 +216,6 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git pull --all - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} From 13b90874f9934f0a79a9ab2b9c4e1288ed2e6764 Mon Sep 17 00:00:00 2001 From: splatoon1enjoyer <131005903+splatoon1enjoyer@users.noreply.github.com> Date: Mon, 13 May 2024 14:52:25 +0000 Subject: [PATCH 101/130] Fix commas edge case in strings when parsing an assembly line (#1201) --- src/Cemu/PPCAssembler/ppcAssembler.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Cemu/PPCAssembler/ppcAssembler.cpp b/src/Cemu/PPCAssembler/ppcAssembler.cpp index 5bab7b8..df20b21 100644 --- a/src/Cemu/PPCAssembler/ppcAssembler.cpp +++ b/src/Cemu/PPCAssembler/ppcAssembler.cpp @@ -2418,6 +2418,9 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* _ppcAssembler_translateAlias(instructionName); // parse operands internalInfo.listOperandStr.clear(); + + bool isInString = false; + while (currentPtr < endPtr) { currentPtr++; @@ -2425,7 +2428,10 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* // find end of operand while (currentPtr < endPtr) { - if (*currentPtr == ',') + if (*currentPtr == '"') + isInString=!isInString; + + if (*currentPtr == ',' && !isInString) break; currentPtr++; } From 84e78088fb2d3d25797032fe963967aa2d1b5af0 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 4 May 2024 14:46:12 +0200 Subject: [PATCH 102/130] PPCCoreCallback: Add support for stack args if GPR limit is reached --- src/Cafe/HW/Espresso/PPCCallback.h | 37 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/Cafe/HW/Espresso/PPCCallback.h b/src/Cafe/HW/Espresso/PPCCallback.h index 19fcd4d..3d5393b 100644 --- a/src/Cafe/HW/Espresso/PPCCallback.h +++ b/src/Cafe/HW/Espresso/PPCCallback.h @@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t { sint32 gprCount = 0; sint32 floatCount = 0; + sint32 stackCount = 0; }; +inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value) +{ + if (data.gprCount < 8) + { + hCPU->gpr[3 + data.gprCount] = value; + data.gprCount++; + } + else + { + uint32 stackOffset = 8 + data.stackCount * 4; + + // PPCCore_executeCallbackInternal does -16*4 to save the current stack area + stackOffset -= 16 * 4; + + memory_writeU32(hCPU->gpr[1] + stackOffset, value); + data.stackCount++; + } +} + // callback functions inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) { @@ -16,23 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) template <typename T, typename... TArgs> uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) { - cemu_assert_debug(data.gprCount <= 8); - cemu_assert_debug(data.floatCount <= 8); + // TODO float arguments on stack + cemu_assert_debug(data.floatCount < 8); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if constexpr (std::is_pointer_v<T>) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR()); } else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>) { - hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR()); } else if constexpr (std::is_reference_v<T>) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR()); } else if constexpr(std::is_enum_v<T>) { @@ -53,8 +71,7 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, } else { - hCPU->gpr[3 + data.gprCount] = (uint32)currentArg; - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg); } return PPCCoreCallback(function, data, args...); From 1c6b209692953bcf5a958499ba3ebba0e24d5c6f Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 4 May 2024 14:49:23 +0200 Subject: [PATCH 103/130] Add initial ntag and nfc implementation --- src/Cafe/CMakeLists.txt | 14 + src/Cafe/CafeSystem.cpp | 6 + src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp | 406 +++++++++++++++++ src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h | 31 ++ src/Cafe/OS/libs/nfc/TLV.cpp | 139 ++++++ src/Cafe/OS/libs/nfc/TLV.h | 37 ++ src/Cafe/OS/libs/nfc/TagV0.cpp | 301 +++++++++++++ src/Cafe/OS/libs/nfc/TagV0.h | 39 ++ src/Cafe/OS/libs/nfc/ndef.cpp | 277 ++++++++++++ src/Cafe/OS/libs/nfc/ndef.h | 88 ++++ src/Cafe/OS/libs/nfc/nfc.cpp | 596 +++++++++++++++++++++++++ src/Cafe/OS/libs/nfc/nfc.h | 62 +++ src/Cafe/OS/libs/nfc/stream.cpp | 201 +++++++++ src/Cafe/OS/libs/nfc/stream.h | 139 ++++++ src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp | 83 ++-- src/Cafe/OS/libs/nn_nfp/nn_nfp.h | 8 +- src/Cafe/OS/libs/ntag/ntag.cpp | 438 ++++++++++++++++++ src/Cafe/OS/libs/ntag/ntag.h | 94 ++++ src/Cemu/Logging/CemuLogging.cpp | 2 + src/Cemu/Logging/CemuLogging.h | 3 + src/gui/MainWindow.cpp | 10 +- 21 files changed, 2927 insertions(+), 47 deletions(-) create mode 100644 src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp create mode 100644 src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h create mode 100644 src/Cafe/OS/libs/nfc/TLV.cpp create mode 100644 src/Cafe/OS/libs/nfc/TLV.h create mode 100644 src/Cafe/OS/libs/nfc/TagV0.cpp create mode 100644 src/Cafe/OS/libs/nfc/TagV0.h create mode 100644 src/Cafe/OS/libs/nfc/ndef.cpp create mode 100644 src/Cafe/OS/libs/nfc/ndef.h create mode 100644 src/Cafe/OS/libs/nfc/nfc.cpp create mode 100644 src/Cafe/OS/libs/nfc/nfc.h create mode 100644 src/Cafe/OS/libs/nfc/stream.cpp create mode 100644 src/Cafe/OS/libs/nfc/stream.h create mode 100644 src/Cafe/OS/libs/ntag/ntag.cpp create mode 100644 src/Cafe/OS/libs/ntag/ntag.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 851854f..b5090dc 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -218,6 +218,8 @@ add_library(CemuCafe HW/SI/SI.cpp HW/SI/si.h HW/VI/VI.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.h IOSU/fsa/fsa_types.h IOSU/fsa/iosu_fsa.cpp IOSU/fsa/iosu_fsa.h @@ -378,6 +380,16 @@ add_library(CemuCafe OS/libs/h264_avc/parser/H264Parser.h OS/libs/mic/mic.cpp OS/libs/mic/mic.h + OS/libs/nfc/ndef.cpp + OS/libs/nfc/ndef.h + OS/libs/nfc/nfc.cpp + OS/libs/nfc/nfc.h + OS/libs/nfc/stream.cpp + OS/libs/nfc/stream.h + OS/libs/nfc/TagV0.cpp + OS/libs/nfc/TagV0.h + OS/libs/nfc/TLV.cpp + OS/libs/nfc/TLV.h OS/libs/nlibcurl/nlibcurl.cpp OS/libs/nlibcurl/nlibcurlDebug.hpp OS/libs/nlibcurl/nlibcurl.h @@ -453,6 +465,8 @@ add_library(CemuCafe OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.h + OS/libs/ntag/ntag.cpp + OS/libs/ntag/ntag.h OS/libs/padscore/padscore.cpp OS/libs/padscore/padscore.h OS/libs/proc_ui/proc_ui.cpp diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 3c62a68..958a5a5 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -35,6 +35,7 @@ #include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" @@ -51,6 +52,8 @@ #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/mic/mic.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/ntag/ntag.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" @@ -533,6 +536,7 @@ namespace CafeSystem iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), + iosu::ccr_nfc::GetModule(), }; // initialize all subsystems which are persistent and don't depend on a game running @@ -587,6 +591,8 @@ namespace CafeSystem H264::Initialize(); snd_core::Initialize(); mic::Initialize(); + nfc::Initialize(); + ntag::Initialize(); // init hardware register interfaces HW_SI::Initialize(); } diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp new file mode 100644 index 0000000..ff8ba2b --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp @@ -0,0 +1,406 @@ +#include "iosu_ccr_nfc.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "util/crypto/aes128.h" +#include <openssl/evp.h> +#include <openssl/hmac.h> + +namespace iosu +{ + namespace ccr_nfc + { + IOSMsgQueueId sCCRNFCMsgQueue; + SysAllocator<iosu::kernel::IOSMessage, 0x20> sCCRNFCMsgQueueMsgBuffer; + std::thread sCCRNFCThread; + + constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 }; + constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB }; + + constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; + constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; + constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 }; + constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 }; + + constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; + constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; + + uint8 sLockedSecretInternalKey[0x10]; + uint8 sLockedSecretInternalNonce[0x10]; + uint8 sLockedSecretInternalHmacKey[0x10]; + + uint8 sUnfixedInfosInternalKey[0x10]; + uint8 sUnfixedInfosInternalNonce[0x10]; + uint8 sUnfixedInfosInternalHmacKey[0x10]; + + sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets) + { + if (!data) + { + return CCR_NFC_ERROR; + } + + if (size != sizeof(CCRNFCCryptData)) + { + return CCR_NFC_ERROR; + } + + if (!validateOffsets) + { + return 0; + } + + // Make sure all offsets are within bounds + if (data->version == 0) + { + if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 && + data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 && + data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9) + { + return 0; + } + } + else if (data->version == 2) + { + if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D && + data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D && + data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D) + { + return 0; + } + } + + return CCR_NFC_ERROR; + } + + sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32_t inSize, void* outData, uint32_t outSize) + { + uint8_t tmpIv[0x10]; + memcpy(tmpIv, ivNonce, sizeof(tmpIv)); + + memcpy(outData, inData, inSize); + AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv); + return 0; + } + + sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32_t nameSize, const uint8* inData, uint32_t inSize, uint8* outData, uint32_t outSize) + { + if (nameSize != 0xe || outSize != 0x40) + { + return CCR_NFC_ERROR; + } + + // Create a buffer containing 2 counter bytes, the key name, and the key data + uint8_t buffer[0x50]; + buffer[0] = 0; + buffer[1] = 0; + memcpy(buffer + 2, name, nameSize); + memcpy(buffer + nameSize + 2, inData, inSize); + + uint16_t counter = 0; + while (outSize > 0) + { + // Set counter bytes and increment counter + buffer[0] = (counter >> 8) & 0xFF; + buffer[1] = counter & 0xFF; + counter++; + + uint32 dataSize = outSize; + if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize)) + { + return CCR_NFC_ERROR; + } + + outSize -= 0x20; + outData += 0x20; + } + + return 0; + } + + sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) + { + uint8_t lockedSecretBuffer[0x40] = { 0 }; + uint8_t unfixedInfosBuffer[0x40] = { 0 }; + uint8_t outBuffer[0x40] = { 0 }; + + // Fill the locked secret buffer + memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sLockedSecretInternalKey, outBuffer, 0x10); + memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10); + + // Fill the unfixed infos buffer + memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2); + memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10); + memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10); + + return 0; + } + + sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) + { + // Decrypt key generation salt + uint8_t keyGenSalt[0x20]; + sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); + if (res != 0) + { + return res; + } + + // Prepare internal keys + res = __CCRNFCGenerateInternalKeys(in, keyGenSalt); + if (res != 0) + { + return res; + } + + if (decrypt) + { + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + + // Decrypt unfxied infos + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + + // Verify HMACs + uint8_t hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + if (in->version == 0) + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + else + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_UNFIXED_INFOS; + } + } + else + { + uint8_t hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + else + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + } + + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + } + + return res; + } + + void CCRNFCThread() + { + iosu::kernel::IOSMessage msg; + while (true) + { + IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0); + cemu_assert(!IOS_ResultIsError(error)); + + // Check for system exit + if (msg == 0xf00dd00d) + { + return; + } + + IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) + { + sint32 result; + uint32 requestId = cmd->args[0]; + void* ptrIn = MEMPTR<void>(cmd->args[1]); + uint32 sizeIn = cmd->args[2]; + void* ptrOut = MEMPTR<void>(cmd->args[3]); + uint32 sizeOut = cmd->args[4]; + + if ((result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrIn), sizeIn, true)) == 0 && + (result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrOut), sizeOut, false)) == 0) + { + // Initialize outData with inData + memcpy(ptrOut, ptrIn, sizeIn); + + switch (requestId) + { + case 1: // encrypt + result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), false); + break; + case 2: // decrypt + result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), true); + break; + default: + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId"); + cemu_assert_suspicious(); + result = IOS_ERROR_INVALID; + break; + } + } + + iosu::kernel::IOS_ResourceReply(cmd, static_cast<IOS_ERROR>(result)); + } + else + { + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId"); + cemu_assert_suspicious(); + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + } + + class : public ::IOSUModule + { + void SystemLaunch() override + { + sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount()); + cemu_assert(!IOS_ResultIsError(static_cast<IOS_ERROR>(sCCRNFCMsgQueue))); + + IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue); + cemu_assert(!IOS_ResultIsError(error)); + + sCCRNFCThread = std::thread(CCRNFCThread); + } + + void SystemExit() override + { + if (sCCRNFCMsgQueue < 0) + { + return; + } + + iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0); + sCCRNFCThread.join(); + + iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue); + sCCRNFCMsgQueue = -1; + } + } sIOSUModuleCCRNFC; + + IOSUModule* GetModule() + { + return &sIOSUModuleCCRNFC; + } + } +} diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h new file mode 100644 index 0000000..ae99d64 --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h @@ -0,0 +1,31 @@ +#pragma once +#include "Cafe/IOSU/iosu_types_common.h" + +#define CCR_NFC_ERROR (-0x2F001E) +#define CCR_NFC_INVALID_LOCKED_SECRET (-0x2F0029) +#define CCR_NFC_INVALID_UNFIXED_INFOS (-0x2F002A) + +namespace iosu +{ + namespace ccr_nfc + { + struct CCRNFCCryptData + { + uint32 version; + uint32 dataSize; + uint32 seedOffset; + uint32 keyGenSaltOffset; + uint32 uuidOffset; + uint32 unfixedInfosOffset; + uint32 unfixedInfosSize; + uint32 lockedSecretOffset; + uint32 lockedSecretSize; + uint32 unfixedInfosHmacOffset; + uint32 lockedSecretHmacOffset; + uint8 data[540]; + }; + static_assert(sizeof(CCRNFCCryptData) == 0x248); + + IOSUModule* GetModule(); + } +} diff --git a/src/Cafe/OS/libs/nfc/TLV.cpp b/src/Cafe/OS/libs/nfc/TLV.cpp new file mode 100644 index 0000000..9953642 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.cpp @@ -0,0 +1,139 @@ +#include "TLV.h" +#include "stream.h" + +#include <cassert> + +TLV::TLV() +{ +} + +TLV::TLV(Tag tag, std::vector<std::byte> value) + : mTag(tag), mValue(std::move(value)) +{ +} + +TLV::~TLV() +{ +} + +std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) +{ + bool hasTerminator = false; + std::vector<TLV> tlvs; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0 && !hasTerminator) + { + // Read the tag + uint8_t byte; + stream >> byte; + Tag tag = static_cast<Tag>(byte); + + switch (tag) + { + case TLV::TAG_NULL: + // Don't need to do anything for NULL tags + break; + + case TLV::TAG_TERMINATOR: + tlvs.emplace_back(tag, std::vector<std::byte>{}); + hasTerminator = true; + break; + + default: + { + // Read the length + uint16_t length; + stream >> byte; + length = byte; + + // If the length is 0xff, 2 bytes with length follow + if (length == 0xff) { + stream >> length; + } + + std::vector<std::byte> value; + value.resize(length); + stream.Read(value); + + tlvs.emplace_back(tag, value); + break; + } + } + + if (stream.GetError() != Stream::ERROR_OK) + { + cemuLog_log(LogType::Force, "Error: TLV parsing read past end of stream"); + // Clear tlvs to prevent further havoc while parsing ndef data + tlvs.clear(); + break; + } + } + + // This seems to be okay, at least NTAGs don't add a terminator tag + // if (!hasTerminator) + // { + // cemuLog_log(LogType::Force, "Warning: TLV parsing reached end of stream without terminator tag"); + // } + + return tlvs; +} + +std::vector<std::byte> TLV::ToBytes() const +{ + std::vector<std::byte> bytes; + VectorStream stream(bytes, std::endian::big); + + // Write tag + stream << std::uint8_t(mTag); + + switch (mTag) + { + case TLV::TAG_NULL: + case TLV::TAG_TERMINATOR: + // Nothing to do here + break; + + default: + { + // Write length (decide if as a 8-bit or 16-bit value) + if (mValue.size() >= 0xff) + { + stream << std::uint8_t(0xff); + stream << std::uint16_t(mValue.size()); + } + else + { + stream << std::uint8_t(mValue.size()); + } + + // Write value + stream.Write(mValue); + } + } + + return bytes; +} + +TLV::Tag TLV::GetTag() const +{ + return mTag; +} + +const std::vector<std::byte>& TLV::GetValue() const +{ + return mValue; +} + +void TLV::SetTag(Tag tag) +{ + mTag = tag; +} + +void TLV::SetValue(const std::span<const std::byte>& value) +{ + // Can only write max 16-bit lengths into TLV + cemu_assert(value.size() < 0x10000); + + mValue.assign(value.begin(), value.end()); +} diff --git a/src/Cafe/OS/libs/nfc/TLV.h b/src/Cafe/OS/libs/nfc/TLV.h new file mode 100644 index 0000000..f582128 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.h @@ -0,0 +1,37 @@ +#pragma once + +#include <cstdint> +#include <span> +#include <vector> + +class TLV +{ +public: + enum Tag + { + TAG_NULL = 0x00, + TAG_LOCK_CTRL = 0x01, + TAG_MEM_CTRL = 0x02, + TAG_NDEF = 0x03, + TAG_PROPRIETARY = 0xFD, + TAG_TERMINATOR = 0xFE, + }; + +public: + TLV(); + TLV(Tag tag, std::vector<std::byte> value); + virtual ~TLV(); + + static std::vector<TLV> FromBytes(const std::span<std::byte>& data); + std::vector<std::byte> ToBytes() const; + + Tag GetTag() const; + const std::vector<std::byte>& GetValue() const; + + void SetTag(Tag tag); + void SetValue(const std::span<const std::byte>& value); + +private: + Tag mTag; + std::vector<std::byte> mValue; +}; diff --git a/src/Cafe/OS/libs/nfc/TagV0.cpp b/src/Cafe/OS/libs/nfc/TagV0.cpp new file mode 100644 index 0000000..8b5a814 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.cpp @@ -0,0 +1,301 @@ +#include "TagV0.h" +#include "TLV.h" + +#include <algorithm> + +namespace +{ + +constexpr std::size_t kTagSize = 512u; +constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); + +constexpr std::uint8_t kLockbyteBlock0 = 0xe; +constexpr std::uint8_t kLockbytesStart0 = 0x0; +constexpr std::uint8_t kLockbytesEnd0 = 0x2; +constexpr std::uint8_t kLockbyteBlock1 = 0xf; +constexpr std::uint8_t kLockbytesStart1 = 0x2; +constexpr std::uint8_t kLockbytesEnd1 = 0x8; + +constexpr std::uint8_t kNDEFMagicNumber = 0xe1; + +// These blocks are not part of the locked area +constexpr bool IsBlockLockedOrReserved(std::uint8_t blockIdx) +{ + // Block 0 is the UID + if (blockIdx == 0x0) + { + return true; + } + + // Block 0xd is reserved + if (blockIdx == 0xd) + { + return true; + } + + // Block 0xe and 0xf contains lock / reserved bytes + if (blockIdx == 0xe || blockIdx == 0xf) + { + return true; + } + + return false; +} + +} // namespace + +TagV0::TagV0() +{ +} + +TagV0::~TagV0() +{ +} + +std::shared_ptr<TagV0> TagV0::FromBytes(const std::span<const std::byte>& data) +{ + // Version 0 tags need at least 512 bytes + if (data.size() != kTagSize) + { + cemuLog_log(LogType::Force, "Error: Version 0 tags should be {} bytes in size", kTagSize); + return {}; + } + + std::shared_ptr<TagV0> tag = std::make_shared<TagV0>(); + + // Parse the locked area before continuing + if (!tag->ParseLockedArea(data)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse locked area"); + return {}; + } + + // Now that the locked area is known, parse the data area + std::vector<std::byte> dataArea; + if (!tag->ParseDataArea(data, dataArea)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse data area"); + return {}; + } + + // The first few bytes in the dataArea make up the capability container + std::copy_n(dataArea.begin(), tag->mCapabilityContainer.size(), std::as_writable_bytes(std::span(tag->mCapabilityContainer)).begin()); + if (!tag->ValidateCapabilityContainer()) + { + cemuLog_log(LogType::Force, "Error: Failed to validate capability container"); + return {}; + } + + // The rest of the dataArea contains the TLVs + tag->mTLVs = TLV::FromBytes(std::span(dataArea).subspan(tag->mCapabilityContainer.size())); + if (tag->mTLVs.empty()) + { + cemuLog_log(LogType::Force, "Error: Tag contains no TLVs"); + return {}; + } + + // Look for the NDEF tlv + tag->mNdefTlvIdx = static_cast<size_t>(-1); + for (std::size_t i = 0; i < tag->mTLVs.size(); i++) + { + if (tag->mTLVs[i].GetTag() == TLV::TAG_NDEF) + { + tag->mNdefTlvIdx = i; + break; + } + } + + if (tag->mNdefTlvIdx == static_cast<size_t>(-1)) + { + cemuLog_log(LogType::Force, "Error: Tag contains no NDEF TLV"); + return {}; + } + + // Append locked data + for (const auto& [key, value] : tag->mLockedBlocks) + { + tag->mLockedArea.insert(tag->mLockedArea.end(), value.begin(), value.end()); + } + + return tag; +} + +std::vector<std::byte> TagV0::ToBytes() const +{ + std::vector<std::byte> bytes(kTagSize); + + // Insert locked or reserved blocks + for (const auto& [key, value] : mLockedOrReservedBlocks) + { + std::copy(value.begin(), value.end(), bytes.begin() + key * sizeof(Block)); + } + + // Insert locked area + auto lockedDataIterator = mLockedArea.begin(); + for (const auto& [key, value] : mLockedBlocks) + { + std::copy_n(lockedDataIterator, sizeof(Block), bytes.begin() + key * sizeof(Block)); + lockedDataIterator += sizeof(Block); + } + + // Pack the dataArea into a linear buffer + std::vector<std::byte> dataArea; + const auto ccBytes = std::as_bytes(std::span(mCapabilityContainer)); + dataArea.insert(dataArea.end(), ccBytes.begin(), ccBytes.end()); + for (const TLV& tlv : mTLVs) + { + const auto tlvBytes = tlv.ToBytes(); + dataArea.insert(dataArea.end(), tlvBytes.begin(), tlvBytes.end()); + } + + // Make sure the dataArea is block size aligned + dataArea.resize((dataArea.size() + (sizeof(Block)-1)) & ~(sizeof(Block)-1)); + + // The rest will be the data area + auto dataIterator = dataArea.begin(); + for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + std::copy_n(dataIterator, sizeof(Block), bytes.begin() + currentBlock * sizeof(Block)); + dataIterator += sizeof(Block); + } + } + + return bytes; +} + +const TagV0::Block& TagV0::GetUIDBlock() const +{ + return mLockedOrReservedBlocks.at(0); +} + +const std::vector<std::byte>& TagV0::GetNDEFData() const +{ + return mTLVs[mNdefTlvIdx].GetValue(); +} + +const std::vector<std::byte>& TagV0::GetLockedArea() const +{ + return mLockedArea; +} + +void TagV0::SetNDEFData(const std::span<const std::byte>& data) +{ + // Update the ndef value + mTLVs[mNdefTlvIdx].SetValue(data); +} + +bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) +{ + std::uint8_t currentBlock = 0; + + // Start by parsing the first set of lock bytes + for (std::uint8_t i = kLockbytesStart0; i < kLockbytesEnd0; i++) + { + std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock0 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (std::uint8_t j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + // Parse the second set of lock bytes + for (std::uint8_t i = kLockbytesStart1; i < kLockbytesEnd1; i++) { + std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock1 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (std::uint8_t j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + return true; +} + +bool TagV0::IsBlockLocked(std::uint8_t blockIdx) const +{ + return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); +} + +bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea) +{ + for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + auto blockOffset = data.begin() + sizeof(Block) * currentBlock; + dataArea.insert(dataArea.end(), blockOffset, blockOffset + sizeof(Block)); + } + } + + return true; +} + +bool TagV0::ValidateCapabilityContainer() +{ + // NDEF Magic Number + std::uint8_t nmn = mCapabilityContainer[0]; + if (nmn != kNDEFMagicNumber) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); + return false; + } + + // Version Number + std::uint8_t vno = mCapabilityContainer[1]; + if (vno >> 4 != 1) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); + return false; + } + + // Tag memory size + std::uint8_t tms = mCapabilityContainer[2]; + if (8u * (tms + 1) < kTagSize) + { + cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); + return false; + } + + return true; +} diff --git a/src/Cafe/OS/libs/nfc/TagV0.h b/src/Cafe/OS/libs/nfc/TagV0.h new file mode 100644 index 0000000..1d0e88d --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.h @@ -0,0 +1,39 @@ +#pragma once + +#include <memory> +#include <span> +#include <map> + +#include "TLV.h" + +class TagV0 +{ +public: + using Block = std::array<std::byte, 0x8>; + +public: + TagV0(); + virtual ~TagV0(); + + static std::shared_ptr<TagV0> FromBytes(const std::span<const std::byte>& data); + std::vector<std::byte> ToBytes() const; + + const Block& GetUIDBlock() const; + const std::vector<std::byte>& GetNDEFData() const; + const std::vector<std::byte>& GetLockedArea() const; + + void SetNDEFData(const std::span<const std::byte>& data); + +private: + bool ParseLockedArea(const std::span<const std::byte>& data); + bool IsBlockLocked(std::uint8_t blockIdx) const; + bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea); + bool ValidateCapabilityContainer(); + + std::map<std::uint8_t, Block> mLockedOrReservedBlocks; + std::map<std::uint8_t, Block> mLockedBlocks; + std::array<std::uint8_t, 0x4> mCapabilityContainer; + std::vector<TLV> mTLVs; + std::size_t mNdefTlvIdx; + std::vector<std::byte> mLockedArea; +}; diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp new file mode 100644 index 0000000..f8d87fb --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -0,0 +1,277 @@ +#include "ndef.h" + +#include <cassert> + +namespace ndef +{ + + Record::Record() + { + } + + Record::~Record() + { + } + + std::optional<Record> Record::FromStream(Stream& stream) + { + Record rec; + + // Read record header + uint8_t recHdr; + stream >> recHdr; + rec.mFlags = recHdr & ~NDEF_TNF_MASK; + rec.mTNF = static_cast<TypeNameFormat>(recHdr & NDEF_TNF_MASK); + + // Type length + uint8_t typeLen; + stream >> typeLen; + + // Payload length; + uint32_t payloadLen; + if (recHdr & NDEF_SR) + { + uint8_t len; + stream >> len; + payloadLen = len; + } + else + { + stream >> payloadLen; + } + + // Some sane limits for the payload size + if (payloadLen > 2 * 1024 * 1024) + { + return {}; + } + + // ID length + uint8_t idLen = 0; + if (recHdr & NDEF_IL) + { + stream >> idLen; + } + + // Make sure we didn't read past the end of the stream yet + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + // Type + rec.mType.resize(typeLen); + stream.Read(rec.mType); + + // ID + rec.mID.resize(idLen); + stream.Read(rec.mID); + + // Payload + rec.mPayload.resize(payloadLen); + stream.Read(rec.mPayload); + + // Make sure we didn't read past the end of the stream again + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + return rec; + } + + std::vector<std::byte> Record::ToBytes(uint8_t flags) const + { + std::vector<std::byte> bytes; + VectorStream stream(bytes, std::endian::big); + + // Combine flags (clear message begin and end flags) + std::uint8_t finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); + finalFlags |= flags; + + // Write flags + tnf + stream << std::uint8_t(finalFlags | std::uint8_t(mTNF)); + + // Type length + stream << std::uint8_t(mType.size()); + + // Payload length + if (IsShort()) + { + stream << std::uint8_t(mPayload.size()); + } + else + { + stream << std::uint32_t(mPayload.size()); + } + + // ID length + if (mFlags & NDEF_IL) + { + stream << std::uint8_t(mID.size()); + } + + // Type + stream.Write(mType); + + // ID + stream.Write(mID); + + // Payload + stream.Write(mPayload); + + return bytes; + } + + Record::TypeNameFormat Record::GetTNF() const + { + return mTNF; + } + + const std::vector<std::byte>& Record::GetID() const + { + return mID; + } + + const std::vector<std::byte>& Record::GetType() const + { + return mType; + } + + const std::vector<std::byte>& Record::GetPayload() const + { + return mPayload; + } + + void Record::SetTNF(TypeNameFormat tnf) + { + mTNF = tnf; + } + + void Record::SetID(const std::span<const std::byte>& id) + { + cemu_assert(id.size() < 0x100); + + if (id.size() > 0) + { + mFlags |= NDEF_IL; + } + else + { + mFlags &= ~NDEF_IL; + } + + mID.assign(id.begin(), id.end()); + } + + void Record::SetType(const std::span<const std::byte>& type) + { + cemu_assert(type.size() < 0x100); + + mType.assign(type.begin(), type.end()); + } + + void Record::SetPayload(const std::span<const std::byte>& payload) + { + // Update short record flag + if (payload.size() < 0xff) + { + mFlags |= NDEF_SR; + } + else + { + mFlags &= ~NDEF_SR; + } + + mPayload.assign(payload.begin(), payload.end()); + } + + bool Record::IsLast() const + { + return mFlags & NDEF_ME; + } + + bool Record::IsShort() const + { + return mFlags & NDEF_SR; + } + + Message::Message() + { + } + + Message::~Message() + { + } + + std::optional<Message> Message::FromBytes(const std::span<const std::byte>& data) + { + Message msg; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0) + { + std::optional<Record> rec = Record::FromStream(stream); + if (!rec) + { + cemuLog_log(LogType::Force, "Warning: Failed to parse NDEF Record #{}." + "Ignoring the remaining {} bytes in NDEF message", msg.mRecords.size(), stream.GetRemaining()); + break; + } + + msg.mRecords.emplace_back(*rec); + + if ((*rec).IsLast() && stream.GetRemaining() > 0) + { + cemuLog_log(LogType::Force, "Warning: Ignoring {} bytes in NDEF message", stream.GetRemaining()); + break; + } + } + + if (msg.mRecords.empty()) + { + return {}; + } + + if (!msg.mRecords.back().IsLast()) + { + cemuLog_log(LogType::Force, "Error: NDEF message missing end record"); + return {}; + } + + return msg; + } + + std::vector<std::byte> Message::ToBytes() const + { + std::vector<std::byte> bytes; + + for (std::size_t i = 0; i < mRecords.size(); i++) + { + std::uint8_t flags = 0; + + // Add message begin flag to first record + if (i == 0) + { + flags |= Record::NDEF_MB; + } + + // Add message end flag to last record + if (i == mRecords.size() - 1) + { + flags |= Record::NDEF_ME; + } + + std::vector<std::byte> recordBytes = mRecords[i].ToBytes(flags); + bytes.insert(bytes.end(), recordBytes.begin(), recordBytes.end()); + } + + return bytes; + } + + void Message::append(const Record& r) + { + mRecords.push_back(r); + } + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/ndef.h b/src/Cafe/OS/libs/nfc/ndef.h new file mode 100644 index 0000000..b5f38b1 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.h @@ -0,0 +1,88 @@ +#pragma once + +#include <span> +#include <vector> +#include <optional> + +#include "stream.h" + +namespace ndef +{ + + class Record + { + public: + enum HeaderFlag + { + NDEF_IL = 0x08, + NDEF_SR = 0x10, + NDEF_CF = 0x20, + NDEF_ME = 0x40, + NDEF_MB = 0x80, + NDEF_TNF_MASK = 0x07, + }; + + enum TypeNameFormat + { + NDEF_TNF_EMPTY = 0, + NDEF_TNF_WKT = 1, + NDEF_TNF_MEDIA = 2, + NDEF_TNF_URI = 3, + NDEF_TNF_EXT = 4, + NDEF_TNF_UNKNOWN = 5, + NDEF_TNF_UNCHANGED = 6, + NDEF_TNF_RESERVED = 7, + }; + + public: + Record(); + virtual ~Record(); + + static std::optional<Record> FromStream(Stream& stream); + std::vector<std::byte> ToBytes(uint8_t flags = 0) const; + + TypeNameFormat GetTNF() const; + const std::vector<std::byte>& GetID() const; + const std::vector<std::byte>& GetType() const; + const std::vector<std::byte>& GetPayload() const; + + void SetTNF(TypeNameFormat tnf); + void SetID(const std::span<const std::byte>& id); + void SetType(const std::span<const std::byte>& type); + void SetPayload(const std::span<const std::byte>& payload); + + bool IsLast() const; + bool IsShort() const; + + private: + uint8_t mFlags; + TypeNameFormat mTNF; + std::vector<std::byte> mID; + std::vector<std::byte> mType; + std::vector<std::byte> mPayload; + }; + + class Message + { + public: + Message(); + virtual ~Message(); + + static std::optional<Message> FromBytes(const std::span<const std::byte>& data); + std::vector<std::byte> ToBytes() const; + + Record& operator[](int i) { return mRecords[i]; } + const Record& operator[](int i) const { return mRecords[i]; } + + void append(const Record& r); + + auto begin() { return mRecords.begin(); } + auto end() { return mRecords.end(); } + auto begin() const { return mRecords.begin(); } + auto end() const { return mRecords.end(); } + + private: + std::vector<Record> mRecords; + }; + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp new file mode 100644 index 0000000..21e9e91 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -0,0 +1,596 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Common/FileStream.h" + +#include "TagV0.h" +#include "ndef.h" + +// TODO move errors to header and allow ntag to convert them + +#define NFC_MODE_INVALID -1 +#define NFC_MODE_IDLE 0 +#define NFC_MODE_ACTIVE 1 + +#define NFC_STATE_UNINITIALIZED 0x0 +#define NFC_STATE_INITIALIZED 0x1 +#define NFC_STATE_IDLE 0x2 +#define NFC_STATE_READ 0x3 +#define NFC_STATE_WRITE 0x4 +#define NFC_STATE_ABORT 0x5 +#define NFC_STATE_FORMAT 0x6 +#define NFC_STATE_SET_READ_ONLY 0x7 +#define NFC_STATE_TAG_PRESENT 0x8 +#define NFC_STATE_DETECT 0x9 +#define NFC_STATE_RAW 0xA + +#define NFC_STATUS_COMMAND_COMPLETE 0x1 +#define NFC_STATUS_READY 0x2 +#define NFC_STATUS_HAS_TAG 0x4 + +namespace nfc +{ + struct NFCContext + { + bool isInitialized; + uint32 state; + sint32 mode; + bool hasTag; + + uint32 nfcStatus; + std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; + + MPTR tagDetectCallback; + void* tagDetectContext; + MPTR abortCallback; + void* abortContext; + MPTR rawCallback; + void* rawContext; + MPTR readCallback; + void* readContext; + MPTR writeCallback; + void* writeContext; + MPTR getTagInfoCallback; + + SysAllocator<NFCTagInfo> tagInfo; + + fs::path tagPath; + std::shared_ptr<TagV0> tag; + + ndef::Message writeMessage; + }; + NFCContext gNFCContexts[2]; + + sint32 NFCInit(uint32 chan) + { + return NFCInitEx(chan, 0); + } + + void __NFCClearContext(NFCContext* context) + { + context->isInitialized = false; + context->state = NFC_STATE_UNINITIALIZED; + context->mode = NFC_MODE_IDLE; + context->hasTag = false; + + context->nfcStatus = NFC_STATUS_READY; + context->discoveryTimeout = {}; + + context->tagDetectCallback = MPTR_NULL; + context->tagDetectContext = nullptr; + context->abortCallback = MPTR_NULL; + context->abortContext = nullptr; + context->rawCallback = MPTR_NULL; + context->rawContext = nullptr; + context->readCallback = MPTR_NULL; + context->readContext = nullptr; + context->writeCallback = MPTR_NULL; + context->writeContext = nullptr; + + context->tagPath = ""; + context->tag = {}; + } + + sint32 NFCInitEx(uint32 chan, uint32 powerMode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + ctx->isInitialized = true; + ctx->state = NFC_STATE_INITIALIZED; + + return 0; + } + + sint32 NFCShutdown(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + + return 0; + } + + bool NFCIsInit(uint32 chan) + { + cemu_assert(chan < 2); + + return gNFCContexts[chan].isInitialized; + } + + void __NFCHandleRead(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + StackAllocator<NFCUid> uid; + bool readOnly = false; + uint32 dataSize = 0; + StackAllocator<uint8_t, 0x200> data; + uint32 lockedDataSize = 0; + StackAllocator<uint8_t, 0x200> lockedData; + + if (ctx->tag) + { + // Try to parse ndef message + auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); + if (ndefMsg) + { + // Look for the unknown TNF which contains the data we care about + for (const auto& rec : *ndefMsg) + { + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) { + dataSize = rec.GetPayload().size(); + cemu_assert(dataSize < 0x200); + memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); + break; + } + } + + if (dataSize) + { + // Get locked data + lockedDataSize = ctx->tag->GetLockedArea().size(); + memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); + + // Fill in uid + memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + + result = 0; + } + else + { + result = -0xBFE; + } + } + else + { + result = -0xBFE; + } + + // Clear tag status after read + // TODO this is not really nice here + ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; + ctx->tag = {}; + } + else + { + result = -0x1DD; + } + + PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); + } + + void __NFCHandleWrite(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + // TODO write to file + + PPCCoreCallback(ctx->writeCallback, chan, 0, ctx->writeContext); + } + + void __NFCHandleAbort(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + PPCCoreCallback(ctx->abortCallback, chan, 0, ctx->abortContext); + } + + void __NFCHandleRaw(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + result = 0; + } + else + { + result = -0x9DD; + } + + // We don't actually send any commands/responses + uint32 responseSize = 0; + void* responseData = nullptr; + + PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); + } + + void NFCProc(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!ctx->isInitialized) + { + return; + } + + // Check if the detect callback should be called + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + if (!ctx->hasTag && ctx->state > NFC_STATE_IDLE && ctx->state != NFC_STATE_ABORT) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, true, ctx->tagDetectContext); + } + + ctx->hasTag = true; + } + } + else + { + if (ctx->hasTag && ctx->state == NFC_STATE_IDLE) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, false, ctx->tagDetectContext); + } + + ctx->hasTag = false; + } + } + + switch (ctx->state) + { + case NFC_STATE_INITIALIZED: + ctx->state = NFC_STATE_IDLE; + break; + case NFC_STATE_IDLE: + break; + case NFC_STATE_READ: + // Do we have a tag or did the timeout expire? + if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + __NFCHandleRead(chan); + } + break; + case NFC_STATE_WRITE: + __NFCHandleWrite(chan); + break; + case NFC_STATE_ABORT: + __NFCHandleAbort(chan); + break; + case NFC_STATE_RAW: + // Do we have a tag or did the timeout expire? + if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + __NFCHandleRaw(chan); + } + break; + } + } + + sint32 NFCGetMode(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan) || ctx->state == NFC_STATE_UNINITIALIZED) + { + return NFC_MODE_INVALID; + } + + return ctx->mode; + } + + sint32 NFCSetMode(uint32 chan, sint32 mode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0xAE0; + } + + if (ctx->state == NFC_STATE_UNINITIALIZED) + { + return -0xADF; + } + + ctx->mode = mode; + + return 0; + } + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + ctx->tagDetectCallback = callback; + ctx->tagDetectContext = context; + } + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x6E0; + } + + if (ctx->state <= NFC_STATE_IDLE) + { + return -0x6DF; + } + + ctx->state = NFC_STATE_ABORT; + ctx->abortCallback = callback; + ctx->abortContext = context; + + return 0; + } + + void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamU32(responseSize, 2); + ppcDefineParamPtr(responseData, void, 3); + ppcDefineParamPtr(context, void, 4); + + NFCContext* ctx = &gNFCContexts[chan]; + + // TODO convert error + error = error; + if (error == 0 && ctx->tag) + { + // this is usually parsed from response data + ctx->tagInfo->uidSize = sizeof(NFCUid); + memcpy(ctx->tagInfo->uid, ctx->tag->GetUIDBlock().data(), ctx->tagInfo->uidSize); + ctx->tagInfo->technology = NFC_TECHNOLOGY_A; + ctx->tagInfo->protocol = NFC_PROTOCOL_T1T; + } + + PPCCoreCallback(ctx->getTagInfoCallback, chan, error, ctx->tagInfo.GetPtr(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nn::nfp::NFCGetTagInfo(chan, discoveryTimeout, callback, context); + } + + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->getTagInfoCallback = callback; + + sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); + return result; // TODO convert result + } + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x9E0; + } + + // Only allow discovery + if (!startDiscovery) + { + return -0x9DC; + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return -0x9DC; + } + + if (ctx->state != NFC_STATE_IDLE) + { + return -0x9DF; + } + + ctx->state = NFC_STATE_RAW; + ctx->rawCallback = callback; + ctx->rawContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + return 0; + } + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x1E0; + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return -0x1DC; + } + + if (ctx->state != NFC_STATE_IDLE) + { + return -0x1DF; + } + + cemuLog_log(LogType::NFC, "starting read"); + + ctx->state = NFC_STATE_READ; + ctx->readCallback = callback; + ctx->readContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + // TODO uid filter? + + return 0; + } + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x2e0; + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return -0x2dc; + } + + if (ctx->state != NFC_STATE_IDLE) + { + return -0x1df; + } + + // Create unknown record which contains the rw area + ndef::Record rec; + rec.SetTNF(ndef::Record::NDEF_TNF_UNKNOWN); + rec.SetPayload(std::span(reinterpret_cast<std::byte*>(data), size)); + + // Create ndef message which contains the record + ndef::Message msg; + msg.append(rec); + ctx->writeMessage = msg; + + ctx->state = NFC_STATE_WRITE; + ctx->writeCallback = callback; + ctx->writeContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + // TODO uid filter? + + return 0; + } + + void Initialize() + { + cafeExportRegister("nfc", NFCInit, LogType::NFC); + cafeExportRegister("nfc", NFCInitEx, LogType::NFC); + cafeExportRegister("nfc", NFCShutdown, LogType::NFC); + cafeExportRegister("nfc", NFCIsInit, LogType::NFC); + cafeExportRegister("nfc", NFCProc, LogType::NFC); + cafeExportRegister("nfc", NFCGetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetTagDetectCallback, LogType::NFC); + cafeExportRegister("nfc", NFCGetTagInfo, LogType::NFC); + cafeExportRegister("nfc", NFCSendRawData, LogType::NFC); + cafeExportRegister("nfc", NFCAbort, LogType::NFC); + cafeExportRegister("nfc", NFCRead, LogType::NFC); + cafeExportRegister("nfc", NFCWrite, LogType::NFC); + } + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError) + { + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nnNfp_touchNfcTagFromFile(filePath, nfcError); + } + + NFCContext* ctx = &gNFCContexts[0]; + + auto nfcData = FileStream::LoadIntoMemory(filePath); + if (!nfcData) + { + *nfcError = NFC_ERROR_NO_ACCESS; + return false; + } + + ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); + if (!ctx->tag) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + + ctx->nfcStatus |= NFC_STATUS_HAS_TAG; + ctx->tagPath = filePath; + + *nfcError = NFC_ERROR_NONE; + return true; + } +} diff --git a/src/Cafe/OS/libs/nfc/nfc.h b/src/Cafe/OS/libs/nfc/nfc.h new file mode 100644 index 0000000..2ebdd2a --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.h @@ -0,0 +1,62 @@ +#pragma once + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) + +#define NFC_PROTOCOL_T1T 0x1 +#define NFC_PROTOCOL_T2T 0x2 + +#define NFC_TECHNOLOGY_A 0x0 +#define NFC_TECHNOLOGY_B 0x1 +#define NFC_TECHNOLOGY_F 0x2 + +namespace nfc +{ + struct NFCUid + { + /* +0x00 */ uint8 uid[7]; + }; + static_assert(sizeof(NFCUid) == 0x7); + + struct NFCTagInfo + { + /* +0x00 */ uint8 uidSize; + /* +0x01 */ uint8 uid[10]; + /* +0x0B */ uint8 technology; + /* +0x0C */ uint8 protocol; + /* +0x0D */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NFCTagInfo) == 0x2D); + + sint32 NFCInit(uint32 chan); + + sint32 NFCInitEx(uint32 chan, uint32 powerMode); + + sint32 NFCShutdown(uint32 chan); + + bool NFCIsInit(uint32 chan); + + void NFCProc(uint32 chan); + + sint32 NFCGetMode(uint32 chan); + + sint32 NFCSetMode(uint32 chan, sint32 mode); + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context); + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context); + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context); + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context); + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context); + + void Initialize(); + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError); +} diff --git a/src/Cafe/OS/libs/nfc/stream.cpp b/src/Cafe/OS/libs/nfc/stream.cpp new file mode 100644 index 0000000..73c2880 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.cpp @@ -0,0 +1,201 @@ +#include "stream.h" + +#include <algorithm> + +Stream::Stream(std::endian endianness) + : mError(ERROR_OK), mEndianness(endianness) +{ +} + +Stream::~Stream() +{ +} + +Stream::Error Stream::GetError() const +{ + return mError; +} + +void Stream::SetEndianness(std::endian endianness) +{ + mEndianness = endianness; +} + +std::endian Stream::GetEndianness() const +{ + return mEndianness; +} + +Stream& Stream::operator>>(bool& val) +{ + std::uint8_t i; + *this >> i; + val = !!i; + + return *this; +} + +Stream& Stream::operator>>(float& val) +{ + std::uint32_t i; + *this >> i; + val = std::bit_cast<float>(i); + + return *this; +} + +Stream& Stream::operator>>(double& val) +{ + std::uint64_t i; + *this >> i; + val = std::bit_cast<double>(i); + + return *this; +} + +Stream& Stream::operator<<(bool val) +{ + std::uint8_t i = val; + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(float val) +{ + std::uint32_t i = std::bit_cast<std::uint32_t>(val); + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(double val) +{ + std::uint64_t i = std::bit_cast<std::uint64_t>(val); + *this >> i; + + return *this; +} + +void Stream::SetError(Error error) +{ + mError = error; +} + +bool Stream::NeedsSwap() +{ + return mEndianness != std::endian::native; +} + +VectorStream::VectorStream(std::vector<std::byte>& vector, std::endian endianness) + : Stream(endianness), mVector(vector), mPosition(0) +{ +} + +VectorStream::~VectorStream() +{ +} + +std::size_t VectorStream::Read(const std::span<std::byte>& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mVector.get().begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t VectorStream::Write(const std::span<const std::byte>& data) +{ + // Resize vector if not enough bytes remain + if (mPosition + data.size() > mVector.get().size()) + { + mVector.get().resize(mPosition + data.size()); + } + + std::copy(data.begin(), data.end(), mVector.get().begin() + mPosition); + mPosition += data.size(); + return data.size(); +} + +bool VectorStream::SetPosition(std::size_t position) +{ + if (position >= mVector.get().size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t VectorStream::GetPosition() const +{ + return mPosition; +} + +std::size_t VectorStream::GetRemaining() const +{ + return mVector.get().size() - mPosition; +} + +SpanStream::SpanStream(std::span<const std::byte> span, std::endian endianness) + : Stream(endianness), mSpan(std::move(span)), mPosition(0) +{ +} + +SpanStream::~SpanStream() +{ +} + +std::size_t SpanStream::Read(const std::span<std::byte>& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mSpan.begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t SpanStream::Write(const std::span<const std::byte>& data) +{ + // Cannot write to const span + SetError(ERROR_WRITE_FAILED); + return 0; +} + +bool SpanStream::SetPosition(std::size_t position) +{ + if (position >= mSpan.size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t SpanStream::GetPosition() const +{ + return mPosition; +} + +std::size_t SpanStream::GetRemaining() const +{ + if (mPosition > mSpan.size()) + { + return 0; + } + + return mSpan.size() - mPosition; +} diff --git a/src/Cafe/OS/libs/nfc/stream.h b/src/Cafe/OS/libs/nfc/stream.h new file mode 100644 index 0000000..e666b48 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.h @@ -0,0 +1,139 @@ +#pragma once + +#include <cstdint> +#include <vector> +#include <span> +#include <bit> + +#include "Common/precompiled.h" + +class Stream +{ +public: + enum Error + { + ERROR_OK, + ERROR_READ_FAILED, + ERROR_WRITE_FAILED, + }; + +public: + Stream(std::endian endianness = std::endian::native); + virtual ~Stream(); + + Error GetError() const; + + void SetEndianness(std::endian endianness); + std::endian GetEndianness() const; + + virtual std::size_t Read(const std::span<std::byte>& data) = 0; + virtual std::size_t Write(const std::span<const std::byte>& data) = 0; + + virtual bool SetPosition(std::size_t position) = 0; + virtual std::size_t GetPosition() const = 0; + + virtual std::size_t GetRemaining() const = 0; + + // Stream read operators + template<std::integral T> + Stream& operator>>(T& val) + { + val = 0; + if (Read(std::as_writable_bytes(std::span(std::addressof(val), 1))) == sizeof(val)) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + } + + return *this; + } + Stream& operator>>(bool& val); + Stream& operator>>(float& val); + Stream& operator>>(double& val); + + // Stream write operators + template<std::integral T> + Stream& operator<<(T val) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + + Write(std::as_bytes(std::span(std::addressof(val), 1))); + return *this; + } + Stream& operator<<(bool val); + Stream& operator<<(float val); + Stream& operator<<(double val); + +protected: + void SetError(Error error); + + bool NeedsSwap(); + + Error mError; + std::endian mEndianness; +}; + +class VectorStream : public Stream +{ +public: + VectorStream(std::vector<std::byte>& vector, std::endian endianness = std::endian::native); + virtual ~VectorStream(); + + virtual std::size_t Read(const std::span<std::byte>& data) override; + virtual std::size_t Write(const std::span<const std::byte>& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::reference_wrapper<std::vector<std::byte>> mVector; + std::size_t mPosition; +}; + +class SpanStream : public Stream +{ +public: + SpanStream(std::span<const std::byte> span, std::endian endianness = std::endian::native); + virtual ~SpanStream(); + + virtual std::size_t Read(const std::span<std::byte>& data) override; + virtual std::size_t Write(const std::span<const std::byte>& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::span<const std::byte> mSpan; + std::size_t mPosition; +}; diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp index ad2ea20..10d9e7c 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -293,41 +293,6 @@ void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } -typedef struct -{ - /* +0x00 */ uint8 uidLength; - /* +0x01 */ uint8 uid[0xA]; - /* +0x0B */ uint8 ukn0B; - /* +0x0C */ uint8 ukn0C; - /* +0x0D */ uint8 ukn0D; - // more? -}NFCTagInfoCallbackParam_t; - -uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) -{ - cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); - - - cemu_assert(index == 0); - - nnNfpLock(); - - StackAllocator<NFCTagInfoCallbackParam_t> _callbackParam; - NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); - - memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); - - memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); - callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; - - PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); - - nnNfpUnlock(); - - - return 0; // 0 -> success -} - void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Mount()"); @@ -769,6 +734,16 @@ void nnNfp_unloadAmiibo() nnNfpUnlock(); } +bool nnNfp_isInitialized() +{ + return nfp_data.nfpIsInitialized; +} + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) + bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; @@ -960,6 +935,41 @@ void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) namespace nn::nfp { + typedef struct + { + /* +0x00 */ uint8 uidLength; + /* +0x01 */ uint8 uid[0xA]; + /* +0x0B */ uint8 ukn0B; + /* +0x0C */ uint8 ukn0C; + /* +0x0D */ uint8 ukn0D; + // more? + }NFCTagInfoCallbackParam_t; + + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) + { + cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); + + + cemu_assert(index == 0); + + nnNfpLock(); + + StackAllocator<NFCTagInfoCallbackParam_t> _callbackParam; + NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); + + memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); + + memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); + callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; + + PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); + + nnNfpUnlock(); + + + return 0; // 0 -> success + } + uint32 GetErrorCode(uint32 result) { uint32 level = (result >> 0x1b) & 3; @@ -1019,9 +1029,6 @@ namespace nn::nfp nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); - - // NFC API - cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder); } } diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h index e8a1c55..25b36cc 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h @@ -2,12 +2,15 @@ namespace nn::nfp { + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam); + void load(); } void nnNfp_load(); void nnNfp_update(); +bool nnNfp_isInitialized(); bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_NONE (0) @@ -18,8 +21,3 @@ bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_RW_MOUNT (5) #define NFP_STATE_UNEXPECTED (6) #define NFP_STATE_RW_MOUNT_ROM (7) - -// CEMU NFC error codes -#define NFC_ERROR_NONE (0) -#define NFC_ERROR_NO_ACCESS (1) -#define NFC_ERROR_INVALID_FILE_FORMAT (2) diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp new file mode 100644 index 0000000..8bdbb66 --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -0,0 +1,438 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/ntag/ntag.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" + +namespace ntag +{ + struct NTAGWriteData + { + + }; + NTAGWriteData gWriteData[2]; + + bool ccrNfcOpened = false; + IOSDevHandle gCcrNfcHandle; + + NTAGFormatSettings gFormatSettings; + + MPTR gDetectCallbacks[2]; + MPTR gAbortCallbacks[2]; + MPTR gReadCallbacks[2]; + MPTR gWriteCallbacks[2]; + + sint32 __NTAGConvertNFCError(sint32 error) + { + // TODO + return error; + } + + sint32 NTAGInit(uint32 chan) + { + return NTAGInitEx(chan); + } + + sint32 NTAGInitEx(uint32 chan) + { + sint32 result = nfc::NFCInitEx(chan, 1); + return __NTAGConvertNFCError(result); + } + + sint32 NTAGShutdown(uint32 chan) + { + sint32 result = nfc::NFCShutdown(chan); + + if (ccrNfcOpened) + { + coreinit::IOS_Close(gCcrNfcHandle); + ccrNfcOpened = false; + } + + gDetectCallbacks[chan] = MPTR_NULL; + gAbortCallbacks[chan] = MPTR_NULL; + gReadCallbacks[chan] = MPTR_NULL; + gWriteCallbacks[chan] = MPTR_NULL; + + return __NTAGConvertNFCError(result); + } + + bool NTAGIsInit(uint32 chan) + { + return nfc::NFCIsInit(chan); + } + + void NTAGProc(uint32 chan) + { + nfc::NFCProc(chan); + } + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings) + { + gFormatSettings.version = formatSettings->version; + gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); + gFormatSettings.indentifyCode = _swapEndianU32(formatSettings->indentifyCode); + } + + void __NTAGDetectCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamU32(hasTag, 1); + ppcDefineParamPtr(context, void, 2); + + cemuLog_log(LogType::NTAG, "__NTAGDetectCallback: {} {} {}", chan, hasTag, context); + + PPCCoreCallback(gDetectCallbacks[chan], chan, hasTag, context); + + osLib_returnFromFunction(hCPU, 0); + } + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gDetectCallbacks[chan] = callback; + nfc::NFCSetTagDetectCallback(chan, RPLLoader_MakePPCCallable(__NTAGDetectCallback), context); + } + + void __NTAGAbortCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO is it normal that Rumble U calls this? + + gAbortCallbacks[chan] = callback; + sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); + return __NTAGConvertNFCError(result); + } + + bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) + { + memcpy(nfc, raw, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (raw->version == 0) + { + nfc->version = 0; + nfc->dataSize = 0x1C8; + nfc->seedOffset = 0x25; + nfc->keyGenSaltOffset = 0x1A8; + nfc->uuidOffset = 0x198; + nfc->unfixedInfosOffset = 0x28; + nfc->unfixedInfosSize = 0x120; + nfc->lockedSecretOffset = 0x168; + nfc->lockedSecretSize = 0x30; + nfc->unfixedInfosHmacOffset = 0; + nfc->lockedSecretHmacOffset = 0x148; + } + else if (raw->version == 2) + { + nfc->version = 2; + nfc->dataSize = 0x208; + nfc->seedOffset = 0x29; + nfc->keyGenSaltOffset = 0x1E8; + nfc->uuidOffset = 0x1D4; + nfc->unfixedInfosOffset = 0x2C; + nfc->unfixedInfosSize = 0x188; + nfc->lockedSecretOffset = 0x1DC; + nfc->lockedSecretSize = 0; + nfc->unfixedInfosHmacOffset = 0x8; + nfc->lockedSecretHmacOffset = 0x1B4; + + memcpy(nfc->data + 0x1d4, raw->data, 0x8); + memcpy(nfc->data, raw->data + 0x8, 0x8); + memcpy(nfc->data + 0x28, raw->data + 0x10, 0x4); + memcpy(nfc->data + nfc->unfixedInfosOffset, raw->data + 0x14, 0x20); + memcpy(nfc->data + nfc->lockedSecretHmacOffset, raw->data + 0x34, 0x20); + memcpy(nfc->data + nfc->lockedSecretOffset, raw->data + 0x54, 0xC); + memcpy(nfc->data + nfc->keyGenSaltOffset, raw->data + 0x60, 0x20); + memcpy(nfc->data + nfc->unfixedInfosHmacOffset, raw->data + 0x80, 0x20); + memcpy(nfc->data + nfc->unfixedInfosOffset + 0x20, raw->data + 0xa0, 0x168); + memcpy(nfc->data + 0x208, raw->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + bool __NTAGNfcDataToRawData(iosu::ccr_nfc::CCRNFCCryptData* nfc, iosu::ccr_nfc::CCRNFCCryptData* raw) + { + memcpy(raw, nfc, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (nfc->version == 0) + { + raw->version = 0; + raw->dataSize = 0x1C8; + raw->seedOffset = 0x25; + raw->keyGenSaltOffset = 0x1A8; + raw->uuidOffset = 0x198; + raw->unfixedInfosOffset = 0x28; + raw->unfixedInfosSize = 0x120; + raw->lockedSecretOffset = 0x168; + raw->lockedSecretSize = 0x30; + raw->unfixedInfosHmacOffset = 0; + raw->lockedSecretHmacOffset = 0x148; + } + else if (nfc->version == 2) + { + raw->version = 2; + raw->dataSize = 0x208; + raw->seedOffset = 0x11; + raw->keyGenSaltOffset = 0x60; + raw->uuidOffset = 0; + raw->unfixedInfosOffset = 0x14; + raw->unfixedInfosSize = 0x188; + raw->lockedSecretOffset = 0x54; + raw->lockedSecretSize = 0xC; + raw->unfixedInfosHmacOffset = 0x80; + raw->lockedSecretHmacOffset = 0x34; + + memcpy(raw->data + 0x8, nfc->data, 0x8); + memcpy(raw->data + raw->unfixedInfosHmacOffset, nfc->data + 0x8, 0x20); + memcpy(raw->data + 0x10, nfc->data + 0x28, 0x4); + memcpy(raw->data + raw->unfixedInfosOffset, nfc->data + 0x2C, 0x20); + memcpy(raw->data + 0xa0, nfc->data + 0x4C, 0x168); + memcpy(raw->data + raw->lockedSecretHmacOffset, nfc->data + 0x1B4, 0x20); + memcpy(raw->data + raw->uuidOffset, nfc->data + 0x1D4, 0x8); + memcpy(raw->data + raw->lockedSecretOffset, nfc->data + 0x1DC, 0xC); + memcpy(raw->data + raw->keyGenSaltOffset, nfc->data + 0x1E8, 0x20); + memcpy(raw->data + 0x208, nfc->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + sint32 __NTAGDecryptData(void* decryptedData, void* rawData) + { + StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Decrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 2, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(decryptedData, nfcRawData->data, 0x1C8); + + // Convert result + if (result == CCR_NFC_INVALID_UNFIXED_INFOS) + { + return -0x2708; + } + else if (result == CCR_NFC_INVALID_LOCKED_SECRET) + { + return -0x2707; + } + + return result; + } + + sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + // TODO + return 0; + } + + sint32 __NTAGParseHeaders(const uint8* data, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); + memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); + memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); + memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); + return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); + } + + sint32 __NTAGParseData(void* rawData, void* rwData, void* roData, nfc::NFCUid* uid, uint32 lockedDataSize, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + uint8 decryptedData[0x1C8]; + sint32 result = __NTAGDecryptData(decryptedData, rawData); + if (result < 0) + { + return result; + } + + result = __NTAGParseHeaders(decryptedData, noftHeader, infoHeader, rwHeader, roHeader); + if (result < 0) + { + return result; + } + + if (_swapEndianU16(roHeader->size) + 0x70 != lockedDataSize) + { + cemuLog_log(LogType::Force, "Invalid locked area size"); + return -0x270C; + } + + if (memcmp(infoHeader->uid.uid, uid->uid, sizeof(nfc::NFCUid)) != 0) + { + cemuLog_log(LogType::Force, "UID mismatch"); + return -0x270B; + } + + cemu_assert(_swapEndianU16(rwHeader->offset) + _swapEndianU16(rwHeader->size) < 0x200); + cemu_assert(_swapEndianU16(roHeader->offset) + _swapEndianU16(roHeader->size) < 0x200); + + memcpy(rwData, decryptedData + _swapEndianU16(rwHeader->offset), _swapEndianU16(rwHeader->size)); + memcpy(roData, decryptedData + _swapEndianU16(roHeader->offset), _swapEndianU16(roHeader->size)); + + return 0; + } + + void __NTAGReadCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]; + StackAllocator<NTAGData> readResult; + StackAllocator<uint8, 0x1C8> rwData; + StackAllocator<uint8, 0x1C8> roData; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + + readResult->readOnly = readOnly; + + error = __NTAGConvertNFCError(error); + if (error == 0) + { + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData.GetPointer(), roData.GetPointer(), uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error == 0) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + readResult->rwInfo.data = _swapEndianU32(rwData.GetMPTR()); + readResult->roInfo.data = _swapEndianU32(roData.GetMPTR()); + readResult->rwInfo.makerCode = rwHeader.makerCode; + readResult->rwInfo.size = rwHeader.size; + readResult->roInfo.makerCode = roHeader.makerCode; + readResult->rwInfo.identifyCode = rwHeader.identifyCode; + readResult->roInfo.identifyCode = roHeader.identifyCode; + readResult->formatVersion = infoHeader.formatVersion; + readResult->roInfo.size = roHeader.size; + + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + + PPCCoreCallback(gReadCallbacks[chan], chan, 0, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + return; + } + } + + if (uid) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + } + readResult->roInfo.size = 0; + readResult->rwInfo.size = 0; + readResult->roInfo.data = MPTR_NULL; + readResult->formatVersion = 0; + readResult->rwInfo.data = MPTR_NULL; + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + PPCCoreCallback(gReadCallbacks[chan], chan, error, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gReadCallbacks[chan] = callback; + + nfc::NFCUid _uid{}, _uidMask{}; + if (uid && uidMask) + { + memcpy(&_uid, uid, sizeof(*uid)); + memcpy(&_uidMask, uidMask, sizeof(*uidMask)); + } + + sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); + return __NTAGConvertNFCError(result); + } + + void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gWriteCallbacks[chan] = callback; + + nfc::NFCUid _uid{}, _uidMask{}; + if (uid) + { + memcpy(&_uid, uid, sizeof(*uid)); + } + memset(_uidMask.uid, 0xff, sizeof(_uidMask.uid)); + + // TODO save write data + + // TODO we probably don't need to read first here + sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); + return __NTAGConvertNFCError(result); + } + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO + return 0; + } + + void Initialize() + { + cafeExportRegister("ntag", NTAGInit, LogType::NTAG); + cafeExportRegister("ntag", NTAGInitEx, LogType::NTAG); + cafeExportRegister("ntag", NTAGShutdown, LogType::NTAG); + cafeExportRegister("ntag", NTAGIsInit, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGProc, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGSetFormatSettings, LogType::NTAG); + cafeExportRegister("ntag", NTAGSetTagDetectCallback, LogType::NTAG); + cafeExportRegister("ntag", NTAGAbort, LogType::NTAG); + cafeExportRegister("ntag", NTAGRead, LogType::NTAG); + cafeExportRegister("ntag", NTAGWrite, LogType::NTAG); + cafeExportRegister("ntag", NTAGFormat, LogType::NTAG); + } +} diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h new file mode 100644 index 0000000..1174e6b --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -0,0 +1,94 @@ +#pragma once +#include "Cafe/OS/libs/nfc/nfc.h" + +namespace ntag +{ + struct NTAGFormatSettings + { + /* +0x00 */ uint8 version; + /* +0x04 */ uint32 makerCode; + /* +0x08 */ uint32 indentifyCode; + /* +0x0C */ uint8 reserved[0x1C]; + }; + static_assert(sizeof(NTAGFormatSettings) == 0x28); + +#pragma pack(1) + struct NTAGNoftHeader + { + /* +0x00 */ uint32 magic; + /* +0x04 */ uint8 version; + /* +0x05 */ uint16 writeCount; + /* +0x07 */ uint8 unknown; + }; + static_assert(sizeof(NTAGNoftHeader) == 0x8); +#pragma pack() + + struct NTAGInfoHeader + { + /* +0x00 */ uint16 rwHeaderOffset; + /* +0x02 */ uint16 rwSize; + /* +0x04 */ uint16 roHeaderOffset; + /* +0x06 */ uint16 roSize; + /* +0x08 */ nfc::NFCUid uid; + /* +0x0F */ uint8 formatVersion; + }; + static_assert(sizeof(NTAGInfoHeader) == 0x10); + + struct NTAGAreaHeader + { + /* +0x00 */ uint16 magic; + /* +0x02 */ uint16 offset; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + }; + static_assert(sizeof(NTAGAreaHeader) == 0x10); + + struct NTAGAreaInfo + { + /* +0x00 */ MPTR data; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + /* +0x10 */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGAreaInfo) == 0x30); + + struct NTAGData + { + /* +0x00 */ nfc::NFCUid uid; + /* +0x07 */ uint8 readOnly; + /* +0x08 */ uint8 formatVersion; + /* +0x09 */ uint8 padding[3]; + /* +0x0C */ NTAGAreaInfo rwInfo; + /* +0x3C */ NTAGAreaInfo roInfo; + /* +0x6C */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGData) == 0x8C); + + sint32 NTAGInit(uint32 chan); + + sint32 NTAGInitEx(uint32 chan); + + sint32 NTAGShutdown(uint32 chan); + + bool NTAGIsInit(uint32 chan); + + void NTAGProc(uint32 chan); + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings); + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context); + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context); + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + void Initialize(); +} diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 058ab07..e49ece9 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -51,6 +51,8 @@ const std::map<LogType, std::string> g_logging_window_mapping {LogType::Socket, "Socket"}, {LogType::Save, "Save"}, {LogType::H264, "H264"}, + {LogType::NFC, "NFC"}, + {LogType::NTAG, "NTAG"}, {LogType::Patches, "Graphic pack patches"}, {LogType::TextureCache, "Texture cache"}, {LogType::TextureReadback, "Texture readback"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 8fbb318..5fd652b 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -44,6 +44,9 @@ enum class LogType : sint32 nlibcurl = 41, PRUDP = 40, + + NFC = 41, + NTAG = 42, }; template <> diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 097d506..cb2e988 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -12,7 +12,7 @@ #include "audio/audioDebuggerWindow.h" #include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/VulkanCanvas.h" -#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "gui/debugger/DebuggerWindow2.h" #include "util/helpers/helpers.h" @@ -261,7 +261,7 @@ public: return false; uint32 nfcError; std::string path = filenames[0].utf8_string(); - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError)) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError)) { GetConfig().AddRecentNfcFile(path); m_window->UpdateNFCMenu(); @@ -749,7 +749,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) return; wxString wxStrFilePath = openFileDialog.GetPath(); uint32 nfcError; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { if (nfcError == NFC_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); @@ -772,7 +772,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) if (!path.empty()) { uint32 nfcError = 0; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { if (nfcError == NFC_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); @@ -2210,6 +2210,8 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("&Socket API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("&Save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("&H264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("&NFC API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("&NTAG API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache)); From 8e8431113a4128330351674ca59771cf203bf8d9 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Fri, 10 May 2024 00:33:31 +0200 Subject: [PATCH 104/130] ntag: Implement NTAGWrite --- src/Cafe/OS/libs/nfc/ndef.cpp | 1 + src/Cafe/OS/libs/nfc/nfc.cpp | 119 +++++++++++++----- src/Cafe/OS/libs/ntag/ntag.cpp | 214 +++++++++++++++++++++++++++++++-- src/Cafe/OS/libs/ntag/ntag.h | 2 +- 4 files changed, 293 insertions(+), 43 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp index f8d87fb..32097cf 100644 --- a/src/Cafe/OS/libs/nfc/ndef.cpp +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -6,6 +6,7 @@ namespace ndef { Record::Record() + : mFlags(0), mTNF(NDEF_TNF_EMPTY) { } diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index 21e9e91..f8f67eb 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -39,6 +39,7 @@ namespace nfc bool hasTag; uint32 nfcStatus; + std::chrono::time_point<std::chrono::system_clock> touchTime; std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; MPTR tagDetectCallback; @@ -146,7 +147,8 @@ namespace nfc // Look for the unknown TNF which contains the data we care about for (const auto& rec : *ndefMsg) { - if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) { + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + { dataSize = rec.GetPayload().size(); cemu_assert(dataSize < 0x200); memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); @@ -174,11 +176,6 @@ namespace nfc { result = -0xBFE; } - - // Clear tag status after read - // TODO this is not really nice here - ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; - ctx->tag = {}; } else { @@ -194,9 +191,42 @@ namespace nfc ctx->state = NFC_STATE_IDLE; - // TODO write to file + sint32 result; - PPCCoreCallback(ctx->writeCallback, chan, 0, ctx->writeContext); + if (ctx->tag) + { + // Update tag NDEF data + ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); + + // TODO remove this once writing is confirmed working + fs::path newPath = ctx->tagPath; + if (newPath.extension() != ".bak") + { + newPath += ".bak"; + } + cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + + // open file for writing + FileStream* fs = FileStream::createFile2(newPath); + if (!fs) + { + result = -0x2DE; + } + else + { + auto tagBytes = ctx->tag->ToBytes(); + fs->writeData(tagBytes.data(), tagBytes.size()); + delete fs; + + result = 0; + } + } + else + { + result = -0x2DD; + } + + PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); } void __NFCHandleAbort(uint32 chan) @@ -231,6 +261,29 @@ namespace nfc PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); } + bool __NFCShouldHandleState(NFCContext* ctx) + { + // Always handle abort + if (ctx->state == NFC_STATE_ABORT) + { + return true; + } + + // Do we have a tag? + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + return true; + } + + // Did the timeout expire? + if (ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + return true; + } + + return false; + } + void NFCProc(uint32 chan) { cemu_assert(chan < 2); @@ -242,6 +295,11 @@ namespace nfc return; } + if (ctx->state == NFC_STATE_INITIALIZED) + { + ctx->state = NFC_STATE_IDLE; + } + // Check if the detect callback should be called if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { @@ -254,6 +312,14 @@ namespace nfc ctx->hasTag = true; } + + // Check if the tag should be removed again + if (ctx->touchTime + std::chrono::seconds(2) < std::chrono::system_clock::now()) + { + ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; + ctx->tag = {}; + ctx->tagPath = ""; + } } else { @@ -268,33 +334,25 @@ namespace nfc } } - switch (ctx->state) + if (__NFCShouldHandleState(ctx)) { - case NFC_STATE_INITIALIZED: - ctx->state = NFC_STATE_IDLE; - break; - case NFC_STATE_IDLE: - break; - case NFC_STATE_READ: - // Do we have a tag or did the timeout expire? - if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) + switch (ctx->state) { + case NFC_STATE_READ: __NFCHandleRead(chan); - } - break; - case NFC_STATE_WRITE: - __NFCHandleWrite(chan); - break; - case NFC_STATE_ABORT: - __NFCHandleAbort(chan); - break; - case NFC_STATE_RAW: - // Do we have a tag or did the timeout expire? - if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) - { + break; + case NFC_STATE_WRITE: + __NFCHandleWrite(chan); + break; + case NFC_STATE_ABORT: + __NFCHandleAbort(chan); + break; + case NFC_STATE_RAW: __NFCHandleRaw(chan); + break; + default: + break; } - break; } } @@ -589,6 +647,7 @@ namespace nfc ctx->nfcStatus |= NFC_STATUS_HAS_TAG; ctx->tagPath = filePath; + ctx->touchTime = std::chrono::system_clock::now(); *nfcError = NFC_ERROR_NONE; return true; diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp index 8bdbb66..18ed798 100644 --- a/src/Cafe/OS/libs/ntag/ntag.cpp +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -9,7 +9,10 @@ namespace ntag { struct NTAGWriteData { - + uint16 size; + uint8 data[0x1C8]; + nfc::NFCUid uid; + nfc::NFCUid uidMask; }; NTAGWriteData gWriteData[2]; @@ -72,7 +75,7 @@ namespace ntag { gFormatSettings.version = formatSettings->version; gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); - gFormatSettings.indentifyCode = _swapEndianU32(formatSettings->indentifyCode); + gFormatSettings.identifyCode = _swapEndianU32(formatSettings->identifyCode); } void __NTAGDetectCallback(PPCInterpreter_t* hCPU) @@ -220,7 +223,7 @@ namespace ntag return true; } - sint32 __NTAGDecryptData(void* decryptedData, void* rawData) + sint32 __NTAGDecryptData(void* decryptedData, const void* rawData) { StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; @@ -256,7 +259,41 @@ namespace ntag sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) { - // TODO + if (infoHeader->formatVersion != gFormatSettings.version || noftHeader->version != 0x1) + { + cemuLog_log(LogType::Force, "Invalid format version"); + return -0x2710; + } + + if (_swapEndianU32(noftHeader->magic) != 0x4E4F4654 /* 'NOFT' */ || + _swapEndianU16(rwHeader->magic) != 0x5257 /* 'RW' */ || + _swapEndianU16(roHeader->magic) != 0x524F /* 'RO' */) + { + cemuLog_log(LogType::Force, "Invalid header magic"); + return -0x270F; + } + + if (_swapEndianU32(rwHeader->makerCode) != gFormatSettings.makerCode || + _swapEndianU32(roHeader->makerCode) != gFormatSettings.makerCode) + { + cemuLog_log(LogType::Force, "Invalid maker code"); + return -0x270E; + } + + if (infoHeader->formatVersion != 0 && + (_swapEndianU32(rwHeader->identifyCode) != gFormatSettings.identifyCode || + _swapEndianU32(roHeader->identifyCode) != gFormatSettings.identifyCode)) + { + cemuLog_log(LogType::Force, "Invalid identify code"); + return -0x2709; + } + + if (_swapEndianU16(rwHeader->size) + _swapEndianU16(roHeader->size) != 0x130) + { + cemuLog_log(LogType::Force, "Invalid data size"); + return -0x270D; + } + return 0; } @@ -264,8 +301,13 @@ namespace ntag { memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); + + cemu_assert(_swapEndianU16(infoHeader->rwHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + cemu_assert(_swapEndianU16(infoHeader->roHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); + return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); } @@ -317,7 +359,7 @@ namespace ntag ppcDefineParamPtr(lockedData, void, 7); ppcDefineParamPtr(context, void, 8); - uint8 rawData[0x1C8]; + uint8 rawData[0x1C8]{}; StackAllocator<NTAGData> readResult; StackAllocator<uint8, 0x1C8> rwData; StackAllocator<uint8, 0x1C8> roData; @@ -331,6 +373,9 @@ namespace ntag error = __NTAGConvertNFCError(error); if (error == 0) { + memset(rwData.GetPointer(), 0, 0x1C8); + memset(roData.GetPointer(), 0, 0x1C8); + // Copy raw and locked data into a contigous buffer memcpy(rawData, data, dataSize); memcpy(rawData + dataSize, lockedData, lockedDataSize); @@ -388,28 +433,173 @@ namespace ntag return __NTAGConvertNFCError(result); } + sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) + { + StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Encrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 1, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(encryptedData, nfcRawData->data, 0x1C8); + + return result; + } + + sint32 __NTAGPrepareWriteData(void* outBuffer, uint32 dataSize, const void* data, const void* tagData, NTAGNoftHeader* noftHeader, NTAGAreaHeader* rwHeader) + { + uint8 decryptedBuffer[0x1C8]; + uint8 encryptedBuffer[0x1C8]; + + memcpy(decryptedBuffer, tagData, 0x1C8); + + // Fill the rest of the rw area with random data + if (dataSize < _swapEndianU16(rwHeader->size)) + { + uint8 randomBuffer[0x1C8]; + for (int i = 0; i < sizeof(randomBuffer); i++) + { + randomBuffer[i] = rand() & 0xFF; + } + + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset) + dataSize, randomBuffer, _swapEndianU16(rwHeader->size) - dataSize); + } + + // Make sure the data fits into the rw area + if (_swapEndianU16(rwHeader->size) < dataSize) + { + return -0x270D; + } + + // Update write count (check for overflow) + if ((_swapEndianU16(noftHeader->writeCount) & 0x7fff) == 0x7fff) + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) & 0x8000); + } + else + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); + } + + memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(noftHeader)); + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); + + // Encrypt + sint32 result = __NTAGEncryptData(encryptedBuffer, decryptedBuffer); + if (result < 0) + { + return result; + } + + memcpy(outBuffer, encryptedBuffer, _swapEndianU16(rwHeader->size) + 0x28); + return 0; + } + + void __NTAGWriteCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]{}; + uint8 rwData[0x1C8]{}; + uint8 roData[0x1C8]{}; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + uint8 writeBuffer[0x1C8]{}; + + error = __NTAGConvertNFCError(error); + if (error == 0) + { + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData, roData, uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to parse data before write"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Prepare data + memcpy(rawData + _swapEndianU16(infoHeader.rwHeaderOffset), &rwHeader, sizeof(rwHeader)); + memcpy(rawData + _swapEndianU16(infoHeader.roHeaderOffset), &roHeader, sizeof(roHeader)); + memcpy(rawData + _swapEndianU16(roHeader.offset), roData, _swapEndianU16(roHeader.size)); + error = __NTAGPrepareWriteData(writeBuffer, gWriteData[chan].size, gWriteData[chan].data, rawData, &noftHeader, &rwHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to prepare write data"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Write data to tag + error = nfc::NFCWrite(chan, 200, &gWriteData[chan].uid, &gWriteData[chan].uidMask, + _swapEndianU16(rwHeader.size) + 0x28, writeBuffer, RPLLoader_MakePPCCallable(__NTAGWriteCallback), context); + if (error >= 0) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + error = __NTAGConvertNFCError(error); + } + + PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); osLib_returnFromFunction(hCPU, 0); } sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) { cemu_assert(chan < 2); + cemu_assert(rwSize < 0x1C8); gWriteCallbacks[chan] = callback; - nfc::NFCUid _uid{}, _uidMask{}; if (uid) { - memcpy(&_uid, uid, sizeof(*uid)); + memcpy(&gWriteData[chan].uid, uid, sizeof(nfc::NFCUid)); } - memset(_uidMask.uid, 0xff, sizeof(_uidMask.uid)); + memset(&gWriteData[chan].uidMask, 0xff, sizeof(nfc::NFCUid)); - // TODO save write data + gWriteData[chan].size = rwSize; + memcpy(gWriteData[chan].data, rwData, rwSize); - // TODO we probably don't need to read first here - sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); + sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); return __NTAGConvertNFCError(result); } @@ -418,7 +608,7 @@ namespace ntag cemu_assert(chan < 2); // TODO - return 0; + return -1; } void Initialize() diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h index 1174e6b..697c065 100644 --- a/src/Cafe/OS/libs/ntag/ntag.h +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -7,7 +7,7 @@ namespace ntag { /* +0x00 */ uint8 version; /* +0x04 */ uint32 makerCode; - /* +0x08 */ uint32 indentifyCode; + /* +0x08 */ uint32 identifyCode; /* +0x0C */ uint8 reserved[0x1C]; }; static_assert(sizeof(NTAGFormatSettings) == 0x28); From 41fe598e333920196aa8fd6033aaa78172e21655 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Fri, 17 May 2024 14:19:51 +0200 Subject: [PATCH 105/130] nfc: Implement UID filter --- src/Cafe/OS/libs/nfc/nfc.cpp | 118 ++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 42 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index f8f67eb..4505f3b 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -41,6 +41,10 @@ namespace nfc uint32 nfcStatus; std::chrono::time_point<std::chrono::system_clock> touchTime; std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; + struct { + NFCUid uid; + NFCUid mask; + } filter; MPTR tagDetectCallback; void* tagDetectContext; @@ -124,6 +128,19 @@ namespace nfc return gNFCContexts[chan].isInitialized; } + bool __NFCCompareUid(NFCUid* uid, NFCUid* filterUid, NFCUid* filterMask) + { + for (int i = 0; i < sizeof(uid->uid); i++) + { + if ((uid->uid[i] & filterMask->uid[i]) != filterUid->uid[i]) + { + return false; + } + } + + return true; + } + void __NFCHandleRead(uint32 chan) { NFCContext* ctx = &gNFCContexts[chan]; @@ -140,32 +157,38 @@ namespace nfc if (ctx->tag) { - // Try to parse ndef message - auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); - if (ndefMsg) + // Compare UID + memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(uid.GetPointer(), &ctx->filter.uid, &ctx->filter.mask)) { - // Look for the unknown TNF which contains the data we care about - for (const auto& rec : *ndefMsg) + // Try to parse ndef message + auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); + if (ndefMsg) { - if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + // Look for the unknown TNF which contains the data we care about + for (const auto& rec : *ndefMsg) { - dataSize = rec.GetPayload().size(); - cemu_assert(dataSize < 0x200); - memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); - break; + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + { + dataSize = rec.GetPayload().size(); + cemu_assert(dataSize < 0x200); + memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); + break; + } } - } - if (dataSize) - { - // Get locked data - lockedDataSize = ctx->tag->GetLockedArea().size(); - memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); + if (dataSize) + { + // Get locked data + lockedDataSize = ctx->tag->GetLockedArea().size(); + memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); - // Fill in uid - memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); - - result = 0; + result = 0; + } + else + { + result = -0xBFE; + } } else { @@ -174,7 +197,7 @@ namespace nfc } else { - result = -0xBFE; + result = -0x1F6; } } else @@ -195,30 +218,39 @@ namespace nfc if (ctx->tag) { - // Update tag NDEF data - ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); - - // TODO remove this once writing is confirmed working - fs::path newPath = ctx->tagPath; - if (newPath.extension() != ".bak") + NFCUid uid; + memcpy(&uid, ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(&uid, &ctx->filter.uid, &ctx->filter.mask)) { - newPath += ".bak"; - } - cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + // Update tag NDEF data + ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); - // open file for writing - FileStream* fs = FileStream::createFile2(newPath); - if (!fs) - { - result = -0x2DE; + // TODO remove this once writing is confirmed working + fs::path newPath = ctx->tagPath; + if (newPath.extension() != ".bak") + { + newPath += ".bak"; + } + cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + + // open file for writing + FileStream* fs = FileStream::createFile2(newPath); + if (!fs) + { + result = -0x2DE; + } + else + { + auto tagBytes = ctx->tag->ToBytes(); + fs->writeData(tagBytes.data(), tagBytes.size()); + delete fs; + + result = 0; + } } else { - auto tagBytes = ctx->tag->ToBytes(); - fs->writeData(tagBytes.data(), tagBytes.size()); - delete fs; - - result = 0; + result = -0x2F6; } } else @@ -548,7 +580,8 @@ namespace nfc ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } - // TODO uid filter? + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); return 0; } @@ -598,7 +631,8 @@ namespace nfc ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } - // TODO uid filter? + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); return 0; } From 8fe69cd0fb6be8d916a963290e7c5525c0848bb5 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 16:38:52 +0200 Subject: [PATCH 106/130] Properly implement NFC result codes --- src/Cafe/OS/libs/nfc/nfc.cpp | 133 ++++++++++++++++++--------------- src/Cafe/OS/libs/nfc/nfc.h | 36 ++++++++- src/Cafe/OS/libs/ntag/ntag.cpp | 47 ++++++++---- src/Cafe/OS/libs/ntag/ntag.h | 7 ++ src/gui/MainWindow.cpp | 18 ++--- 5 files changed, 153 insertions(+), 88 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index 4505f3b..818c733 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -7,27 +7,25 @@ #include "TagV0.h" #include "ndef.h" -// TODO move errors to header and allow ntag to convert them +#define NFC_MODE_INVALID -1 +#define NFC_MODE_IDLE 0 +#define NFC_MODE_ACTIVE 1 -#define NFC_MODE_INVALID -1 -#define NFC_MODE_IDLE 0 -#define NFC_MODE_ACTIVE 1 +#define NFC_STATE_UNINITIALIZED 0x0 +#define NFC_STATE_INITIALIZED 0x1 +#define NFC_STATE_IDLE 0x2 +#define NFC_STATE_READ 0x3 +#define NFC_STATE_WRITE 0x4 +#define NFC_STATE_ABORT 0x5 +#define NFC_STATE_FORMAT 0x6 +#define NFC_STATE_SET_READ_ONLY 0x7 +#define NFC_STATE_TAG_PRESENT 0x8 +#define NFC_STATE_DETECT 0x9 +#define NFC_STATE_SEND_RAW_DATA 0xA -#define NFC_STATE_UNINITIALIZED 0x0 -#define NFC_STATE_INITIALIZED 0x1 -#define NFC_STATE_IDLE 0x2 -#define NFC_STATE_READ 0x3 -#define NFC_STATE_WRITE 0x4 -#define NFC_STATE_ABORT 0x5 -#define NFC_STATE_FORMAT 0x6 -#define NFC_STATE_SET_READ_ONLY 0x7 -#define NFC_STATE_TAG_PRESENT 0x8 -#define NFC_STATE_DETECT 0x9 -#define NFC_STATE_RAW 0xA - -#define NFC_STATUS_COMMAND_COMPLETE 0x1 -#define NFC_STATUS_READY 0x2 -#define NFC_STATUS_HAS_TAG 0x4 +#define NFC_STATUS_COMMAND_COMPLETE 0x1 +#define NFC_STATUS_READY 0x2 +#define NFC_STATUS_HAS_TAG 0x4 namespace nfc { @@ -107,7 +105,7 @@ namespace nfc ctx->isInitialized = true; ctx->state = NFC_STATE_INITIALIZED; - return 0; + return NFC_RESULT_SUCCESS; } sint32 NFCShutdown(uint32 chan) @@ -118,7 +116,7 @@ namespace nfc __NFCClearContext(ctx); - return 0; + return NFC_RESULT_SUCCESS; } bool NFCIsInit(uint32 chan) @@ -183,26 +181,26 @@ namespace nfc lockedDataSize = ctx->tag->GetLockedArea().size(); memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); - result = 0; + result = NFC_RESULT_SUCCESS; } else { - result = -0xBFE; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); } } else { - result = -0xBFE; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); } } else { - result = -0x1F6; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UID_MISMATCH); } } else { - result = -0x1DD; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_NO_TAG); } PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); @@ -231,13 +229,13 @@ namespace nfc { newPath += ".bak"; } - cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + cemuLog_log(LogType::NFC, "Saving tag as {}...", newPath.string()); // open file for writing FileStream* fs = FileStream::createFile2(newPath); if (!fs) { - result = -0x2DE; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); } else { @@ -245,17 +243,17 @@ namespace nfc fs->writeData(tagBytes.data(), tagBytes.size()); delete fs; - result = 0; + result = NFC_RESULT_SUCCESS; } } else { - result = -0x2F6; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UID_MISMATCH); } } else { - result = -0x2DD; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_NO_TAG); } PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); @@ -279,11 +277,11 @@ namespace nfc sint32 result; if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { - result = 0; + result = NFC_RESULT_SUCCESS; } else { - result = -0x9DD; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG); } // We don't actually send any commands/responses @@ -379,12 +377,15 @@ namespace nfc case NFC_STATE_ABORT: __NFCHandleAbort(chan); break; - case NFC_STATE_RAW: + case NFC_STATE_SEND_RAW_DATA: __NFCHandleRaw(chan); break; default: break; } + + // Return back to idle mode + ctx->mode = NFC_MODE_IDLE; } } @@ -410,17 +411,17 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0xAE0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_UNINITIALIZED); } if (ctx->state == NFC_STATE_UNINITIALIZED) { - return -0xADF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_INVALID_STATE); } ctx->mode = mode; - return 0; + return NFC_RESULT_SUCCESS; } void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) @@ -440,19 +441,30 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x6E0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_UNINITIALIZED); } if (ctx->state <= NFC_STATE_IDLE) { - return -0x6DF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_INVALID_STATE); } ctx->state = NFC_STATE_ABORT; ctx->abortCallback = callback; ctx->abortContext = context; - return 0; + return NFC_RESULT_SUCCESS; + } + + sint32 __NFCConvertGetTagInfoResult(sint32 result) + { + if (result == NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_GET_TAG_INFO, NFC_RESULT_TAG_INFO_TIMEOUT); + } + + // TODO convert the rest of the results + return result; } void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) @@ -465,8 +477,7 @@ namespace nfc NFCContext* ctx = &gNFCContexts[chan]; - // TODO convert error - error = error; + error = __NFCConvertGetTagInfoResult(error); if (error == 0 && ctx->tag) { // this is usually parsed from response data @@ -496,7 +507,7 @@ namespace nfc ctx->getTagInfoCallback = callback; sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); - return result; // TODO convert result + return __NFCConvertGetTagInfoResult(result); } sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) @@ -507,26 +518,26 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x9E0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_UNINITIALIZED); } // Only allow discovery if (!startDiscovery) { - return -0x9DC; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { - return -0x9DC; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { - return -0x9DF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_STATE); } - ctx->state = NFC_STATE_RAW; + ctx->state = NFC_STATE_SEND_RAW_DATA; ctx->rawCallback = callback; ctx->rawContext = context; @@ -540,7 +551,7 @@ namespace nfc ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } - return 0; + return NFC_RESULT_SUCCESS; } sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) @@ -551,21 +562,19 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x1E0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UNINITIALIZED); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { - return -0x1DC; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { - return -0x1DF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_STATE); } - cemuLog_log(LogType::NFC, "starting read"); - ctx->state = NFC_STATE_READ; ctx->readCallback = callback; ctx->readContext = context; @@ -583,7 +592,7 @@ namespace nfc memcpy(&ctx->filter.uid, uid, sizeof(*uid)); memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); - return 0; + return NFC_RESULT_SUCCESS; } sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) @@ -594,17 +603,17 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x2e0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UNINITIALIZED); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { - return -0x2dc; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { - return -0x1df; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_STATE); } // Create unknown record which contains the rw area @@ -634,7 +643,7 @@ namespace nfc memcpy(&ctx->filter.uid, uid, sizeof(*uid)); memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); - return 0; + return NFC_RESULT_SUCCESS; } void Initialize() @@ -668,14 +677,14 @@ namespace nfc auto nfcData = FileStream::LoadIntoMemory(filePath); if (!nfcData) { - *nfcError = NFC_ERROR_NO_ACCESS; + *nfcError = NFC_TOUCH_TAG_ERROR_NO_ACCESS; return false; } ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); if (!ctx->tag) { - *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + *nfcError = NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT; return false; } @@ -683,7 +692,7 @@ namespace nfc ctx->tagPath = filePath; ctx->touchTime = std::chrono::system_clock::now(); - *nfcError = NFC_ERROR_NONE; + *nfcError = NFC_TOUCH_TAG_ERROR_NONE; return true; } } diff --git a/src/Cafe/OS/libs/nfc/nfc.h b/src/Cafe/OS/libs/nfc/nfc.h index 2ebdd2a..ea959cd 100644 --- a/src/Cafe/OS/libs/nfc/nfc.h +++ b/src/Cafe/OS/libs/nfc/nfc.h @@ -1,9 +1,39 @@ #pragma once // CEMU NFC error codes -#define NFC_ERROR_NONE (0) -#define NFC_ERROR_NO_ACCESS (1) -#define NFC_ERROR_INVALID_FILE_FORMAT (2) +#define NFC_TOUCH_TAG_ERROR_NONE (0) +#define NFC_TOUCH_TAG_ERROR_NO_ACCESS (1) +#define NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT (2) + +// NFC result base +#define NFC_RESULT_BASE_INIT (-0x100) +#define NFC_RESULT_BASE_READ (-0x200) +#define NFC_RESULT_BASE_WRITE (-0x300) +#define NFC_RESULT_BASE_FORMAT (-0x400) +#define NFC_RESULT_BASE_SET_READ_ONLY (-0x500) +#define NFC_RESULT_BASE_IS_TAG_PRESENT (-0x600) +#define NFC_RESULT_BASE_ABORT (-0x700) +#define NFC_RESULT_BASE_SHUTDOWN (-0x800) +#define NFC_RESULT_BASE_DETECT (-0x900) +#define NFC_RESULT_BASE_SEND_RAW_DATA (-0xA00) +#define NFC_RESULT_BASE_SET_MODE (-0xB00) +#define NFC_RESULT_BASE_TAG_PARSE (-0xC00) +#define NFC_RESULT_BASE_GET_TAG_INFO (-0x1400) + +// NFC result status +#define NFC_RESULT_NO_TAG (0x01) +#define NFC_RESULT_INVALID_TAG (0x02) +#define NFC_RESULT_UID_MISMATCH (0x0A) +#define NFC_RESULT_UNINITIALIZED (0x20) +#define NFC_RESULT_INVALID_STATE (0x21) +#define NFC_RESULT_INVALID_MODE (0x24) +#define NFC_RESULT_TAG_INFO_TIMEOUT (0x7A) + +// Result macros +#define NFC_RESULT_SUCCESS (0) +#define NFC_RESULT_BASE_MASK (0xFFFFFF00) +#define NFC_RESULT_MASK (0x000000FF) +#define NFC_MAKE_RESULT(base, result) ((base) | (result)) #define NFC_PROTOCOL_T1T 0x1 #define NFC_PROTOCOL_T2T 0x2 diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp index 18ed798..2461779 100644 --- a/src/Cafe/OS/libs/ntag/ntag.cpp +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -26,10 +26,27 @@ namespace ntag MPTR gReadCallbacks[2]; MPTR gWriteCallbacks[2]; - sint32 __NTAGConvertNFCError(sint32 error) + sint32 __NTAGConvertNFCResult(sint32 result) { - // TODO - return error; + if (result == NFC_RESULT_SUCCESS) + { + return NTAG_RESULT_SUCCESS; + } + + switch (result & NFC_RESULT_MASK) + { + case NFC_RESULT_UNINITIALIZED: + return NTAG_RESULT_UNINITIALIZED; + case NFC_RESULT_INVALID_STATE: + return NTAG_RESULT_INVALID_STATE; + case NFC_RESULT_NO_TAG: + return NTAG_RESULT_NO_TAG; + case NFC_RESULT_UID_MISMATCH: + return NTAG_RESULT_UID_MISMATCH; + } + + // TODO convert more errors + return NTAG_RESULT_INVALID; } sint32 NTAGInit(uint32 chan) @@ -40,7 +57,7 @@ namespace ntag sint32 NTAGInitEx(uint32 chan) { sint32 result = nfc::NFCInitEx(chan, 1); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } sint32 NTAGShutdown(uint32 chan) @@ -58,7 +75,7 @@ namespace ntag gReadCallbacks[chan] = MPTR_NULL; gWriteCallbacks[chan] = MPTR_NULL; - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } bool NTAGIsInit(uint32 chan) @@ -105,7 +122,7 @@ namespace ntag ppcDefineParamS32(error, 1); ppcDefineParamPtr(context, void, 2); - PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); osLib_returnFromFunction(hCPU, 0); } @@ -118,7 +135,7 @@ namespace ntag gAbortCallbacks[chan] = callback; sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) @@ -370,7 +387,7 @@ namespace ntag readResult->readOnly = readOnly; - error = __NTAGConvertNFCError(error); + error = __NTAGConvertNFCResult(error); if (error == 0) { memset(rwData.GetPointer(), 0, 0x1C8); @@ -430,7 +447,7 @@ namespace ntag } sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) @@ -512,7 +529,7 @@ namespace ntag ppcDefineParamS32(error, 1); ppcDefineParamPtr(context, void, 2); - PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); osLib_returnFromFunction(hCPU, 0); } @@ -538,7 +555,7 @@ namespace ntag NTAGAreaHeader roHeader; uint8 writeBuffer[0x1C8]{}; - error = __NTAGConvertNFCError(error); + error = __NTAGConvertNFCResult(error); if (error == 0) { // Copy raw and locked data into a contigous buffer @@ -576,7 +593,7 @@ namespace ntag return; } - error = __NTAGConvertNFCError(error); + error = __NTAGConvertNFCResult(error); } PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); @@ -600,7 +617,7 @@ namespace ntag memcpy(gWriteData[chan].data, rwData, rwSize); sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) @@ -608,7 +625,9 @@ namespace ntag cemu_assert(chan < 2); // TODO - return -1; + cemu_assert_debug(false); + + return NTAG_RESULT_INVALID; } void Initialize() diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h index 697c065..68f1801 100644 --- a/src/Cafe/OS/libs/ntag/ntag.h +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -1,6 +1,13 @@ #pragma once #include "Cafe/OS/libs/nfc/nfc.h" +#define NTAG_RESULT_SUCCESS (0) +#define NTAG_RESULT_UNINITIALIZED (-0x3E7) +#define NTAG_RESULT_INVALID_STATE (-0x3E6) +#define NTAG_RESULT_NO_TAG (-0x3E5) +#define NTAG_RESULT_INVALID (-0x3E1) +#define NTAG_RESULT_UID_MISMATCH (-0x3DB) + namespace ntag { struct NTAGFormatSettings diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index cb2e988..33e2cdc 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -269,10 +269,10 @@ public: } else { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } @@ -751,10 +751,10 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) uint32 nfcError; if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { @@ -774,10 +774,10 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) uint32 nfcError = 0; if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { From eb1983daa6e46dfa09ad76eba86c5b636fe0b826 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 17:27:49 +0200 Subject: [PATCH 107/130] nfc: Remove backup path --- src/Cafe/OS/libs/nfc/nfc.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index 818c733..c680936 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -223,16 +223,8 @@ namespace nfc // Update tag NDEF data ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); - // TODO remove this once writing is confirmed working - fs::path newPath = ctx->tagPath; - if (newPath.extension() != ".bak") - { - newPath += ".bak"; - } - cemuLog_log(LogType::NFC, "Saving tag as {}...", newPath.string()); - // open file for writing - FileStream* fs = FileStream::createFile2(newPath); + FileStream* fs = FileStream::openFile2(ctx->tagPath, true); if (!fs) { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); From a115921b43d39c24fafd387d9c87190168422583 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 19:56:56 +0200 Subject: [PATCH 108/130] Fix inconsistency with int types --- src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp | 22 +++++++------- src/Cafe/OS/libs/nfc/TLV.cpp | 12 ++++---- src/Cafe/OS/libs/nfc/TagV0.cpp | 42 +++++++++++++------------- src/Cafe/OS/libs/nfc/TagV0.h | 8 ++--- src/Cafe/OS/libs/nfc/ndef.cpp | 26 ++++++++-------- src/Cafe/OS/libs/nfc/ndef.h | 4 +-- src/Cafe/OS/libs/nfc/nfc.cpp | 4 +-- src/Cafe/OS/libs/nfc/stream.cpp | 12 ++++---- 8 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp index ff8ba2b..1ceb16d 100644 --- a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp @@ -71,9 +71,9 @@ namespace iosu return CCR_NFC_ERROR; } - sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32_t inSize, void* outData, uint32_t outSize) + sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32 inSize, void* outData, uint32 outSize) { - uint8_t tmpIv[0x10]; + uint8 tmpIv[0x10]; memcpy(tmpIv, ivNonce, sizeof(tmpIv)); memcpy(outData, inData, inSize); @@ -81,7 +81,7 @@ namespace iosu return 0; } - sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32_t nameSize, const uint8* inData, uint32_t inSize, uint8* outData, uint32_t outSize) + sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32 nameSize, const uint8* inData, uint32 inSize, uint8* outData, uint32 outSize) { if (nameSize != 0xe || outSize != 0x40) { @@ -89,13 +89,13 @@ namespace iosu } // Create a buffer containing 2 counter bytes, the key name, and the key data - uint8_t buffer[0x50]; + uint8 buffer[0x50]; buffer[0] = 0; buffer[1] = 0; memcpy(buffer + 2, name, nameSize); memcpy(buffer + nameSize + 2, inData, inSize); - uint16_t counter = 0; + uint16 counter = 0; while (outSize > 0) { // Set counter bytes and increment counter @@ -118,9 +118,9 @@ namespace iosu sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) { - uint8_t lockedSecretBuffer[0x40] = { 0 }; - uint8_t unfixedInfosBuffer[0x40] = { 0 }; - uint8_t outBuffer[0x40] = { 0 }; + uint8 lockedSecretBuffer[0x40] = { 0 }; + uint8 unfixedInfosBuffer[0x40] = { 0 }; + uint8 outBuffer[0x40] = { 0 }; // Fill the locked secret buffer memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); @@ -193,7 +193,7 @@ namespace iosu sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) { // Decrypt key generation salt - uint8_t keyGenSalt[0x20]; + uint8 keyGenSalt[0x20]; sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); if (res != 0) { @@ -227,7 +227,7 @@ namespace iosu } // Verify HMACs - uint8_t hmacBuffer[0x20]; + uint8 hmacBuffer[0x20]; uint32 hmacLen = sizeof(hmacBuffer); if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) @@ -258,7 +258,7 @@ namespace iosu } else { - uint8_t hmacBuffer[0x20]; + uint8 hmacBuffer[0x20]; uint32 hmacLen = sizeof(hmacBuffer); if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) diff --git a/src/Cafe/OS/libs/nfc/TLV.cpp b/src/Cafe/OS/libs/nfc/TLV.cpp index 9953642..2650858 100644 --- a/src/Cafe/OS/libs/nfc/TLV.cpp +++ b/src/Cafe/OS/libs/nfc/TLV.cpp @@ -25,7 +25,7 @@ std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) while (stream.GetRemaining() > 0 && !hasTerminator) { // Read the tag - uint8_t byte; + uint8 byte; stream >> byte; Tag tag = static_cast<Tag>(byte); @@ -43,7 +43,7 @@ std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) default: { // Read the length - uint16_t length; + uint16 length; stream >> byte; length = byte; @@ -85,7 +85,7 @@ std::vector<std::byte> TLV::ToBytes() const VectorStream stream(bytes, std::endian::big); // Write tag - stream << std::uint8_t(mTag); + stream << uint8(mTag); switch (mTag) { @@ -99,12 +99,12 @@ std::vector<std::byte> TLV::ToBytes() const // Write length (decide if as a 8-bit or 16-bit value) if (mValue.size() >= 0xff) { - stream << std::uint8_t(0xff); - stream << std::uint16_t(mValue.size()); + stream << uint8(0xff); + stream << uint16(mValue.size()); } else { - stream << std::uint8_t(mValue.size()); + stream << uint8(mValue.size()); } // Write value diff --git a/src/Cafe/OS/libs/nfc/TagV0.cpp b/src/Cafe/OS/libs/nfc/TagV0.cpp index 8b5a814..41b5c7a 100644 --- a/src/Cafe/OS/libs/nfc/TagV0.cpp +++ b/src/Cafe/OS/libs/nfc/TagV0.cpp @@ -9,17 +9,17 @@ namespace constexpr std::size_t kTagSize = 512u; constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); -constexpr std::uint8_t kLockbyteBlock0 = 0xe; -constexpr std::uint8_t kLockbytesStart0 = 0x0; -constexpr std::uint8_t kLockbytesEnd0 = 0x2; -constexpr std::uint8_t kLockbyteBlock1 = 0xf; -constexpr std::uint8_t kLockbytesStart1 = 0x2; -constexpr std::uint8_t kLockbytesEnd1 = 0x8; +constexpr uint8 kLockbyteBlock0 = 0xe; +constexpr uint8 kLockbytesStart0 = 0x0; +constexpr uint8 kLockbytesEnd0 = 0x2; +constexpr uint8 kLockbyteBlock1 = 0xf; +constexpr uint8 kLockbytesStart1 = 0x2; +constexpr uint8 kLockbytesEnd1 = 0x8; -constexpr std::uint8_t kNDEFMagicNumber = 0xe1; +constexpr uint8 kNDEFMagicNumber = 0xe1; // These blocks are not part of the locked area -constexpr bool IsBlockLockedOrReserved(std::uint8_t blockIdx) +constexpr bool IsBlockLockedOrReserved(uint8 blockIdx) { // Block 0 is the UID if (blockIdx == 0x0) @@ -153,7 +153,7 @@ std::vector<std::byte> TagV0::ToBytes() const // The rest will be the data area auto dataIterator = dataArea.begin(); - for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) { // All blocks which aren't locked make up the dataArea if (!IsBlockLocked(currentBlock)) @@ -189,15 +189,15 @@ void TagV0::SetNDEFData(const std::span<const std::byte>& data) bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) { - std::uint8_t currentBlock = 0; + uint8 currentBlock = 0; // Start by parsing the first set of lock bytes - for (std::uint8_t i = kLockbytesStart0; i < kLockbytesEnd0; i++) + for (uint8 i = kLockbytesStart0; i < kLockbytesEnd0; i++) { - std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock0 * sizeof(Block) + i]); + uint8 lockByte = uint8(data[kLockbyteBlock0 * sizeof(Block) + i]); // Iterate over the individual bits in the lock byte - for (std::uint8_t j = 0; j < 8; j++) + for (uint8 j = 0; j < 8; j++) { // Is block locked? if (lockByte & (1u << j)) @@ -221,11 +221,11 @@ bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) } // Parse the second set of lock bytes - for (std::uint8_t i = kLockbytesStart1; i < kLockbytesEnd1; i++) { - std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock1 * sizeof(Block) + i]); + for (uint8 i = kLockbytesStart1; i < kLockbytesEnd1; i++) { + uint8 lockByte = uint8(data[kLockbyteBlock1 * sizeof(Block) + i]); // Iterate over the individual bits in the lock byte - for (std::uint8_t j = 0; j < 8; j++) + for (uint8 j = 0; j < 8; j++) { // Is block locked? if (lockByte & (1u << j)) @@ -251,14 +251,14 @@ bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) return true; } -bool TagV0::IsBlockLocked(std::uint8_t blockIdx) const +bool TagV0::IsBlockLocked(uint8 blockIdx) const { return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); } bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea) { - for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) { // All blocks which aren't locked make up the dataArea if (!IsBlockLocked(currentBlock)) @@ -274,7 +274,7 @@ bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<st bool TagV0::ValidateCapabilityContainer() { // NDEF Magic Number - std::uint8_t nmn = mCapabilityContainer[0]; + uint8 nmn = mCapabilityContainer[0]; if (nmn != kNDEFMagicNumber) { cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); @@ -282,7 +282,7 @@ bool TagV0::ValidateCapabilityContainer() } // Version Number - std::uint8_t vno = mCapabilityContainer[1]; + uint8 vno = mCapabilityContainer[1]; if (vno >> 4 != 1) { cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); @@ -290,7 +290,7 @@ bool TagV0::ValidateCapabilityContainer() } // Tag memory size - std::uint8_t tms = mCapabilityContainer[2]; + uint8 tms = mCapabilityContainer[2]; if (8u * (tms + 1) < kTagSize) { cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); diff --git a/src/Cafe/OS/libs/nfc/TagV0.h b/src/Cafe/OS/libs/nfc/TagV0.h index 1d0e88d..72c321b 100644 --- a/src/Cafe/OS/libs/nfc/TagV0.h +++ b/src/Cafe/OS/libs/nfc/TagV0.h @@ -26,13 +26,13 @@ public: private: bool ParseLockedArea(const std::span<const std::byte>& data); - bool IsBlockLocked(std::uint8_t blockIdx) const; + bool IsBlockLocked(uint8 blockIdx) const; bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea); bool ValidateCapabilityContainer(); - std::map<std::uint8_t, Block> mLockedOrReservedBlocks; - std::map<std::uint8_t, Block> mLockedBlocks; - std::array<std::uint8_t, 0x4> mCapabilityContainer; + std::map<uint8, Block> mLockedOrReservedBlocks; + std::map<uint8, Block> mLockedBlocks; + std::array<uint8, 0x4> mCapabilityContainer; std::vector<TLV> mTLVs; std::size_t mNdefTlvIdx; std::vector<std::byte> mLockedArea; diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp index 32097cf..60be581 100644 --- a/src/Cafe/OS/libs/nfc/ndef.cpp +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -19,20 +19,20 @@ namespace ndef Record rec; // Read record header - uint8_t recHdr; + uint8 recHdr; stream >> recHdr; rec.mFlags = recHdr & ~NDEF_TNF_MASK; rec.mTNF = static_cast<TypeNameFormat>(recHdr & NDEF_TNF_MASK); // Type length - uint8_t typeLen; + uint8 typeLen; stream >> typeLen; // Payload length; - uint32_t payloadLen; + uint32 payloadLen; if (recHdr & NDEF_SR) { - uint8_t len; + uint8 len; stream >> len; payloadLen = len; } @@ -48,7 +48,7 @@ namespace ndef } // ID length - uint8_t idLen = 0; + uint8 idLen = 0; if (recHdr & NDEF_IL) { stream >> idLen; @@ -81,35 +81,35 @@ namespace ndef return rec; } - std::vector<std::byte> Record::ToBytes(uint8_t flags) const + std::vector<std::byte> Record::ToBytes(uint8 flags) const { std::vector<std::byte> bytes; VectorStream stream(bytes, std::endian::big); // Combine flags (clear message begin and end flags) - std::uint8_t finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); + uint8 finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); finalFlags |= flags; // Write flags + tnf - stream << std::uint8_t(finalFlags | std::uint8_t(mTNF)); + stream << uint8(finalFlags | uint8(mTNF)); // Type length - stream << std::uint8_t(mType.size()); + stream << uint8(mType.size()); // Payload length if (IsShort()) { - stream << std::uint8_t(mPayload.size()); + stream << uint8(mPayload.size()); } else { - stream << std::uint32_t(mPayload.size()); + stream << uint32(mPayload.size()); } // ID length if (mFlags & NDEF_IL) { - stream << std::uint8_t(mID.size()); + stream << uint8(mID.size()); } // Type @@ -249,7 +249,7 @@ namespace ndef for (std::size_t i = 0; i < mRecords.size(); i++) { - std::uint8_t flags = 0; + uint8 flags = 0; // Add message begin flag to first record if (i == 0) diff --git a/src/Cafe/OS/libs/nfc/ndef.h b/src/Cafe/OS/libs/nfc/ndef.h index b5f38b1..398feb5 100644 --- a/src/Cafe/OS/libs/nfc/ndef.h +++ b/src/Cafe/OS/libs/nfc/ndef.h @@ -39,7 +39,7 @@ namespace ndef virtual ~Record(); static std::optional<Record> FromStream(Stream& stream); - std::vector<std::byte> ToBytes(uint8_t flags = 0) const; + std::vector<std::byte> ToBytes(uint8 flags = 0) const; TypeNameFormat GetTNF() const; const std::vector<std::byte>& GetID() const; @@ -55,7 +55,7 @@ namespace ndef bool IsShort() const; private: - uint8_t mFlags; + uint8 mFlags; TypeNameFormat mTNF; std::vector<std::byte> mID; std::vector<std::byte> mType; diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index c680936..fcb1d8d 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -149,9 +149,9 @@ namespace nfc StackAllocator<NFCUid> uid; bool readOnly = false; uint32 dataSize = 0; - StackAllocator<uint8_t, 0x200> data; + StackAllocator<uint8, 0x200> data; uint32 lockedDataSize = 0; - StackAllocator<uint8_t, 0x200> lockedData; + StackAllocator<uint8, 0x200> lockedData; if (ctx->tag) { diff --git a/src/Cafe/OS/libs/nfc/stream.cpp b/src/Cafe/OS/libs/nfc/stream.cpp index 73c2880..dd6de7a 100644 --- a/src/Cafe/OS/libs/nfc/stream.cpp +++ b/src/Cafe/OS/libs/nfc/stream.cpp @@ -28,7 +28,7 @@ std::endian Stream::GetEndianness() const Stream& Stream::operator>>(bool& val) { - std::uint8_t i; + uint8 i; *this >> i; val = !!i; @@ -37,7 +37,7 @@ Stream& Stream::operator>>(bool& val) Stream& Stream::operator>>(float& val) { - std::uint32_t i; + uint32 i; *this >> i; val = std::bit_cast<float>(i); @@ -46,7 +46,7 @@ Stream& Stream::operator>>(float& val) Stream& Stream::operator>>(double& val) { - std::uint64_t i; + uint64 i; *this >> i; val = std::bit_cast<double>(i); @@ -55,7 +55,7 @@ Stream& Stream::operator>>(double& val) Stream& Stream::operator<<(bool val) { - std::uint8_t i = val; + uint8 i = val; *this >> i; return *this; @@ -63,7 +63,7 @@ Stream& Stream::operator<<(bool val) Stream& Stream::operator<<(float val) { - std::uint32_t i = std::bit_cast<std::uint32_t>(val); + uint32 i = std::bit_cast<uint32>(val); *this >> i; return *this; @@ -71,7 +71,7 @@ Stream& Stream::operator<<(float val) Stream& Stream::operator<<(double val) { - std::uint64_t i = std::bit_cast<std::uint64_t>(val); + uint64 i = std::bit_cast<uint64>(val); *this >> i; return *this; From 964d2acb44c64015637d1a8713cc2e96bf53bb48 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 20:47:09 +0200 Subject: [PATCH 109/130] Filestream_unix: Include cstdarg --- src/Common/unix/FileStream_unix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/unix/FileStream_unix.cpp b/src/Common/unix/FileStream_unix.cpp index 2dba17b..4bc9b52 100644 --- a/src/Common/unix/FileStream_unix.cpp +++ b/src/Common/unix/FileStream_unix.cpp @@ -1,4 +1,5 @@ #include "Common/unix/FileStream_unix.h" +#include <cstdarg> fs::path findPathCI(const fs::path& path) { From c913a59c7a7140eb7b148611362eee519217dc27 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 22 May 2024 04:11:02 +0200 Subject: [PATCH 110/130] TitleList: Add homebrew title type (#1203) --- src/Cafe/TitleList/TitleId.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Cafe/TitleList/TitleId.h b/src/Cafe/TitleList/TitleId.h index b7f63b1..073472d 100644 --- a/src/Cafe/TitleList/TitleId.h +++ b/src/Cafe/TitleList/TitleId.h @@ -13,6 +13,7 @@ public: /* 00 */ BASE_TITLE = 0x00, // eShop and disc titles /* 02 */ BASE_TITLE_DEMO = 0x02, /* 0E */ BASE_TITLE_UPDATE = 0x0E, // update for BASE_TITLE (and maybe BASE_TITLE_DEMO?) + /* 0F */ HOMEBREW = 0x0F, /* 0C */ AOC = 0x0C, // DLC /* 10 */ SYSTEM_TITLE = 0x10, // eShop etc /* 1B */ SYSTEM_DATA = 0x1B, @@ -43,6 +44,8 @@ public: return TITLE_TYPE::BASE_TITLE_DEMO; case 0x0E: return TITLE_TYPE::BASE_TITLE_UPDATE; + case 0x0F: + return TITLE_TYPE::HOMEBREW; case 0x0C: return TITLE_TYPE::AOC; case 0x10: From 523a1652df4e8e7a18466e1d2668573dc06909af Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 22 May 2024 04:23:33 +0200 Subject: [PATCH 111/130] OpenGL: Restore ProgramBinary cache for GL shaders (#1209) --- src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp index 3d46f20..cae5314 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp @@ -23,16 +23,13 @@ bool RendererShaderGL::loadBinary() cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); - sint32 fileSize = 0; std::vector<uint8> cacheFileData; if (!s_programBinaryCache->GetFile({h1, h2 }, cacheFileData)) return false; - if (fileSize < sizeof(uint32)) - { + if (cacheFileData.size() <= sizeof(uint32)) return false; - } - uint32 shaderBinFormat = *(uint32*)(cacheFileData.data() + 0); + uint32 shaderBinFormat = *(uint32*)(cacheFileData.data()); m_program = glCreateProgram(); glProgramBinary(m_program, shaderBinFormat, cacheFileData.data()+4, cacheFileData.size()-4); From b048a1fd9effb59a212c8e1c8ad5069a953f3f3b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 22 May 2024 05:08:03 +0200 Subject: [PATCH 112/130] Use CURLOPT_USERAGENT instead of manually setting User-Agent --- src/Cemu/napi/napi_helper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index e498d07..164de7e 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -388,9 +388,8 @@ bool CurlSOAPHelper::submitRequest() headers = curl_slist_append(headers, "Accept-Charset: UTF-8"); headers = curl_slist_append(headers, fmt::format("SOAPAction: urn:{}.wsapi.broadon.com/{}", m_serviceType, m_requestMethod).c_str()); headers = curl_slist_append(headers, "Accept: */*"); - headers = curl_slist_append(headers, "User-Agent: EVL NUP 040800 Sep 18 2012 20:20:02"); - curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "EVL NUP 040800 Sep 18 2012 20:20:02"); // send request auto res = curl_easy_perform(m_curl); From 917ea2ef234f583032dab84a6f0f371709823ce1 Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Thu, 23 May 2024 17:48:04 +0000 Subject: [PATCH 113/130] Update translation files --- bin/resources/de/cemu.mo | Bin 65048 -> 65571 bytes bin/resources/es/cemu.mo | Bin 62413 -> 65733 bytes bin/resources/fr/cemu.mo | Bin 63716 -> 72178 bytes bin/resources/hu/cemu.mo | Bin 62167 -> 71267 bytes bin/resources/ko/cemu.mo | Bin 69116 -> 69784 bytes bin/resources/ru/cemu.mo | Bin 46500 -> 89284 bytes bin/resources/zh/cemu.mo | Bin 56868 -> 58070 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/resources/de/cemu.mo b/bin/resources/de/cemu.mo index 918fcd3546c99e9c385fe726bbd91c9e95b19270..8dc4e8cc9ec032d5dcd38af5359b3246398a7e0a 100644 GIT binary patch delta 15405 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 delta 14957 zcmYk?2YgT0|HttgNhE?Kk`Nh)NF+v(7%^hRRx?HIy|>!?8>LoJ#8%qWUQIQ$s1~)g zD7A`8X-id&wkWFgf4y@~{y+EeJ9(bZIp>~p?m6e)r0s8Qs>kvd?yl<r9!ngqj9iWr zj*AOA&N6q$$yY|Dj?=%I<0Rq$%#Z1q4>K???!!EI+<MM>&3X&f?jeTZ3-rUn)g8yz zaa>L;86OG~(F5yZ05(Ez?1%-h7Y5;IjKVqSj~Pfu&H?noGpGq&Lfv-*J@GHhjn7d1 zJ2f09knx>BGMYdPYQO~44K+|5CEK_uYJe2f6Lmnf?}K`xp{N0;pxP}!O>jA~sLp28 zME9fmJ&m4>?_4IM30y~YbO)pH8ESxHN#=%FRL2RZt*DNwZ){CLZB0ki!2M7wIu13l z$*BI{Mcwy4y7VNQ$Y@D+q9$?-HNZL4%zwl{e1e*QPffFw0jQ2bP)`(z8lWr&V-gm^ zHmG)kP~(lVacWK0Uwbm!7A!zbU<Infb*Lx$2(@&dquQTDb$k}}WWQoDe2#jM&|0QE z4s~W~qxx-wdVp@I2kKXg_198Oq#y<7+J@I{!#lR&A6S%n&)TMa6mpK7$~YN^V+h_y zP1L)NS;1iBWpbji4VFSpa0+T73tePpl39W3uz0dr!p^9MgRw5Uus(i;{Lk^Cklk>a zpq_9B7RObn!}|ql?+>B+IgVP%t5^(w#meaNu4fw7LM?ecER4-jXP_@?rlU}MI0?0M zZ(CQO+HFVe?dPb0j-i(PJZj6bP!qX@8s`~?>-~4<u~ZO&TH?~ErEG%QnqH{GGz!(x zT&#m>s1DDg+TF$?_!O&Pp$2AUnqv#%o~V9y;P-d}2kQM_+t6|NC^^239H%WdMonl9 zuE0&W9P2hVD{$5NGioL7p$2wqVpgsgdJ<PfeK~8{cnpRSPeLzTj^2##d_YF8&1Tfn z?XeAxVg&JN)IblA^X0g|W|q7js-qNCeFy6hTR#<-QT{Ggz?i0v(+t}q&*ZE}m-hM| z87*aQYIT@Gur!v#?2fT3@et%iaK6D-n76rk^3JH&Z~|&Vb5UEh81=-fP+R&L>hPV# z2>hct>#u<W*dTQnjoWby=Eta(*`H}A0oCD9)Ig(AOFR?ZaUQClg|>Vh>ie+8dJr|S z%cy>SMD=sKCF>tc<{1TfF_KxRK{?b)R7P)n4gIk-#$#{f>+CGWzIYA?W3?1hzXA2- zyo~;M-}(|Gi2YwTTORKs6GK5VY61f=6h|YkhqD0H(RS1!JA?sv!<Iin4dBz-d|<** z6CaLh|2Fo*wb&57+VEy!3oL-H<z%#!TQCqmMh$ovHP98*(%r+U_!tM^SY8$F=?&CU z|ARpo@P@fB2KBmCNA>?Y`e1w1gZ08PdjC_&Bv7!;6gXL^!<V<6<5a{dSPlnb6<mR( z@LQ~e&r$c6Z*N|=o=C^eO4L>!wVpx`;`28C9t-OI|B;NA;t6U%?+)hFCZRfNkJ_5) zs1;d&deXJ1L%JJv2#;bMo<yDcr>F-B>gYHnur?OLURV|<U@69THj>e|{4AD4-%jQU zD`R!yZWxIxu{eH#>NpGagg%{(1<;SUFlu74w!8}JzM2@0-LM8uLYF$&O-4_+AGJhZ zV<>)$IumzMPyW)@2Xrxe8H{--kF;@F%txGvIy3dLH#W2J4%A`YgHd?03+u0?c|bul z2J)5B3RFWqNq6gTREMdk0cN2lv;_5}o3JW=iu>>Z>b2e0&9vWz>hCKojOT3m-EJ;( z<5LQ@QxV_YEM*qzOx(gSe2Q9`pdMzWLQoTlMqf-oO(Yq$A}vt^c0ir_0jT~)p;mSV zYGu=1WOSHzp|;>8&cKT}7Q6H`@9za{O<awaIoXXb9_qVshV@m)f1{o(PapH-k*NO4 zpjIvsRbLx5!S<*Ja`hym_q`u#$tI(oWCrTV7GnagMRj-))$S&$gS!}w4^j1n`kE(? z#1P^HRQqPAL);d1-#E;z_kS`O-8daJ(*>vo%TY_W8a2U9$bomhwDqC=%s|CZ?aN~> ztb|(HMAQVDqE@&Ms-Fp{_6smv@Bb1qwJ7)m_2hqG6uz`^RDbgX38(>TpgY#Gab47X z4NwEMM(urn)I`RiR&uV5SE05n0}C_0bC8S%yn<f%5;aq=0VXbj*@q3aQVBM$g++;* zqE>1k=El)j94FzoxCSfYuz_X;*CKDS^AcU&WI7KrGwp+#z<5;oR8)s^Fb^(7Ph5o> zI34vxJAk_H5~_U`s{Jk0gFM6{n0v7K07aqNB@br(wPcMch`=tWFVhsvhkLEZPy?Jt zJ;^n!kN!jWgB9DN>Q|t)A{{kA21ekgs1>?o%Wt9{@Xip{Ux(pO3c~Sk)Q2T#s9EY5 z)LvG^{8$gQ0x75g+M(|2iorM$M`If5!SW0<{S`%RaXhM@H&AawZx<OqGBZ(2xeVj* zBh=}=h9&R`YEMIln_o&vsCNBPzZI8aBA!CMuAXnQyjT^rRTEGXT!Fs01N9+t9Uv1! z<|=AN?jy`hOQPbYsHGZ;+RL%%hx1S!uSV_lK3jei3lLvKE%~pQ5C1|9?2I%k;D_|* za*B}AlUGDFcn$rr1FD08SQ$s7_IxYG;zQIEg^V)y$D_8gF=}g?qbAx0^$G87%Tuj0 z(O2*Pd@^}yusXYdKf_UnX*X)G4xpakGzQ{TTmJyHHGiX4D2$EQeNoo3sFkRMy1yan z{?@1o_QMi-|A&&%lP*FHxEg!7@ua9d&p+02Heh+wVY`SC_!kzVUC=o5_k5-CJT>K$ zuo|AkB=nfTpI?}a+Ums^f&0*<%nxLA!+&@Ot4`#EV&o+A4;<q$f_M*VfGZfoz_F7} zeXA*Epaqyr{VAM}emq@ST!V@DH5NnnRP$GIiB#5Kr?)Ky2{;$m<9^gk`%L9V2AYR@ zeU7|kR^T_(cfxC$IWzT8Tktw+1v{Yz8i3I_8+E2KP-pD`W`7T+F(ci0lLDRMyBLd) zQRPLao0W+{b<hJna0C{_vA7B6VO^~9wwd5~Od?*2bme@9%-Lx-!}N0nbvEw0$mnqV zizU%#ra2_>r~w*a3+!a$E!G35x8)+X#$T`uR-0v>tPkq`;iymV3|qbqJ&7|g99_G} zXerO3md<y!S)y>%id4iXtchOO6LaH08^4LGiN~X!D0q$;xD;v&YGW*RL;V3V3$>Ez zNPCy_2^n_^4&zcht~eJrykq`3zt=qTAzFu;;4#!nUBK%20z)v76IT;c@DRR>-q`wG z^C|9(dh$0>TQwH5|Nh^bo#9s{>d9`Q2i`;7@Fxc0Q(K;Yfmz}JRDBT)z!Ip5RY5=O zg4&`XsEJQPwV#7JgbT1V=g-+lMjxPasDZDemii}TA)LF|0`H?g)?8>N)B^o@J9?qE zaO5KMMa)1Aa13<@uA@FYkF4HlCJslJDk_lC1nOfbwn9DeAZsf65HCSZ@B<9Q^{D&y zpeA+%HSk4@#(Nlyfs0Lj8O%eRgj$(~i`jn-)RqD*%|O&r4M*KL#kv$Vz(=SK_M`Up zg!LS1>#m~izlm!1(AGc40Alwg=ByM#-Ctn|>#vThQJ|%I4b?$+R0l&*9gjn;fD846 zb5MJoj(UBM+xiQr75Nd>@9(I7{zVPwwbYdRV?pBLE;3ruL=3_t48#=FjeSrP8iqRM zlWqAj)Dxv+B<{5FCDa6dLtZcE3929OWyS#17KdXAbS08e$DL3!9F6K=3hKr=s1;aj z>sO&Bw9eLlf`y3pp$7a8b$EZl2)u`SAm8QYI}nMAn<5Y3a=MYxQj9<iJPS3E#i))~ zqi)!V-S9Z7UF-_8^p#K#kc_d|$;Q*MKJgk%!=F%3Ja(n&Zxv?0{~2V|;Sn5<XR|Aq z;VSbaJy0K%0azR3-ZM)-#F~nFJ=3ri-o%brk<X&y`B)UQP-o^p)M1Za!?=3?%a94i zL=3~0SQH0gWt@Y$;ehoh7AF1%wMD;JAD|BDGgN;b?;FEWhqMyvL6dFV99`PWj<%vd z>JW~y@oX$cyu`-4uqyFEjKSv^f{`DXKU9)X6Y7fXu|J05LDYn=VQGAfwJ;)`{ns9M zOE>?NI|d_&uVAnn|56EE#DQyhH8Bkb;&s%MG+D<%#J2p{fzMHgZ`*oPzZ2E}0n~&q zVHCbZow2A5^qxW{Zi9WlF`Re~>Pa@D8ty=?%wE(|p0V}UP#xYv4U~7I`E?wLn&22z zKX0L4*SV;bcptU$yIo|CkU5HpYOu+i^26AYIBK(5kx^KLcnNBPnOG6OMGfq+g^v`5 zVOv~`gYh>kitV<V*K-Vd5YNIq=$c1H9WO&2zFnvf%2_Olk8F9^HnT;Em__+2)Ni?o z+s!YZcTn&DF^s_P(G#DeJ31fPiK5EGkk{Vj6gL?s5w+CKt#6>-`)*hXM`AwQgt>7S z>dE$DUc8DCcpDSYJA;1~z#2FVr(-HU!XzC1u?{Eu{|Om?3QnO0ypGZMCu*q+bF#`~ zWz3I#P~{_06Ig(H-7>Hk9z&g#+qV1#YVU(SF<Tphs-KA^8Q*!2%wRl(Z(xN?v-dMm zdwU7>#P?BO%EzeJF7Hk=a5QQ!YvM9&gyS&_<FLanv&Az}6JC$H|1i4reqJY|0iL0j z()rXZWj@rC$6*uff(f`0tKt=Gh=HG(zxz94GvfD9_uaud7_!^eV<XjLW&C+J>+eM- zbdQ-pBq}b2nn*lq#x*ejJ7XOjhNbai8(&3V;_$uZ$;zPGH$e5%9<@Tfu{sV#y|$b8 zvi^FK(-cUr&&^v<2NQ`qqh`7owE`zG8H4tj`Zq9zcs^FbAF(7B{KDLqggVqCP%AtU z1Mw}?#Fn~j#fKP6!DiGUI)_d1PxQfBUz)?$1oalQ!wT3NgK!C|<1MJy>r2#qhplH& z171NtyoFk6*Ap_s$^3^}qG9{Z7iv6eLW{5revUDC8#S=s0rO;aP)pqe^@N>K1NA~p zXc%fD(@-loA0OgnWMy2=iG!xWIn?X*18Ts#s3#5n%6!qHkRoRSYJj9e=9f-mtVui& zRldo(6V=}VEQqJE0RC+2pJ4WX|Ic;U+*k<pB#{_~l~GHZg6g0zYUw87I-H9$G5!dD z<KrIGS%^8x?|nC3K};Y%dCa_i|Dskj?6_I_`snYXpcxr0T^9_+-l!*cp%>0Yy~m5u z9lt_7=~2{#&S8DLiCXfQ6J~2_U>@T77>_Mb?Nd<?wg6qhWY&|>9({ot=s0@f6YC4q zgmRrU6ZJz6;zFqV!cZ$z68m8is(u}6z|E)!++)iRU<mP%lk9&nGFcR8A}?)4@Ykk; zc#NdH3O2?Ls6G4;HPM}@ExU={_!u?tf2fsm|Hk|ilo#eF_Q41&j9P(e-?08#@)i_m z=?0)?HW~eK8ES%?P)m3mHPCa6K=)Io<0#BWoPc?;Hfmx`u`G5)AAARO|57Z2@4Lv9 z;;?*;btrgzniGyy&Y1sW!4A|JDF3Z_@<h~9HbD*43jMLOjfbEH8jnSAI%;b^#QeA$ zb^lS+L|muH=m{@kI6gtGNZ?s>c*<dM;;N`GVn?ili%{*pLk(~rHL%Ay^RHz6ur+aO z?1CFn^`76E3Hu{wgWvyTG;lm>hAl7;b~X*1zBV3%QIw~mo@_m84-cX4zl@sDW7I(Y z=j|UFs4XpyYF`7_U@MGfeCGj~LKGCdVD_pk79j3`<!}(HekE#v9atB?!P4k=(X^|M zrHBWh_B;*a@BmiB`_|Y?=66L;Y{dA^GBR4?EYy>^T{cTp3^hOntb(0Thh`C$$1gD! z@1s^O_<M6GqcMQEA*$aFsQQT*h_g{Ewi;dfwYk?eIDzbma|yKqw=o!>VF(tuVmc^; z{=~IW?OS0@?2g@W4OYXKsQas3H7l5cI?Vl1hj92+)?Xcsr9g*pmaW)i%|K1$Q`Bic zj)m|%>h#`4J-{Q>>*s#W97ccC03}f?5Ql2t64kCfY9f8EvHn`BA+{nF!-(ghJ|vq_ zd-@UTP+dlK@Dt8;<2M;b6HmU**Av&G9;8Q>*_x@?j&@tHBjvenm|tdHur_g~i%d&0 ze_=bUchhvZ3S)>5qRzw}OhT`p*a|nk={TCW>(AyjJC6~>vA<A;O)!~uo3I)2Z?}wz zx7jY@>9_%17s%+8Px{r&Xe(AGzKaQ1{EqpHrwuAzibwGTs@;O$9OnZDI*O%;`~7bI zWLt=hh(AVc(NokGc-^z_KSpN1|73JyebiPA#Za7z+Vl5NOTWp+nW)3H-^S0dIkD$` z({XFmx4$=PEBoUaoR0Od%>%Pi%TfRT6K4;ZZB%$Y<ZpP~i8^$>|1c98gZYVPV|iSL zI@QNeZ$%b%!)K@gJN#)rRHIRcdnI<kt*F;H&tGOm>R>42J1xnS#DSO(7orZ|8jQfT z*b)z800uoWXCel*LJhDirl1#2vE?&tya=_|D^d3+KQ?bk8+7S|GLTFhF2!&>jB0oT z1Mv~&LhmO`2J_->3No-EasOv#Wil`iaTaPz?_eSZJ~t226m|Fp<0<^&IqUC7=AD1c zp07eZ!Kc^}zr+v>d10QoBIYJev9`B%Lv2Z4^u#Hsm7I<`3kz-i64ZlkM{Uj37p%W8 zncpeU6aRx6!1rIXmjyANI39IFFVxnILOtnt8&AVN#IrC6?_1sf<8w~z&1&hv2($kS z%Pk6ZsJChV8<1+!pg-QHqOsQ6UV4QF6RT5AOxH5vs_K<%fprl1b(G)8F6F<3kk`LO zUrfA+Hg!-F&QDnwX$|@Fl)GvZ<Xoq0XNuz~k0KQ&|BUjfwp~3;u{SIrfAN)j9+T&& zWM8wWyGr~4QJ8J>66+JU!GYA*$;JM^r$XDHbM~#~Jw0jj>u|Eo7qIQFSXDfWe1B4D zQY7(245xD)G+pCKrAYylO~p4+*Dtiw_s8iex$06-5i6k%&r$41<L}A0LpS0H_>lMm z+)C25-{4%PUe|||zfX#@@iDAO8c1Cc%9Ct)S6t@e$2`&$Du2Kv8t|Gr?~vCU&>W*F z+d|T{4fT=IRg8Q;k{79}Df<7P70Bu2A0p+Zj}xSSNVkd4;cGaF<a(PQdq}!6sL+*) zo^;xUq-&=wuZAs2H*6ebs<VIgr|nym>kB#EmT$oqlnuv~_MW!1>rARds-BCNt|6J% zNE=8VG#Zwco*3xTOZOHfUX=90zQp~=N0P5eK9;o4c1bq-`kVM5xwE7J<d>7ikoK8s zXBJN9eqFms?^36$vcXwIoqnkgbJMZl;`8Plq+l-oiydfCKowkfh@X>wC+}f9or9|> z8%3H&K0jts-<Q04988)>%DMch|B<9?l)?U_^#dxl6MEVPs#!?tK&+3duHRp&8&AG5 zsRwB(b<0Qv7<jW9a<w6iC;o_}tBUfp^&@2wufd$Fp=SFL>AEc`h~L<JaSW&Nc^i+x zP&)e+&ygZ+M|CNiOzLFYiPgl{s4q(@L%try*!oX!I`QY|YDMKz(ti}>TzXedl1|uy z4Yuwx;@UZla5+i8FV|B)pY(;TtA>+_&yw_OY%eLDF}^1*Oj<*JGpV^gOXaAnLZN=a z=3M28`;eB9d}x@TiZkT*kgnQByU>?7kowi+`Bmq<MH)-|>NSlvZ%|eozgC{gnWPKk z%jMGgbCo2>x$==8N6-`VkeZUV5;sKsyYhjgw!}Y^nv%-W`2@;xt{~#;#2=8}w&nAv z^d{fUPHqymC#`hx<44_zLue36+DfWO{4458#+&#qsVMo=D#Z8UfNi6q7TlvNkh0?B zKeTm2$PXv~C#e%Dg!mi$nzpW51YNRk``?c;|I0e5<jd2hu`T2GcJ^NqHq53noh_zK z&efMV=Xyx~hz&+!D(w;}OU94&{(tvM<KyHn(5Nfv57Jy3{eZf1QCEZbr&rp4Yx7U3 z4<)T4b|-07duRa4p4v9eh&z*SL7GTj*9bTEzXKT$(lAmIX)I|i4O-b7pApw2-<`Cb z@}0!5lOIREE%qb6MasEWP!>$!NlGC<oOU;HJ?i?5{QIN;(gAz^oRU;dqp}8R3HdLm z+)m25n%JA`<EO8b=OccHMx`h_gP&kGtY+Km{zsJUCB=|x5zip4C0~Z*%1y&^WZuK} zsB1L&6x+dJ%6_Bl7t&o@e@~6L{v{Q)?G91*ZFZ^olXE5c_ej4{z5+|o?ho=s$gd*5 z36mM$`I5{+Dl$oLl7ACt+D^lX<B4_oQPv(~iDSvHux;j0R!I%GdXV(j<s5rYJMt}Q z_aW-qP5D<?l%(5xx$$?%Dl%zA1*zObYNQ&j`-bfQrrC`0Cd9=^8QH%#2hnN^rBkpE zr6ovVBwa%(Z%TO`%!M6M*JaXd@>6Y`7yD2@g!00;i`WhCsxen}+s;I6GIhNOg7w`D zqOx^P4KAcoSBY0{dYiIs#Dz%nsq0N#2or7F!IVc6N04%^2V`E7Hd7ZzyQAb=lW#|A zWA7V~^WFT8%0;BtNh7ID$9FN0ZCD!rq)b<DI=E!APHpnKx}cBEr(sol-x5>p%%tpz zt^e7osu;cD|B$Im>Q1U)E7Nflb*F9IjJk`Yyp%;y)`GmQ{kHx!oI#zgX!1XqZ1(@% z%Vx6~OI;`1P6gTT?FKS|+0}fqs0<;WKto^Ka5jELyy=x2-yz?Cve~pPg1Rc$`qAVQ zZ2n8?j%C-HAAiFj+n%h;sX@?_#@$Kx$j`?S)J(CB;>aH-O|u>LCZ9kGp?*1KCCTfW zU~mdiHk!Ip#OrJys=7kDMC^-y;TYQerRP6FFoNJ36=g|{h{utdlV4A2P2B>VLD>!* ziMobT?oayM#v92GFjdYD%04B(7$@0!RlPyJ0qwlWZzP{p_?5~CDsB@O#3pGs;sP6W zBZ{&uDpOireQ?#H%->|Qe-7p6Qpz^kvMPAk<{clOw5iR5(|R^9mR7v_rL_O5cg*Zn z!_O^qa?%R#%(w>M<;u+0c(YsP`6lz-GnY4ybk980%GWpZa##PL%)pUnish|PEv{mP zi1-TC<144#TXZ28{iScMp7~W;tXtZn#eFmTE~)31nYr|!Pv)ZZba$_au>(f*j_93s z_F`ySvx`1yBi2vLe6qfRTUzHQ`7&SM80+EIBW>c=Mwu_RX1QnH%V<*|v)REGKAw@y T2MmbJT>bS>-^?0UevSS=9Aq~l diff --git a/bin/resources/es/cemu.mo b/bin/resources/es/cemu.mo index 4f78fdcd0d63921934cbce6338ed108d179fbc24..856049de037de70de90a2868e6da991fe25d1235 100644 GIT binary patch delta 20815 zcmb8$2Y3`k!2j{Rgc^EFXp#*r1SB*84ZXL}4Io84l1n&9?!py1o}hxFfVu(}1W}|( z(*r4jfS@2MBBFw#*s&v`i2eWl-EIK=`@GNl?(+<vnc3Ny*_qkdJ<!+B#=kVXSmeXX z#TFYJzr-0vZ9I`=7(d4w#=XsyYZ$k5GK_NQ#<J+gGB^`U<6VvqI4*NsjViYVYvFFJ zfG4p$eu)W&5ix#oPQ-P#8>o!MD3FYmu|Af<wpa-}V{sgUHE;wr!~j;rhp{TIcifBh zNuNNS{}t7Ngf50rg7%FXMCgfOVo7X*y0ccO20Ne{9O$I8umtHmR0n-n1!tq`S&ZuN z<EZP_q3YZ0q<5m)*^6ap-#A1>7rccUqSL5`zeN@N3)S-yUF{J{!fK@JqwXXF)j%Iq zgF{grxydmHRj(f#U<B3PQy9^On~12P9jLk9=M*@K)k(kWq<=uIfnPD!p>Fn6C84gb zjp}%PRD*3$Q_<6L6sqAu)QC;%#`tTIEFeQe`7o;I8&DnDj=IyAP#3(9YUmtl3VuOV z_!sI%N_Mv!u7s>(BLx%j22{C0sP=Ah(w^>&zbf)M8DUfhZbemaH>%>5s5^feHRL-` z*BwGNa1?dtpJOuqg1Vuk9(I0X)JU{Pjp$%hxd{;>YH&X4&K6^Fd<?I_CsF73qZ)n< zV<Ul@f^#?+jh==P4?|ExKMZwUE~?&17#nh|LwW(~`p5<%8ASHrEWCtztS0xe8@vxS z6)R8;Y{VP!8Ppw?=xsMp1Lu)WLDjbo)sfFo<$gkzjZw0XVK6vGUu0cJj73D+lkp~M zh|2e+60C>Cu?LpMepn2%P!*0uO-TW2D5qm<d<0eQ80xyWQA2(Pbt9LsB$nu>HNpIs zk42a()DW4D*P$-xjb(5cs=;xXfPU2CoQ`GiJ}iODP$Tsus-tUA9odE2VGla#kFgHz z8{ZJokSAo?b5b9*s9K{IVJ52L98`r9QFC`IYK<&Hjoe0TgD*PiUvU8G^8M|4#^5Q^ zVVr;iIT^Wu$Sxvf@F&bdW1!vRVfZk~Td)-V<`_T7UPNV4Bh~~pRh>{n+XFS^{hf5K zbKd6|LcJ5F4`Tdv!4fjmz;djBYp^VCN1Z>2CccJh@H^~;E|$Kgs0*sxK-AO>N0l4r zI2l!LJ}$+FusJp#!uT^vhI@!%^ukT3o?k=_b%mk!(ALGqq}yVwV(dtIGSU}g8)_;_ z@Mu)R`lt@IL3OAjY6|<H?tBnx%JU;cl8MYk6IY`eK7<?bJyg%{VqV(e5>!RUQ4O6! z4gEK$MRy)`{m-Zllp1D_Y(-SL8kmU5j*&J*RIop);w)6fqp=bCP><76)EZfanyR&^ z^3PyZ+~MTEjulD2gDvnZs^c|=vl(F)=HqVUe8fl_VLwKrP!~>eoP#E53$-S;Vk#a) zb>v&DiI-51Rh66UdYYrAq9<x3a-94rsQMQmgK8|ts(St}5Yf<-8p-@%dDIAuK;=(B z&3PfJ!aGnSwiIXM3iM*iC_DcitU=nsMBIoPp@Uc*Poe7h5tC@&C_dV5=o-{wsfSH4 z9b4i^C%pi*2zO#D{0!5v`WXAU?}cfkZ^i3yE$aFYuq{@+*{-)YYU)B5QDg=Y&G8)P z#5}A<dLe2gR-qbv4%M-*QB#mO*1nUvs5@+pTC9ChkM9W7qV!->^kOYsin@{LSjN8| zkq^nxP{rM17}sJA)SdLi_E?AwaU&}K1ZLo0n1VOB?FvVsZfLIK0@T`h5Y@3&PX4o) zLVBk=Vqfq+8MVpy1l!@?sEXQ+v+aty<31RB31LmrBT;K3jGEKCQB$}KRc?)w-hib^ zZ+FuBQ6uzPgoqZ?Da^r-Q4ROZb{<RAs?Easn2YuB9&CV{QRUyphIkrv$MIAqtDqXH zhw5l^RD0c>{75Dd_53E(5ar|bI30Im6!p9pdhCj(p(>t>TFpyP6>fIUZ^t;&2XP}F zLUk~lYp<o5s5P(<i|P4aPeenz8P(J0u{<6|b?9Bx(0z_-_#A48|3)p&ggkpHYNPJ3 zEow3LLtS?Zs{RQ$7boKs{1dbF{D;T$$xOy6?BZgjdhI*%Pp~(fDX0opU~$}v8j)S7 z4je#@#M{pK@392wpHSs~M|GgsM7!RKsPfgZH0>KLh%gjJJJe&>8Fk?>)Ci14jYt9N zd7g=7a5Ki^^QiKBQS}_g2KYLb!3(I7`x~_}Rmiu?r(vWn87+zE!YowJN1`rtV@WJ< z&WAC9^mJ55??g5+<55&cenHjqJ8IDy1$M)gQ28mSwb2kYRcQr`zk1Yx3=Q#cRK@wI z3+A96%X!!lS7AB)6zk)8C!OfC@2Ebifu^VqHAj`d4po0w)JP0Rbs*2j_?IU#!^xPB zn$zW|Iog71=oQpRe2?njMJHX>Z!g-as1ZwX(oL}r=?v6}-Gmz198AH9cnlXrh_oVd zbD@3551>}<Qq-KTL=EwJ9E~L=*>{kQIv+&c$xKuS@5A!=5LU!BsB$|{9oUbm|1fGX zM~)LwPfwz@;9pP`l@Hh#B%&_326gADcnx+yP0c7&#Zys396=KwMD3*8uq>W;{0mio znPBY3BSsY>-O1>M-SA#ifn%t-JBe!G3pDXN)R2}B*>_e0b-u2XPD5Sa619dhP#x`r zm2os`s{B|%&;JY}YUm!+kUWg4a0TkZ)tH0_FpTeGRUAx073QMW$TU<v%TZ6&2CRT@ zphor#HpPpmMPGk1+p?bjjzqL5#$gk@166Pfw!sfk@9%1CTP?9acEQ_FQ}-e!;m6n* ze?`5V>QA*duuiBB<)Jz{-$`%Ah!)3jBGvGHRFBW2Dt1k?=iWr+H^xesftrF$)Z7k7 zExH`k2n12}&BjDr>YU$%T9kWG*B_n6__ro<iVUrR3e)Y6&Kps8Gz(S1V$@u2Kuygv zs5#z&>exQiOXZkz{#z&if@Ay)`?@Nqj@Cr2fwURU^WT~bRoESM2bril8;Mmg&pAH> z>yf@4HF9g5{4I_<P$RStb^USF^=D8U(*@KGTtq!pe@2{)k~8g!%cF*-C8{GGP-~+f zuEqkaja6saPeTh#cJaF(dyyZwmGyzU@CGc!4YtRwI0Pe@ipSAZ`Zp(1>o)s>_V^Vi zCgU)?Wwu>#JF4Q7nCfB=pJV?78*sbbz(VZ8`B!lvmb=6Llv{>vNxy>0=(^MXU66up zXx|u0M5}r}K81TwJ<OiVg*0>z)+c>no_*(EVms2s@3N<$3u>hLq8b>14R9)Iu`Nff zq0Oj~eHlyPn^;=U|9eClk?|Rp!g6=pBaw(DNe@C*Gy;pE7vs@~TIB)kig%%==2g^? ze~Ao+(O^C=GF*q6`)lv9>uZITXy52gM2lq@HpFbyqM46sU^%MiTX6vHb@I#KYoD)= zTGbgi6#JmcKjxfYi`ueZaPm)ManffoqR4kd)bk6dp{=;U9@0$I(2c~>I0-csGf*S* zIO>kpqDEx1W7Ijn9UmiqFX~3durM@5LDYy_3mO0FM0S&*A$}Y6_<Vt?@DeV;Ka}SA zu@>=lM0)A{_KtQE%aQ&aHFBjNU=*-9*1(C_0q?{EcmOqp^DKL+@3k0z?d6Nf&`7LD z&E*zUk1sfue$c+tWK;zWQRSOB=@wX(bQ>q#&v6iHDzZ@JZ^6o#i|X*S2oddIk7Ie< zg1V!<s0&_4HTV{4Do$c!Jcnhl=3={J4N>KqqNb)LGGIoAbG`!(Al)4`(iT?5$SNX# z64{9w`t6Lj3cP~q*n6lBeeHP3vBXk4zbdL+U95#IQ8zLG6LAb`1VX5KBd8I$59x5k zc#=p>GS;FN(*dl8N1XIWsJT3gspxvht|$$)b}~>64aGE^fV%!cRL9n1S=@!{$RX4S z9gn5;{1eeoe2#kle?#>!{$cxqL^MgKpgP(Wb%$e6<?~S;52K!vnNI#(C%p(&-@{J+ z22=;PVeI$+tIml}Q5AoS>hbTW*Knyv?EN4W)j&H`gFR5?G97Qinxu2_I-HFv|D5AK zRELjX6265I4aqk|YT`xI1(hDP8?1#InP#ZP)g3i-eNaO_7`0}`qpqKh+GrM|>WQLm zU=M1sp2k}EC92(G%b5QvL@F$^Pt-#VO(#?ZS*Q!gqMn9{s5_p7+DLB2`nVib@d4Cg zJ%hULJ5)WtqV7DN-AE0WbxeAU`A;LGE*UDAiK=KA>W%`a3Kw8)d=yRGfi3YAYHrIe zx2G-{tCLR0#@Gw%VE|Rn5>!W4p>AkngosHbit5=3)QFrxb>ISOM9QyV!C`&W&Nv4( z1#?kr<}uWo*nsM26r15O)D0Pr+dE@zyqn~3OvlK_MAWm2EA7Xs4r-2@VK%l!t${~T z9o&LX;q%xN-A~weycX+_-jBWTBkYKcp0vLU@=)n@n2KK_Qx`ERuCj-sHEIglVKMB1 z8rr_7kr;#5;#BO8PhmWMjq2F<sG<KAH6oSirRqyT4S5sPnz#;CU+0(+e!&pYMll(6 zr*lyaKZ>e&6Y7GUsK@36s^V`^9k_@!@Hf;*R$Xn+d0kY)J+ToE#HKhElW{es(!TLB zk%ss^>Vj%(>^ZFC*bX(9BT#dG7iQoJ)E&QvH{u0UJ-yfR-atQ2!Vc@~e<RwAy56<k z{!LjOBU)@BBAf9pEQ!}YZ4Xsv)E#F!=3sHsGact(Jza<D_y*MV+pq-g!A5ux6Yx7! zxl5=Kc5Pt(V?(yVK2aAnG)+)b(GSPsEvRz)Pz@YKb>tmXMW<04(K%EDu8m9+CSZ3w zgCns1Ci^Wo3pH}DZDRb@(C1|6PJhF6tjuuevFn0;a5xUbm8kQVP}ikyu{+WpRlci} z9)XQX-;5f8yRij6i>>f8)KpZDJi`w}BF(S@{*9fn!n5`r4?^|yX4D-|K`pvjSP5sN zM#RE0_^9I=)N>zod;`mq{suKQzoGVth%0LUvsoQ1M#ha;7H>kW@?2EKg{W0N(@8Hu zjldJA#kd`>#~<-dY_gT_JbVV5VS{b<jbx$f_hA)1|1*fBk+BFX;!bRXM^FvBp0gjn za+pH85pto?7frkwD`Nz&!3R(s-GX5}f+lv}Zr_j_`;(rC)oI_@NkkPM$8z`us)C<T zi>dq$RyHP~Dm;i9@{FC_C3eO7=tC`D3+rMOwQszG4e&f_3X^u(H+l`G(Y}#Rq&1E} z4b?(yfiGZ7{K3hu_q_eXWGJSRzraav!#1SPVP|Z(+pZ@MHP=fWm!mrH6zayF#)wwu zRw7CG0S>{R@doU@$DXS>m`HjN*2dMS=lK9?%8sHI<r!3mKSfPd;tTd~zw1#OQz0sU zDe4AyzQFkFG5Lm!VVJnrUJC`Nkywho@epcAEAF$0vK8u1ZbIGRSX4)HQLB6kYKrEf zhJGO`y%kN|jRWw5eGz*ouHA3j8ub`<bkYM*i*q<?r<;u$fu~Sw<waCSj-eWU$MJJi zJwKry(_$~$9Z1AN(si8lqKFe&iW=e-sG)xj+u#q_0aFjy6?jlXT!4Cv9!E8_5v$=I zOvblRb9@0cGJjxAEO*d;S{h+L(vfyVJ|;2`^}KsuvMUavp6}bx#Kou~d=6Fa1&qVP z$oe#1MKw70W&7z_gj%HQuo%9H>hL?Lk^2-Y>-j%VL_I8a$ga2=YRHmNi>!r{ZjULX z2cYh>5H%HZQ4K$dnt~1ZAU=<G;-JHPpyE3?6g$1bH-L-B6l4GXfB1;K8sEfP6o`M- zUR24biW{Pd&9FWW!r~alk~jlP;O$rj=cBG$f+ns&-SIxubw{xdp1=gwpK*bRhWanm zMpWfB`;#dZb)gsIaSE#98K?&Dz%;xE8A@Xt>iWZ215cwy@FF%u*X#CFG(vT}DMr*l zYa*(+8)~jHQ5OzIRWus&a5AbRAEG+&3992?p%&p^sI^i4sJ%9-qeiqAw!^mA0E1W$ zA3n<bt0#NN(2%@@eep9ai_PA!d)^+)lg>tUG=Q<8cf1|7hVDT%xD2(1cA)P3h;#lt z>M4jjX5ZK~#~A->$!JSPW6VJ{un4ud9zr#|4a?y{)Eyp24fSa>@ds47N^jcNo2dHI z@MF9Y^*FaVZjW36YJ@`(BC1f%#k;XTR(#8z)0<HpnuI1!M-Ba>s17}W6>tlx+&<LE zyn<@rP1K@0jT-V#P&ZcMgnd3zkBBZvM|GqPYAQNoQyhzWIW0gnZ~!%guc3zaEcV9o zZ`&On?l=Ke-z@Bi^Kb~hjG0*DomdAVMlO+xWK2ReFbA99Jy-^}qbfRpy7SjjcYXr3 zSU*ACz*$VebEubDrFZR-NkgrP8?YK?q3W54vA_Qd5UEVYOstNJa6PU?RXFsdogYSx z$U;;_yRjJ_$3%=fWe;r)>`J-~nm7$LQjg$uxF0*=MQo|(zs-C0s`p|a(p#|${)NM_ z^ZWK9eFz(oehGWw_ozkI=Cu7$>c%wE_hC!ig)Q(q)Z$G3z#fTotVy~rMzs1pMD(~l zfYosYnz#eCxK5#_=qs#*zd4ru(7xk3sKwO|HIiddBNRY2d>3AiOHns?4EtiuGd%xE zL_BBghG(JXatUgVmZR3hQ&<Z(p|;>7&iQkw4qik}L5Yv-^4Fm1ZHVf4M<+cHRev7p zI{!xzyN3}nYLKxI)$m$O#?7e5=nX85?>T;kCh7073Rd{o9*KIWw_;~hL)}o<^+VlA z7HS8)8P$P1B1AM~4`Ldw!DsPJtgQl{*k83PafFM%7sL$mtAEB{%i>V%kDIUq{(%EA z<8!;d`Phi`R@4oBfNk+lJcp6iU+^dpX>iuQ;C{>_{XAyUV7)Kx=e6)F`=96b;CRlv zzUJ!(C!p3)({JpbS|c%)^a^Z^hp;pLiCT;uzh$$+LgZ<R7%AVeNzl+hY{H4xzqc#+ z6-SXyp_`ifS*R&`7`3{in2ujyRjl@dy*3(S1=0gii*PJzio)0m@5XYt2RqWf@hTCm z{)F@Pi6ksWx&fBN#;B=iiNkOpYPGM%fp`R;#+pCc|HZ@0s8t`jV0ZX-)b)>{7UeF~ z6dcE7+BXt@vI{iE%A|YY%{UV4;!e~CavG~*{LlQ@!0M<~KMxz=ay0QEmch?akMnsf zjtLj-5w3`XNw>$?@Bf8FG{jG!E_?yo;StoTE%A#z5*09>bdqB&RQY7wfK9L(o<xn% z_joN<zGTmRCrl(g36t=yON_rNe4LDxxC#s6_$!lN?U%;Z-|Y)8pdP~pf7pw(Irbzy z5;a0Ao%BKch;*4h?J4{gE0I2rwK4uLdu^CFjCAwA7=N9ZM}}O5dj9vLhU~E8Q78Wd zYNXyrJ=ecD`L4h14wgb)R|Pc^HLwjf!-|-TS{qYPi*#;;h!)ur)KD+S>bM5=obN+j z_&cgxS;G};xGHMs>!3Q^7*%l#ycs*Au3LdxGh2{{%!o3&jfpHFt|MZ@6mTL(C7gp1 zGxm^Ibfj@^FyTEX&riJPRp(VMhxE%%-T>!9whzPO)H}pE_YQe?kk`va`zy)Z<rL60 z>j>k?>*f>=ph*};nKe$H%Dqhb9_&PzN_d3yZbBw;9WM|cLx?A6YKxBKtLpgO<=lU3 z3iaZ|Qo=4uYH<Y!&y(MPy!Q!Bh_}Ofgvx}+NWbmWdJ=a!am~AqNt`b_j!{0Fprbwc z350KzC>sB_oRh^laSvgRPI5d-yb9;uAijdQjsZB1ytnZq(r=&^<y+4Aew^D(U>`D$ zW6`mR_(;lZCwxY{H=(HgSefZ$d`i%9fI<xjb%;;LqN5@4y-peB>7QuXmtubb`9AT{ z#P7q`38kEJQ}IjEHxd>Se;D7zm74zv%H*OFsG||;1rw$~AL8#3zo-)&+ADZz7&SO| zjWRf<5K>9&NW-VNW+HK3C$WFXewDn^&Uuq_b%?Je{KolmntweuYdG;3>NrMxvU9;< ze3QIwPWr6l9ggqdorF4EvmUR(jyQ^I{ve);dQ3kcPsdAm1oseb<NPI!|3&8{(b#bl zf9~Mq5yG8>76ct@Z7%=0j_XpKbCq!~VG7}O@>@`9nsZKDbA9rjblRD5xrVFee>W$! zQT##ZO8nug3MqOGdEKtcyN>ic(svU!I{7t`ms;#!y}fvp4y?gEybE=FNc?~uHBQDc z|3|5)mDAIq$c}pXxR2OPoPUk*A;BP2fxjtNi+CLFR6a)s+)ntO@S-v}_a{L|XYwWx z_K<!E?<KEB>>T5N0~tDMlW~HuI(C95fxJ${A9c=W5N{tV$*UC~AROV`wa)oB9Qgu^ z?fHvw1J`^_xS4Yy!W6=Dq=#`%N9PEMj|r{qv~fKJdXUz!mH0&`ejjmOibg)_IPYku z>_2~Z6000Pq09@!pT*axXV6vGoF)Hx!sX|`4~Ys_6=+ZV6nR^?usL=jok`eF*hk(f z!i&WFV->=8#3O`yl+D1u$kU-Oq<N%u<Pu&ctXG1gi`svVlXWRvp3siW>tm-VOxhy6 z<)mN1s+4U<-mOlBw>akt$g4-#MEV(gkn=U1b8|^Y39ZPhggTmHIu^un|2L87OyUWG zj+Y44Dcl=>CiEpfi7=d?<0--l;?r>n*JdCeI>wL0vv3kY#{tTmA#WX_17Qd0S@<n( z<@_P6rXjNkhsYSLt2oM$eu;v|h&LmALFh|(fV}S5gY%n+x4>rz&l0~2_Ypc0|Ae69 z5z>jo^|kvh-bVUI!eZiS<gLZX88YUoG{>VhMnz8O*of~CHc(~}q3H0EchD)Lze)=` z@tbiD<z69tOMJd_&F9#UbS**;r;^je%WD45I~li7Xa-?8=~p;e8YiQUAo-sY*J}Nq z;3l3=NF-m!G|VE5CNv~Hnb3v!Z<HyIM@fURpHPXQV<Q<iX#ej<q4k8K;~P#iQ|YUY zo}9myP}0f!hWL|&7by2Q>KH?O7oid17ANmjtVy_$yq|C>;WY7C1pS=*N%P-~$aPfk zGake(<Yf_`PdtY(k@P@9e?ke;ItCFoQ|2ULNG!+xqb2D+lxu_C2}MVLCsG{u6=kCS z`x9do7yLjt#|5)U-%tEqtb`wt_XBa0P?~rtEJ419kWM;}bW7s;=+x1XP>isEypNT^ z@i(D6X&sBv$8}z8uKj<uoo_tu=)u`eVP$<p{0Tw=@ec@Nx%fj|M9}dxVK?Dx!aU9m zb*}x4_+f&MF@!UO`A+&gK14{-^WT-o*IaapFxa_xoKsL27jxnbh)*RR!X=dZnXs35 zZ}Ph0T+;ne$26PRpFaL9&bcOp`ziY%A)fdtoTL5!-^VYUJa<**{lw$Q)A2kOI@k1d z>_Glz!brk)NtqFZOPntttz!$pPZ&+!OhSlInRMiCBFibf40Y_sy7(A*U5KBE<+AdL zKTG^c!c&~n@s*>Uvj5yhxkrem;`M}I38N^t6K`+|-htoPamJsc3E?%u2?|yu)Fjj< z9M(zaSWUVqq3F1a3U%B~-cT${+D+Udej6c3df8Rw$ue4#Uap99JQUkeNHit{xF7|` zV>Thzxwry(3B=0~+7KF%*5M{ROxj1d`Z&wA_mY=HW+CBr(#1$`BmM*NgIATWZr}ga zm-9$bU?2sv$^L{;pKyV6A^D%;GSaOGI{f4<AW!S)d%~BbuRfZSSDnz7%(9$&k@yPY z69^YbN2-zW=D#jd1xe&*5{ix;L>fBf-o|9oC7pC4@h`3_8zh}i&=Dm(;hdZ8IF`Kk zh(AO7#s^nj*!ZfBtftUX!XeJTjx#y8ka%5eN%}d$J;W>GAo9N;?jk%*C`0-<A&L04 zoF7hn0PzLH-z0t~F2a>X^M9O-2Pp9GqXLCy5Z>p+tvHr4U*WxkAp{+5$y@HkhY?RE zUe!s5?IM@|97X;JQWKr?v7PC5F8YOxnVd)`{wkIt{d7?w(%%t3jrH+X)bTN8$Dzw9 zw-tNE&e(sPi;E_<uiz?Xb{W*iYS7{C3f%%8cgSM~$NK}J>~JW$p+ibsnQoqfu$k-6 z&+!DTmpfUN3;dH+#7)}Fc4v?G1g)&jv!ma19vL4U+Wortnh68^A=B#%hTQr2o*bjA zJLoa<+y$PDgy_m%9bLux4jnka`n0#Vin}lr4tRZeX3$+wnC}Uur>9$e`?#$teLF|8 z-M-Y2IVIqwKOw(4V^)e0>qw3#<jD?sa?H?pcZfg!*%kU3Ak!O+HE5zg9LnLIa?%q9 z`tqm!v)^W($L9&?tU1LS8gIrLXp!Esb$aubCKb8I=~mqV+6WgGGA61fVYtT(c(VNk z1s-3Hnd8a#gmep`@z$2Xi4ELA(@VF#`T3^LZ|3`bd5qFHj~Ncq$hc{-Cep3`eV;B7 zE>ypwKlV*@Ra39?Nd-gZWKSUI_4_mwfAy=MG~VxL^ok~g3bWlIPFrTCuZG5D%6M<~ zcyoX!G{qm7Xb$xRCVR6z*4)ey2|k92j!{wcz0A__>7J<`rj%>Vam@0>LciA+GMov| z_vhv5Ca#=`zCUoN%Ss)bQuSZ6**&vcMuIhZ@UVm{g{}32JJb!iz4^fmqq|2vFuHq# z?s56_JUig=_?TesEBejgLGjiXS+iX^)-A)%MaK;PBEFuxu#jmfbceK%V$+uwa2Ik{ z<YiAZQ)kRdwXPqPYUX+wZ8sC)4aF+%njiLr{C;LF;Lh>-)2$mv_3W4Lcjx@ibs0VB zrdE%ia^8SF?^;E<{xA(>`+cE+pJf}c7LMvyGq%+Ky~ctW2{UF{uZ_yDrCNd>cYqnt z3|_f7m~OQgy)f}gM!GqO)|~0sF}j~Co~I}J_m~>45~e2*@CTx0#`brWG3f*IA9DM0 zqQh>PRy>{iF*0>iJZ}NIo$nnNa0k4eAi4HRAY#v**OzWx^4w_k&K=U-n;q~Mj`#aK zW}_Zc3q1iZYn7)l&FJs-nm!&yBEg_$Jlhipd2_wg#X@HQCVHkZ)I5tBRy222qpYFE z02Lp=U21g>@ObmakMjq}_{WNiPRvVmMei72!j;OPy3OIV!-$BitH#B+G78o_?;kDl zJ=z<1Hq2@MaKH>s3x+%esX_C~<QBMnZaqTUPCV`$>$VBgBiu{Se)41G&D?;$z;v_P zC-ZE?9>ssIJM|=w<<3LOeC2yQg?emQoPn^<r)+m@w@5bz1z5bEDMinqo%V-=M7+Li zZ=pNCPmWn&m7Q3*w#JA)@FeAM)=$!ISs7N%iAS6LbEh$ec>a4W#TL$tS)HOEPwZMO z&g-)p7WR*}D_rg>7u#9cX>?tVb!^h~QXy}MM*DP+b_;B9C75i>>>1h&z7CE_$<Fo! zgSp}S*u&)2TG6__Y6V8ShmOTXvcrJ@<ENdFoh!DHy0f!c`|Q|3wyY3?G1;B(&C#Ic zgbNC($nWFkSRQt{4C6*B&hg~B!}%eze`Ys5aN~8g*GCb0;pM?kPw4K~q?%LQR2_Rx z)Ldn%&++Ewden*7g`KPlQ%+TnJp+w`Y36t~3ieC3jMxJiJvpUZ@tXg5*_czfxM*CW zXJ*!mw<_J{i!QirR-9$tJ~ioI`&CwKo8{HB`SyvWtUC8L>yw||yhW^O!{=dF;{Wq1 zOdsd<*>@1T_sdxY&QrqK*t;RXhC~0^{zLx2G;8g>J$lFP+rBEu%3>+TD*K<WG}zFm zIC~@iGn7`nb^U_81~X=v<9QGa$DkqwUSBxmxe^On%NJyn%=G%E_82<I`eeb>?t}8f z0e61Wp8h~VFvIi}#*)DfE!&xPv_m7GnbN^*k=8C@MAKgOE6*RuFbDZPIlQb|rC*oc zA{t!S!BuWhAG0S5yJ;8AqqTR@lB#+^@IWi=FS_&S(EB#WRmgS+3OymF(-RvT>$eBy zl<JZl3bS~oTen-SYWB$Y(s!=bN5X-1Driq+(AsKkvF1E@%Bp&{Qq;RRCqDYwLx<yC z3D(AC)m;hEmzN!J71u{XiuL#MBx}R+JFCUM6xg5qW<i*JDBzBLD>Pe?WnEg4^}vcm z3RRBiJ7rSX!<+Q~Z&Iyup~svM_T>2$J+RXBj|=*FeY?{WhWhiJOx{&~z3SaLyvpsK z^Dz+{H08KiIqaA`8}@CR_U8hh1w*RoqkwM$cY!}>vPZH}(-o`Jy5t(u+_8@FMG)KW z+yNRdIIxn}MCRi!B^HgEJKK98>MN=_T5;t7m(}x$dUYAdJa)-(?9xRg^8Yy{R^Ah{ zle@9sXNNRfG{$%bV;=)vrcIAaj&=5lX$dO8DC+BC^piD{O}5t@f6(YMA<WnWDVc9) z8U1x4y7I|i;)>Jd@owvp)iV=36ZnLPb-_wqlTs?+$@4N7eiDtN$!o%_A6{O5pH_f7 z$8T9{8&%<HbUp>iG3<|rqiemzyJ~!_rfZU{HtYJ9Vh(uc^3J(+U1CMv1Md9E?m{*W za@|2|{<=W%SVyBJ*N=BaXFpvk&dS;JfL0q@KHdCzQ+-#o#g=D_rMV098Kr<GDfT^~ z8!B|OD46*?Mx8gE-~OgJwY6Sb`WCx6ZIeMyUf9d}^7vwVCUu0F3U9E`AN10U|H?Zl z-THlN-;q7^);8VwtdT(8|E5LbzgnBjdxc#*_U&LggKu@+HZrXzx9(T<o}#DdzgD$A zL>}EXz3zWiJuV#7NZ2!+ZdHA*&>lo<(R0<z*z{h$R~CfXqku8<OmJ35^vUP)UB!Fx z>K|lP-O;VYfpvT+`=ePqrnt)3cODzr=<c1XU8Q^YLM(d*FY12&eDSKWt9UK;nBXzJ z(<g<!o<NS}+n;((*5ws!^znJKy=sdI2x`@*TdViyHRWw$3<<NTDAL<sz$d2BCyy;9 z_V^U72>Z>KW3_zo`hNZS)Hx8%_51w){?r)|yWHf9#~FEMHq2Wm;Pzel>S6JlKCixd zSbR%f+!2>G)XF?ir~E%3o#^-j{AbW#AN07=ol(-_h>ffsuGq(p{jFpCYZ&{#oaAcV zgFPi|=7(7(*YdIB)<oyA1L+Oxxw5X52!+ic&y=UYZH+th)Yu-A`Pz-$JC`^o>@OX5 zGjjg%oS3oI91I5wwF&X^=1y7ml#A_o>Bcbqv~YHq!a&$F&MJQRy4s!qA1^EvzKHw= z53o|27^S0a4_9?%{KpP%ytw!B_WoZBQ}?2;nr^R5jdPyeL$Cf3S3EZUR;4$pTW5~; zjh;E$HLm8rUk*hpKF4h(zq#wmN6#B?mUcNS_|xMfiWM;BR?oMdN$SZvMjMVcTYF{L zJJiLu0?E!YcP5RS%4XZq9;okOPr8+N;&G>UmEUgV)E*u5PJydNChrif7JhN)J1Lvb zEZzuwAX!=G%+VRnk}3>)xKX_w^<&tr9t5;}$&7s%#b!8HUkm98FYfK`;m1PostN<9 zpXKOW!nE^?%-zY_{q6^ihHB6CUG;$S%a1F)>{04@(Eicj2}YNm{Lba-AHDNESF!rB zi!KizzdrxtJs6$y;f{E#&nKI#N}pbcCVn<7&bt4Lm+Jg;oBrnuiJyC+vs=^rJd7T; zlzeUEne>K*!sf4+2CvMkPDTfP`A4Y+_S3;C*RQo~?X0|2O@51TnJ$T~Nh^3hXnlRY zv(@s)+FiJ-$p=>Q>FK}x{l!zw27UR#C8p(E^Z~}JE#N+|ak{G0=b5$m$4;zV<Ld7) zYOw27wf{Qtf2i8voz7J0#bUpe4Et%cI$v1OF!r?;aNA#Pe41STR`Xwp1&#j)F{xp0 delta 18004 zcmb8$2Y6J){{Qi_385tP9_rFNQbg$xLN6h-fFR%|S(1fhH*7XkaRrqoDtIU=ARwS3 zAY}moX@b}QD~bgvqKLhq*iioO&z`|w?*0Fs=l}fAbBEW=oH;XdzB6YQz4soUUF6i4 zvEg&&i!8Ia-i)!V8n~#kWxX0}S=XDZ*0QR0wyaW^h$XQRCg81D0=pag8;2Xmq1t6* zbqrw{T#Ti0Eyi0`*xF`p>_-jo1{T3HSRT(}ar^<x;qMrS@m(yd5|+h=*bvL&AgqYv zjQLof@_f|5Td^n}z<9>Dj*%%w#ha)%`T#Z11=K)4oAU3d2jjas6Dx-mDAz~c#A=V4 zXfo=#fvEn5n)02fanezdnTW+1-<m>3p$Ve~egxHU9qNrXp(3*%^~SHF-sDr%0N<hp zyoQ>{bz`w^PQT@`0rhoJ6YPt6ZU}}o&=@jW;|$YaGFGKL!<22*1Xf{mLZ~I$k9z(v z>IGg!h5n53ThxHA?oOoQQEy%gwX{vU6MxOP3l*9`UsOobP#sT1g*=GrXgX@<b5H{= z!Ya55*|*j%RJ#+XiJV3a_!X-DS5$xhm~vcF*a=zbB&VZ9)BvqfZ`=tL>b|H4N1_HA zhgzxvtc~+fZ}g0*-;bKWNmL{+qT2n5df{q4oEK~mCZn}$fi<x$s=*M{z;~h|G6@xW zwI70uk+0PH3>AsXsOPSsBIinWo-2X1DAz<i-x)jNAe@Ea`D7Bw6z%B@=teEYEf|Yk zOgRaMQyz?Z(+#MBUch;H7}ar~Ue1K(pxQl(%*on_G_kHAL9kl%W-^R#1;{8=Td)qk zgmHKYwG>}r5xk1(@ORYGB=m7YS{2(-ZjWl`M?II13jHk93oXZDxB;~nwnSz9cau?w z4;kM@J#ZPdd453+X!UhMTnhCWRz=-!iAAv^Dq=~fiS|NGWE9rGbW;wamSh1YGQRaJ z8LiDx)aH5*wX45IbsW>r>97Q<T@BP;X@R%l08|9$U>2^x6uf~GF{Qs{b-;Z%0<U5T z>^Fe;KTc*Cnd11J@fs>3H&9DaVW1PbL}MLPeG}s?s58EuxjzKe-yNvUnT{nfh{Z6B z>VMuq*1sE>mFC7L7*F|<Y4E)<W{|Tam2n04>ticCfHbkbLsrS^$ZKoO=b)BgB`Pw{ zV^cha8u)wUkhEe36aQ2)NrRn`K8pITx1(ly6g8nYP)qSX*2c5w#_OnoYSPJ8Y=x!p z9CpHQQT;XMn^nX*qV`C4)N?(<WHiu7RLI7o8fK&Rg5UT6s@*EoL^fb!+>F}wr!fIP zGWWkg_4h57#b2>C79HwLtUXSk91fAmA@d2ULEmA{?w)~qaItY6x+(9(GWZ@g!b_-u z6F3~IVHMPFZ-e?u2BS8s7t7;)rhYlnf7seWhW}Ux`J*D1XNxKVjW7dSU~gQ4Y-sC4 zEQfW|oOZWj1<D;!1Ek<=^q>zfp_XLONGF10PzPQHR%U!_78wn^6t#Iap*lK>Rq%b( zguX-Vg{#;M|G_rcbd*ybi`rxhur2PwmUsnk!zy?1#b65Fiqo(S<6B$F+>RGe9akOg ztZ`4{0Mr@}HRX|5iSjs9BtobG=c1n9k6Mz;s2BMe^@4F@oJdr|l9U@`Si7+und;a9 z>){BjjMI@b+FFd7`6=v(zoFi=&7ICf#~L$Hdt(wR<TFkE0@TsI*pzo-C(8ToB>vjX zznUA@Q8SMn>%4IptVX#aYK?C}y-_l1Dbi4p@tE=iEJ1m)DbGZ`>0H#;V`C<+G-YcX z@z*XdKF;||Vj?O6U9kbCqb4{X8{#9VH{4@<1vS72s0m&|4fLC-zk!NG$?^O^VKqE} z!%&}R+3;OX2Z^YT8)8+w4b{<LjKPt(6-T2cRNUjNeHpAyxeh84T~HD1ftpwfmc}ut ziA+RAXc}s~@EkG<<)f(0v>FxiZKyYT1GRb1p&q=78sKl7izU)6Ybq{AeXhm%V;JUO zlFPD=pk5@#>zo(ysQ%huoWB1)WE6@)s0pN@B9M)7I2-lG^HA*`MonM^s^j&j_M1@? zd>IvqH;wP1zLpPB5&0e!k-xBwzW?|MPKRAk1N1|6Fccf$NOOM{Dr5^#6MY=jelx25 zE-Z@sP!l|Yn&3&){nMyOe}tOQ=QxJ(tv^kJv@B;cjzK+;gIfE^sHF&DBF;yJ@F`SB zFQD3=!W#H4cEul1Yu+;3+1#B`We;lN0Ss$^X=Gw?CaN4pHJpPQXff)|H=-u88x_LW zP5DDqM7~8W#XnddtN5G<bVg0Irzwv{Ek(MI_$yQaQ!x{ZQl5`>@DY3)cc9*I&O~R8 z=V5WmOOUh3dcu^ip(c0(i(!RHPP^);e(PfiY=&{zZW8gYMWz!KI`KxM9-NGNU^?o7 zdr@z&5NqOERHzT3+I@mL(LO`HS*;w$W~lz!V_WQwJu!sIxG!uP{EZ5ET&~k$1$0xc zhVj@5^~Sx;{lTW3h8kczDnePPiRGY9$_1E!t58e$ESAE(s0f5#A)_}qiF)uotb#vc z0T%Z=d*B{ahc;?$pT<&n0_);Or~$5{B3UxPPdC;>ZQ3DN4<})CX|b8U{~cu1@Ke;U zU_ze0OTvRmI0dytdr=cRgH7=V)In8kvU9MsLrr8XYJ&Hg@<vqTUc)l@4ywP4SVG^w z6?AUIV>yb|P$6%QCGmFDoAyNQfuX23%*N6f#<KV@>bW)84xdA<^#yE<4f35ANI^Y6 z0gGt-S!A>Xb5Lu!05#JksIz^Yxqr~qzid2h>OV)V@i(Xm{f6qNXvmp(Y1E6<zzWy| zb-xRS6`EeABE^`7io`h7gL$Y2XQ9rAC8!B6MSUGBO?e|~jklm8@ir=QpP<(KD|{Lg z*jU<YI}3=vo6K8O)ONAyumk0?{GxQm{x}rpp&LIo{)K8+V;Y@e8m3|Q=}x;9IEeCI zY(&3BXIRmH&S*K)>DP}*+}}Kt_&-eMJ1Uyv-FG{ia1*wsd=YQM3bUL|*&jDjo`sFD z-aWKufB~pYcOU)Lz-O@&9!D*S>t1IQmPJJ*5o=?kFqsBq`k*#ZE~>#js7<m670UIf zrFtF}@)uB>>LpbD*QkhGMXha%*-n4$un6Ve7>fh25)Q^581|Bh&KNsWaUAc%m^o}n zoXa12pahj#^V+D1Hbm`_+fjR@A2!4^jP4cGb4yS!@C>HlPE+r?KYBlGRUxAgwZLK6 z8TG(or-8K+Yf|1}>fgdR${!mqpkClID&(;bI1%iIir^qr#4@oy`cV<Gv8cZPm1JV6 zc*^*+Y49AbrhX^tO;YDNYc>HD`njlIyKSh5yoQR%N2vb3!R7da$}zN`#|EXmaDj7B zoxoCzZ+%auAzsIhSZ|?oWRAlwl&9e_+=E4M#)Hn9hp{5%`51?*u?Mb4P583$Iu@l| zevvb=L{vXDF{}scnu^A#4qKRV4`Uxx$Od9O4o5vV9(4qJQ4zTpb#QG$E$KegW_}IT z{sYv&XEAyNFCzZUsjwb$POuiJ1|3iXc10p*^+vrxKh!2l#rJUyD&z$ZJ4-MRHG#*l z6h3R*V|>~8E~?#yhlzi6GCxvL8cROnY?|t*H*JGj+b&ohQ&1E1px%4}YGSidA)jx` zPonzWh>h?iR6k##_ROEC%~>J5*!f4RrdWoG;aCzgQ4^Sqip1Tfd_QW59zlKg8&DJ5 zYVIFFH{}zk34V)uv0_V{_LWf+u8k!y+|b-;Why$LI!re8X{a|Fk5#b1)IWskcsXjq z8&PYz6ZQSSgBsuq)PO&r+FdioJ{p~1*eXlrR&F#yJ#d$CBI*r8sDWppBC-tiW@}OH zUqlUf92J?5P)qbZDsoq`9R7uRu9WSZCyg=s``??4W}b=(7(g|gi5hS@R={<rNbE)p zd;-<}ebny0h<d{>QD^%vSRX4cb@oCB)TSJPI?ysOLEryuGV1Ul)Elor4ZPO48Jkev zgKBpHwYk1VO|<ATr{g-PlkgVQ00XfNPQ>!K8ntBGQJe5EhMSW4kW4*{<+#!e8=)rB z4)tbT(TzP(6Z2vP%tKA!eyoIRa4hadoq!cqIJ>+GYT%Zrz0d_U(Vi>#{%;}Up+ax8 z1UuqZT!2@wB?cdJCbkaCQr?DI+ap*RU&9PMj}<Zbac82Va3kdm?2RR!aNax>wdB4h zh<_h4^Qq{HZ(|acS?QFAU?a+lQERyu6_HO-OK}Oc*}g-C_-Cw!ajTraPq?us<w2;3 zJc`jhg;go93zJc3_M<v{9TocbP@C&4s>3VB*wxNCQ495^El~p}qdHDQwfA5Zyc^Z; zQq%;V#6)}wwbbDQWa^Q512ypX*cgAq7U+J`+07}aNcgcKE=RRLh&AvP<0aIRT5Fu8 zY=wH$NtloK<8b^PCo{e^Xsz?#XdK2d+$gus*<87}jq+^NTDN}6iAV?3oAp91#aLqo zYC=9#`ygtw%|N}-Y*d7vL$%wD<@Ni2h>Sw<j=6Cj71}E}9{)ylICi}=pckWWggRL6 zM=jxUJdGPsYd&KG|Lz|TV+vMfC+fK@RR8y46~?!gk<l*Siu#PsVn6&D)3Ec?&cD~Q zQ4b!*I6RGN|FJ3mh^;AK!?xJ?8RzS`6W^kok7Y3VS?4bz!!WEFEhVE4w_+?tP;d4E z>djwAZN_(upJH{&Um4>zIT5IXMX7Iv?eKOig1M-ZF^K9XY<zGN@z)!zprRvg!LIlv zK7b9Mb2iNu)TVN8c0!nnwJ7^g2hn_Nf~!#H!z*|jp1}kxy~X(|s$m_<?NHB++(P`_ zWF}Fe&u9_W#5JhT<RBK{8Fb^It<LA<M|Q6@4YToU9E8K3cP6+FHNbu>g>RzzJBxH> zT|@P|KFk)^Zcf~8b~!eq+yU#O7i;4}tc%Z~zVB1m0I#5yuJjJ)%`2iJ(EzmsgHe%t z09)g3Y=d8#`f#<K&ObQx$41<^-;}qYLU<XwW6g-u(Kyr^KWtovn!qY7j!$7@+>Cm$ z(>N5r#SYkQm$Mh9BS&}GnoC9p$ZFL0xgX2oanxq~0JUZxV?(@d%Jp_TUq>(08?M5> zcoG$vN_(7%bwW)z3Ds|3ERA<yF@68pWEAqeXayZ%3gr!`H@a%Pj%6svz2MYWLWQ~( z*2BK27s)}b`2tjAR-gu4ZQO$D=S3{1@BaiDeP*9x9)4lUX?vaTcN{8o1$Y~7!!CFk z^<4dZ&YCvIl9b1z2FSrmcsFVfJ&Jmvov28>h~e^NPLdgb=kX#o+wbi1QZG6kR>6wY z*GD&YKn*+^V=x_g#PXsBa364XdlS^2N=BUnlTZ`PLq%xT0phO-JV=FR^dy$RZCDQX zq4vZHQ~m(87p|b*EdHPqayM$=E~rrV#-%s{AHc6M8}kl1|BU$&-s<89?lAG!gXu?{ z&tno+r@R`qY4)M+A3-<1j`}RWMy+j`mz+pcLbb1nI&kWvo@<3}?0|aX(WuB}qn^(T zlhH{w1uNh(<0ed^d=T~E4OEAvjyeNX#s-vYq9T}#dOi*7;6zm97NJ7F0yXd|RDT;# z{e^d$1}~x>ID(0I0<-W7)SC@D=Invts3r1Y4CbREHU&H3qo|3!iS_Uo)WB6=c3#wt z{V8`t_CnZNNTwkbD^P292sN`)s0TkVo<j|I8MRlwLJj;UYOhp2?)1|bb$>AGq`V9D zLNl>BF2kmH6pQQo|Bg%zDz2d>P~jCPM0HVb+6oogj;M*HpgI_d7jX_2#mrZo2u?yh zHwEY7y{PAkz2@wNvZ(g0G5Wv%X-}pY6-lTi=!bDQ92LURs0rR<?yp3(e;PH=^Qb+v z7hB*NY=?2LI|FyZzLa~S_R2%XwHPizMTAUOJcvW_Iu69aZ#WZLg$n62SRD7FPRy53 zyZt*<KYyU!EcS#G!FVh}xhiVHHBp<h0an32Cy2jJs_|54Ed!|U`ax7j>rfp(i(12- zSQSs=bNB_S!^J0^`kkl<oIv&S2i}5ZPB|xOH&i6kum|R!V*TA@_E6Cf&)}{24|c=0 zZ#qY7KK7&hG$!GXI1<~u<!q{j*qHJQ*at6QQ*8XU^H;lJ*o5*NY=c{{HJ%TXDNUx_ zJI-!(V{^)#uq0-q-nanW_%N2iZCDl$V>x^q^=6-;_RI~`TGxBmiNtNFaRy?09D{=~ zyp&9TGM}S1(Jk*e1E!$XEDN<10o2-0K`m7n>*Gpu{|IVguVQ69gKGahYNEfP*1Y&> zr(ZYHK5R86qnWiwt#KdJfF6v;Y}6jeH_k#g<#|{EpGCd-0o3{M5o+RRQO|vj(TQL) z%D<r|TJL>Di1lwvMr+d>cVZr@!+IY$q3n*MTzua+i28$PoS)x>51kP9L#_SeSR0Sx z?f4n~fYm<Ymk;AVcE*{4S(Mk}z%U&b|HS#+Mx5mw=f+c*jhFE#4*t~H6Xnl2|6tJ# z+fjc%cE|nL8vnxW*y6nN6`aFa^k4FV^X3aLIzPvIa18CvVOXK>_?fe&{ZLEb#Wonm zmbeou;ze`+8fxv!UUEX&4l7aag<6s^*cJm=3fE&-+=1HEKcXV__a)-51ES{V&YIOl zg}y1KVIQoAPvT%aikq?g7yQEt9>V&VbJ^KLOHePc7S+#w)TTRwT9O}712_88x!>o@ zu=AN@QZbGjQ?V|7fDQ2v)MjyCiT>9_Rx7Mbc|A73{piLEn1HUYoUf-8>IiR$&9FHR z!BMCPJ%{>=4u#3+!H=;MUdG~B_iHC2%`ld7TjTAh_MLDG_CbaI7gVI;zj5|TOVpki zgPQO|s7P!;^>+Z*VfYA{+!+1_^sTc-Z-4JRSca_|PlF_Ef&;NPPC-TJpebL#3zQrE z=$!S%e{v3_Qm7@Ujb*Sij>3Vaexqa9I!dM}H_oGGb{Q4AuZ=&N``1t*`x{GO;?K@= z^{^P_rl<+GK|S9EYhf~KGp3^sqUESf{|uJV_rHgXI(WtSCPw!FYSa9Ida(6XXW$-~ zKzSf);-gR<jz{ewKe{l7KX#z{8b^c`ji0G|j>IZkGjO`T|0>*ENyYCZT}LU`qdo<1 zUI$EGe`~Eux_ON!pHATl{z1Bv{7h11)Ba8J50FN1uRKZDo0v%ILz{4_Q)m5wm#8dG z<(=4`q-!2>P(`oZ{5h0zmbo{EwhxmZi;t81<javbVXYS_-@FKj^(y6>xS!OGHj^lq zjZyr$zNX^l^@7Rp3v4|>DnmnUP+eY9KKVS_*TnnrG3tC+(e&|xu@d)R*F#+6sB4dd z&Apa5hx-W{{~m5^qM|4jhp;8?B7H)A3TXlP@2LNq{21(p`W<+L6vMry)NMyy-;q3& z=bG|+<j;^oq+_H3Bwh8n-<@<(@1I0^h=L!t(O@F^uJ|<Nr^#0$--YxfWnF7YuTZ{u zrPHQtp|a}vs;!dL564}q;NlCl+R*M%>bsEEkivIR`3#wZq>ZFHrlUOabI4D_hNkWp zTtoVtG+sS&Z6*Ib={osMq>|(dUu`Jl6|Nw^nm%>y(fWVP13H;>&F98E(o*V<n~wTW z|7Y|r-?yoIpYlo5iEjNv{cO_0LUs31zQeRv8=VCaQZ}iXsqgI~{)1IX#U<>650P|~ zP9oXl8)M;X0E6gfwWF!LmwXZGTA0CSQr4v(Fb+qnC*`}ir?cc)Qe*N@;b^tf`Y)lP z7wUS;JlvnUH1c(v>gc~LdX@5I>Wg3oE<s%ZybJYTM31Nb26iO1Ca>!@^6N=D<a9#5 zrAn?GQg{h}WN>pN=`#7d@Eej&G+ntQH|74(lJmzk$^|4{)r{wzqP3Yphf()6<?l%y z$d{vTCF-<1Yuel7=Q;0B&Rid2T^dXy-EC%n^PY?&J;J>&OebGqrpfERkF=Qdn+ja> zNqe||U$o@>vW_%)2jZW_KwnV06Bn7ADLj;FCeRFvQoaq>;(n5@mZTP>DWu`leMq{4 zHj}WqX%jXcr;UERR+F^kx0=2es=-kzx>3PjZuGxJu|}Ai&B^OpX37(tqBWOi_M5r` z<cpI^k#v3FU|pa*hkQisOk3T$mwYYKEv9{VRH2({Fo<#{{$v`cPFDlUwMd7ZqSf0x zw}&!6kkP9ybwS$bFC=40FOqLW($xy%xj&iwF7lno_i#mn!S1J^{{xb)?j&Vz=a1*{ zDH@-}n^ywm5)?XeKbiavHRhU2YC^sZ=_>WQZY4FKJjj$cQjRnE>eQFh_+c_{P}v^q z<7=cR$alsK>V>NvX$$3tu_CD%$)a49cCV0DP#i`1I_X);H?NbFn~-!>CdEc`9ObkL zD6h@Xmckq6p&c}iCAFfyn5ip9`4iG?>Y9>tJxKbSI$hOBL!vo8GoI;dp1TKMqQ0jo ztI1!aQl|VV&ujeXRhq&wa(A0fb4;TNlrNG}P5n?){|@C@v@cKj7E(LPT`0dsDofI( zznYgJHRt}X+@DW!ndfz%D{SqfGD3Qk(h#gn3Xy-FhU-ZDz0`V=x^3hak@it8d`+V; zg!;{<@-f;@H+lVaYy$VvNQJLB%B^X)A%^!qWA1pVc$}o`JdUQ(Q1V4cLGq0-nRN4- zPF*hTVo00Fzie*2MBYO>LH#O{uBnu3lW#%lLB0od50OSF{)?y_jdi#&oOJUlL4C3* zA0e;nkSV`J`55Ig_#%Fb6YvSs?icb$X!{`P4f47cb8jhS7j8D~!uNCIAZeRgaJ|fp z&u|6}Ym)zx)SL21$`6=_-ZkDw-9hqh?k~WP@D$#SO-MJd)zm#eTU}3KUrcn$Ve2(A zuTb%h?sCmE4J(je#)FM0zf1mi^17-~9%$MO#ve_6Y4Tf1t!a~u>q&dLx7^%cfl1W; z=SBRdaH9gLAL*pIyOI3P|7;m8rBmOPP7dQ<tWBy&+D`d?yo_JcvIO~j^ZW_yPyGQ? z-iig>*R@!OkFIm1*U8r)9i{Oo%44xHX+8P3@dwh)t2+7TN&9GjnDnroEp%V)Jfz*! zcc;EH`JLo-jUjc7<_dlPvE2NX%HL?*6Hid~lAfkKmDGW{&+z6on|l{11W?x&T*v(t zrmVVaq+68Xs_kG+q)o`QX{x{fRiKb)ZtgWE(P)^-Zy<k+Hh*D)dGIv(k4PD`=||mE ze30_Z>ur;How~=k-<h<Ww9eFTx!F+XpUn+jZAphn4M{JXx=!RzkzZ@d9q<`CT}ZvI z-Gv4%Nx2{C3TY?xrEmaA*9Vl#qmSojnZ8tiT<gDv%qqNry=Yj4G?4T@N!O#8g`G@! z3T>Yyzks@i<ln=-l<SdSPJSs#SH5Ylyq|kj@o~~4<R^2l9rFJFbKOh!1uCB-oih(_ zr|ve=Kcr_!Ztm%7K>C!_igG4tpeniA^4vY<**}e{e^q~Q{p1k+*SEuB_<1-&#VS)d z!-|aSR>oyd?!F+BkW@Ry?wq{HK9@YJe0E@}dqU8Y>viV^0~35X-e6=@&#|#3eEz(G zkUJ^0Uu1iq4zbnTDS?pN=g$v$a&o+x{GTB5z3wcY>gbNd4(RGCGBH2kw|for*~bQs zALa|?yEA=3Z$>B(oZd1%HOH&wxq&HOw=d)l1>7EPPnh5hdi^1HP9Vb*@&)|vK+qj5 z@Vk?}xdm=dzT1<T>+}2aLqQL{)Uitq8etDl*%*l%T-IgZnp(}yO3kr<NllFm9TFEC zd1&|mmwkRjwTk~W&z=L5JG$*sX=!#rTDu}sd>P)z$+XniaB{#O3I=lW-Jxu++mn$| z5cFhBcLyf8C*%Z}jX%qs7w|E;+=85tFE7Vurs(s#^Rqpf-e7*q`1qmTj6iO#*PrRl z?C1^!3%pjZQK`L#4tEbu9o}znO8hV{?@IT%o@u_^f?V=JuP4);Pj5B7dexARs5hq1 zDZXH+z?0+7n4Xd2&2M6t8ok)wIQmLLrkhygM57Z~IOeNZd)@dN_LcFS>=}1;voG8= z-u8IrMdH$Xx+19=m0Xc&ncZEH<KC%pvA&Sk?l`Gd@vJPpiqCIPoHX1%Iw>X5o6Cy0 z)0xPm=nGndd_K3Ib+m`)Y^aD<iq{kL=7-!VUSC#rdLS5a=T>$_+WJel><NKCY&&nN zJ#}(@`^Cv`L|zPbDiUcib&t#bW7_1%^ywR2c8i&#BAaHOim^+EPe+R1`*)E@g9mEH zmU7;Fa)HMmD#*3F&(ACA3*{&>eoqz~?)mxQn2P>M{=ihf+ZznB5dvBgZ>HVs!5LNl zYe`4sWqLx|cLyGvR6Hgp!)~y&^%!<at|!OJ&B<uh+MVYKWm|sl6mQT9gz{RZ6XA?N zK~ARI&mK<qx+eq*{F(pW^5t_k7%a#Oc{8;Kv)Ow-{}fM-kG1m6@Y-vZ_8RcNJfH9N z^2OvvyZ?XnVdd*H%g+dUy?(p-vMhVwvJUY*y&1k9-W*?GnEl7HX^Ep+^zr(=(cRwB zof`0F@~O0K*`{Ti$nND`TxBvcy!rZYJi&}?-;{uTW5x2w=Et_i*nyQZiVgKnV9_${ zpH{ZDyRUk_`k;Wmo1i<-8|0IAXX>3pfh<0AkNwT6v#zAbiPe*1tNMcaz;&AsG3d$k zSV_lru)XbbH>yTPtlJj-?9Es2$qZQi3%pqYcRC+4;jgvv4fEN5`}Asi?K8u|(J$8@ zaOV|x)g`a%OJ@i#A5Fo(=YcQKGXChgR02K0GbIpg&L?(k6VcYY1(<}pAf2x*!*?vg ziQx85^kx)<yw0J(=SHi4kA+N*1?%X#e@C_dd8WVJWz)-%s?QB_*|Rn$*jqM-?Ezb+ z+3#$r+>Ecw`Q~|%pp`VSAV1{gl=9|i7xHfAM9|$x(XGG5#AJKznC*8*-ratW%O1D0 zQ3bYPp-?yn%VRrzcCAQ1dvc_ry(W^KP-uuq$z9p5a-N)APllHd%twTI9e*T$cZryr z$B)OI<Kft4ALK{hm#9(kKWB~;1>(n>dvr8~_<*!n9Gdz6eG+jHF?n+m4Ngt&cg#+4 zC#5B&j85vA6yJ-JJ3Ysn;blE?`ny9uZ*E?|oqKE@XHy`$0~BdzKxZ03Z#ti6CP%f` zo$X_N8fzlH3=M4#ES*&oU)T_-vbUbAN^hT^Z)OI&j(3QjThZPk9ri_v*tHKO+Iffi z*_98Ev{xLSZnr%Ws9h*nj1lq%Iq$X0dIy5JocWQnM{-<o`i;r62OUk0Ja)9e6>0qP za#!TF<3GmLh~DsITD>OnqcUT1fzKP{*@3<(K9Ao%d7@FdetusD`OJVj-y03DUG`*F zgP{c>k9*L-WUFr=mt)ZCr33cg*Ulrp{J_~8PPTWY+Kaa*me19R>GN1A(dKTBOS{pj zz4oP3H6mr+oaJ))BMaZkaz*OAQ^{rLzPsL@{9c>LOYfC;MJm0Y7gMs(D|#af&s>kO ztAA1>^4-ThV(bB*?zJnOyHR+{*oo&yB@k_X1oHy~+%7o3!|r-vvi;VDHHF(n@-Mb^ z{YS;-&vIS%^PiuM?E2yxmn%22_Dffh$m`$iiY?t&E0<AlY#qNg83Ft5AGXyl`~hik zL&vs;3RqPuDThVy+n(*!?EXKV{$KWP&7VfbG#tiu(5?++vR#NSvvtnwOuqPlKcGr0 z$*%EB4SVa=1bfxh)h@T)@s}F>Ir5iXwF)?se0+fe*+!gA{0i$UbbAW;B@G4aOM5y+ zyuY3(-l$OMI7*z&tKsrEIihRt{B6Y^@lSqiCcgr&{*zQJ_t;i`2Lg7n8#Vjn6+~BF z$4|5??W~)>2K0AqojcQ`!!E!n!`I-ChCRdqd2C039}4%BW9#?@2-w~m-T1#D6+Y~v mC*1$tdEqAi9|KsqH_vKk?|CeKxA>C1!GL8Vx~zd~#{U7g9r)4! diff --git a/bin/resources/fr/cemu.mo b/bin/resources/fr/cemu.mo index d31685fca1c4072f58d2fe0e0b467e562a64a205..8328991f01eb915b5ea722452ccefde985d6416c 100644 GIT binary patch delta 25093 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 Cn8+#s delta 17309 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 diff --git a/bin/resources/hu/cemu.mo b/bin/resources/hu/cemu.mo index d7f761fe66fa9a72d7e0ec3fed2c6a21687de3da..dd3d2fbc74157e457fac7fefe3356bdbfbbe9e11 100644 GIT binary patch delta 26825 zcmbWf2Ygh;8ux#aP($w>PUtO56I4);-fJi-2yBu~vShOxcQ=6qaZypP3RsSc6^+<X z5p@L>yAegPU_r4fD2mwi+LizJcg_R?_j>>D`<~C8{mwkopLyn)IVX7E{w!_ZJv9;^ zHmz}$#nU6rvf9JhZ7gd<4a-_RP*ThK*J#UX055{|;W}6k-U{o&`wX`kK5O_2RJlE{ z9sCqFg4P(zY6u#`bjwOuorox-H&h2hVGTGQHic7Q9k>WKfd#N8EQe}nrIB9>8<Vbp z%)xp9c84z+`H!$U=^A4_wt-#g-|`Vr#Vn`>b73tQgN)EBhqdAPP%~ZwHIQ|%CfsP! zo1yAG4K?r`uo-+0s-174Ch|M13Dd^W9{pQ&iAc~4s-regs>^_?kO`%sAy6HUgDQ6_ z)W8=*Y3@we0-g;ukt?AFyaB5HO;7_#8a@FNYWM{r-QYV=9Ug+JSZBP~QBx=zXbaUr zPpERkVQV<vq!&XCBnUOIQdk3CX!2JZUIl9*Uq2rG*Cuis0(H0vYK9L%HShw|0N;gL zyDtq7K@Fh!1W(l+pj6!-N^@h3d<N8vbD$;=hMK@xQ01?ffc|R}xgCK<bT6y}w?GZ( z38(?=fUV#j*b;sR>%iI*y$%~grQ1Wb)6Jy&LJeRrRQ*$+CU`2W3zsB_NM&)TH9H5Y zfy<yeS_f6(Uf2mf1!Y_x8~Kk=nyER-TZ*<&<px1@oCP((g-|w-2RpzJOoxe!iKyeN zpj2`zl<FUbGvNy`4Yr%?X`&NMC*2RK;h|9VM!}A7E>!&zI09Y(SHS0>w&j>9UVjTA zOOmjPh^T|*a5hXp&G1dA4nBt$!f)XyxOA#FkZn-qUWAf=0NLV}J<YO4!T>x8-T|ed zPoXsM8>|Zt!5Z5Cb*Fm`HH7ua$beFz4-SUYpsaWmRK<&+tlohQ;5|?S-3Da?&lv86 z(#)HNUqF@r6V`+2Gw6^0t(HWj(k@We>4PdT2Fey@Kxt?m)Qsmt4I~WZvX`0kRZvTC z6O^i-fQ{iEC|f!JWrSK+wc8pd)L<tfDmW0zI7UOM%nygdQaA)|G4ih<+Otw-c@55h z?~pElOW>$1%Q_Kmf||(Za1Q(tYTy%D=WAisZ1jH(k*^U*br(=dycB9C*T81*A*i+6 z38m`Spa%L5)KdIn<c;Tg<yynzk#~eTeCEOvVASN_ZRGdQMgMB>Wd!PYH*5qygtCQi zpz{BK6JgpsPD(fx_J<`<1H2uMfZL$f_;;v!bx!q`wlP$>c82|+%8gDCSx00hRD%Z~ zhHkYz4YvyOVQaV<YN>WWEzNtdH~bE|4WI5AYd^RUc>rqZo`o#Cl{Vkg*lgH@bQEfU ziB&{oe5;|1VjXM;*TXh&8`N%i6WZ`6sDZRt;5FP6ZX#U<HKQJ9Sk^E&7^=N9p*ma! zwS*T#8S`b3b`sW=L^P5+pq5}WRKbT~OZbH0Zm4o!K{fmnRKtJ3p0EMa);1jiWpu-! z+8Ym5eg>50<{5biHrD<xAu@oB3!p~+FwBEL!4RCk$jjdWTaf-8YPU3A>@fq{qz6LT zP8RF|bD#!t32Y13L2cIupxSvAcA$UjGa^!Hn%~Q43DrSg$i}cvh8p=5umijUN<-UW z4%`XH!G77Ek(NSf<|(LhuS40$9;lALg3@Fvl@sR>NhcD7tD!P}hFbf-p_ZU^t~Y>w zP{ue0YQSf}R<IbVqklo!(pspQUJLuct#BaRYtk(Op7D+hp#MQ+gb?`P&2R{O750L) zScl_af2alm@OXF)RKvSqE%>KlTE1rk=}_`UP%~}`rIEf+{SAlhU^E~7tD&_Bv{u`o zX7(JE%HM&q>TjU@!JjY<)-B*#1{**%Gz@Ax&Vyaxxlo$B9rlHfLrv&Q*bz2I-QD4! z1QDtFbT|~oVQ07*YOP;~8Sp!(88uzvu?>{5*-!)QZ{)+F%8xbae5j=eLmBraM*h5E z;uRvA`EFPPegb8!`=N{`rO?wz3z$y2Csc=npvsRl@^P>(>6s=yA4+3+ur`cAmfu<q zHIa`!dBXafh^+lv*cEEeNrl~DH#iZhLIg@>rBE}!(Qp&2Px^7F0lx^<;fF@PA8JW| zgc^9QBJV6{4j<G0pGu?-8R=oKp=QukEtIwQhiY)5$)5>RNiT%bfFEvxc~Apw9`TH= z9h7ZkLY12c>%nQTK0F;Z)c(&W(g2o1sc;o+1lK~Td_9!)ZiHHzN1<l=7L<{G303b9 zR0nm7ab~auTncZ1>}0F?nK&o74313U%?KtmvxYIxW3_~8a5$_9v!FC{8q@%?p)?XV z`D>tN>_C;f4r%}!pxWIGRsJEU0q%m*#Jh$c#n8X@=cfqN!0%9+NQ--y#3oSt-iB&8 z3u^l<f-0W_)nEw9R?dVfw+=Rf8=!Vq5~}>mP`m3bsCvJ}(Z6Q!Hv(0xS>lbn2~>eL zP)pDewt&6hd^iGXAlE~cy9vsOZ-wf3i{W;YzZ+@*??TzmKG+QIPY}@ve}~Os^HS3h zRKp{n<YVA)cp8+3u7O?Q?Iyhws)Ki+I@k-f-#>%$2N@WG20R*SX~sj9OH3!C24_JZ zTntZvmqM-GE~o(<fEw}Nupw-^%u8oL*~Va~^5dZfwiv3T^I;3P#-ukuS^wP-jVG*! zjo?`**Z3Ng%D;nJnm?dskXq*X?ygXpITgyN&VZVEKGf0_oAgVNJBhU$YDor`d*wz! zO=L2xt^L1<h-Q=zwT7io4PFQ}fXkpdS_ic&Dxd~>2W$zSgPQ3FP~|^^D*qMK%zuV* z<@J|)uDCZ;x$&^M_Wv{@Hq3#;;3_B;J!SYZR0r?EL2xe|16!WOdcyfo`8Po=<sDER zY=Jg>3`(OPKuzomlm9(TNboBWHITBxQ*B+?o^(Sf*VzYZcMOM8<s_(%PKVM+0aS-! zsCp%^6}$ps*VbK7He{dewKo{bMkbt%{?$+sg3fR`R0r#!RC_PXgxg^o_#^BB8=yAX z!~oa_W<ixpz#(uGJRW`u<v-f5^xg@l!;YjchZ^90D=E;M$PNVk;g3+Z(DfWoJ_xoY zy%6?*Wl#gHfSSRxCVc=(ll9N_*1QGOMEXE=I1I`rPB-#gs3nOfh-mwW5?l_o_BTKc zWFwS@9)>ce=b$w6zRCX)%4k#1^XfN)gGqOUvXR-aC%hJF#?L_2e;3w(iSLPM?S6)` zfxn<eUUQXqdbNPc9{?pk!El0+p9(e5MNl>pHtBMx_AY{&z~xXAy9sLh-Q(patfz={ zL9hc#g<p9Y*1rw^g3?gU^SuUIK{e17rot1T1~?2##iLAm3RJsUP?}u|HIQ?mKZR`# zGqnGIxBxdwM)M0j>wOg3r1!v16#Na2C*9^E?h4QkPlTJ{N$>!i1-oBtYy{e*D-553 zD!&gNfE_O3g%s9aO*#6v77|gz3D|=UUxp({|9g$s!Qi!?^+qB8vmWH9FC2ZTXXVAP z6X`qQ0JsBc7yJ%o{R1xJ3meRZ$J2iL<(~15x`IBnU-O9!gR5Xe_!QJqyb7hVJy0F& zhuvT;$1}RVP{wpJOoy|f>MeqGVF8r!MUDJ=C{5f7Yr_v6^e^k(kDvzp1*XA2p{(7y zl39V4a1?wJYQU*id79}2u~%yWWFuG~L)k*k)!qP?K-tc6SRbx|-Qjhx8GQO`^xuNW zZUmaaK{yTm3?(1C&f|P2&729d;7TaR^Cnb1eZ^4udawh`fRc}ZHQ^+P@3Ll?^s7)4 z*_|LF)&BscYTvb<N>74PVHWHP&wx_dxll8_7)n!@8(sya!E2$)t%o<l+o3ct<~nbH zbD@?p1m&0#tBGih?uM=5n@~&gHB`mFp*m`Oy=9>kt2s<#-`x#QA-$PiI>YavCeUoX z_ac)4+mK!WPllzi5<Unu&}BEdwvw<`5Ygdq0hFq)hofKxluAD}{2glMjc@iEXa!Zi zy-9b5YNv-upJX@^YAMD+m7fWl!g<i$|4WE8La-WYjc<UO$=y&D9)TM1ldvUx7IuU0 zL3zBC3U6QypvpCeT7ot(6=s<Hu28ns2TH@`&^`YzA<`(tvTlY_`F*!|1)hN#*c(s- z+GqH^;UU8Y8@zh$U|Y&%LK)WtD60=Z*;3T-EGUg!023PV<wUghS3`|_Gn6JCf-3kN zYzJS1>fmdrwXJijX9JyKFVZ<sGg||tsr69XYzyoHUx03XsEO6Njs35gwZ6?8VRys+ zP^ujYwN~Sx267tI{>_DQG?zfl^h#*M`=L5~9m-brL)H5ls{F4;o^rdFu5)|B>!67d zbcO0L6RP1;pfqxZ$qzt{JPbR)v!Tl00Hxy1P~{(iQvK5={}rh6pTK_b5Ud6JCGPMl z3^5!IHS=*$Dx3wi6hSD5v&`h*3T5s0L8*K@tO4JGn(2FnpF(x?HLMMPf*R<buoX<y zy3^~ZBa~n;lnSRpX}}LP<Flb=x)y2;E1*>Sm`OhmHIThfw)7*^Ayt2)=V(rZn%JpO z1IqQ7unLLD2BJ_iS_wPBt6(p<1xf=SK{fCVtOI|C8bI1zUj2qp^7gP7>;^TE=}`3w zpfnnXn&2g_ob|tnNJj*BLsfVY%3Ak9X`uGq-i*6Kt>FmR2c7}d(M7N(OhV1<5yPjT z+Ib#IGY6q2@+Z{psdo<=rGKj%kpMgaror`4e&80UiVs3*W;?70--TV_eyEw$+vMFB zI>8G`FM{gebEs{ba<5me162J1Fa}SC39b44L}WZKz^CCWa0I+;v$y8Epc>i-`@uu7 zKkRp(ckNybN0VL)wREpTwf6&5hrbx6-S630Bf}o|v;TWgU<87hFdx>2k3dy?8cO9a z!$$BR)EfQ)>%yiFcx&GtYUz3#j)o0MFMz672phxYus&Q3wY1khfd19cRs`wrDX1mb z38lhUp&B{>rSe~)8t9Pp@_R!yGzQwx54Gm!!p`t^D4Te}q<2A0=tnpTHc33_RhSLu zB3K6d!&jjST3c98&=IP^v*AHl0ZZYUt=?z&8V`9hoC6Dy=b7~Dkgaa*gRH7`$-~}6 zhd$zICNY+XR5!;k3}ww1LXCV4)PBDbYHc?|Ezzq`OY}CJ2KT`}aNss>O>iNUW^aY6 zcP~`=SD@N|7h+Qh>tiBP>7RymAN4*IwuA$aFNee6CMb;@fYMa$$2_B42vxrvYK<?1 zP2e?99p43;!xtfww?2ZWz-u1Y*YyOSOo+@!;CsUB;9@9$a3^d9pNBo+N3ad7^Q1SR z&ai;=LMZEh9_GM$PkDz_1hyl6HI&94fNkM6r~&VWc7n)WA{j7^y{C@4!R~MxYz9kU z9=r%X2fu@w=|j($`#w|$ze6o$YNe;*b})@}25bSlL6sW@wKSt)LL-?<M5;aAa2Zqw zS3|kfJD^m0AM6XCfeqldCjT#}wXOB6$EHv-?+8_YC{%l+;Yc_ePJ&lIi~gm;PY^`m zr0t$+ABXDjH`o%^W7<l0hP~l&P)o509tX>y26Qi!1|EYp+znOkXQ&A_e%{ll4{9k! zJ&*p|6FCEc22c)F@N1}s>h17U))g)x-4`x|>)=fIGt^8cyx^%k7plW0up?XrHLwlv zN_amkfa6~D%5P2((MYx!?t)E8AA}m%-$veUr)P{qU=8HcU>%qRHQ)ts6fB2>;Nx&G z{2oq$JznzsL>x{gy&Y--i6$?5MwA1OM{pG!3SWlW*L7a;ULxl~?c0lCZFm{13$KCo z;VsaHo1wPbE~tTg1~XurSG}bj2IZ2MLME87E+nEE+zMqR&p=u08!!zXfJ5NdurKVo z%kz*kVL#I6LUr%})QsPRBVhV#Ub#tdJn7}I3#^30;O8z`|1Ph4f<>@58P`K~{2Z(W zUxeC*ufYcJGnfv4gew0xlqPz;;c0LX97(zmO1=q7V-LZ4@JXopJ7FK~|F?+9h*ID5 zjHV-0M<*LjhHBt6C|mKvCh#n%-LV!ngttMJdk`*xk3mhO$8N9VK2Y_Khtk|EnCM01 zTq0Wgd!T0gDLerlgle$sTi%-Wh3e=e*a=RC$}cqeF(~Ul4{Crn!js@;_%Zwns{G4u z8xQz4>)#l`UIf~1KSC9#zsFNubC^oHBb3!=z(-+sD5Kj8TflFj*0$z5p1hag5ZDy? zXsF#Z7q*5ms3lna4*FLE>kw#Wn_x5e7*vD1pqAz*!^ZC#O+Z%@@Cx!L!)5S8m=EW@ zhc|^!!nG-={e5ruZ2Z78*3GaL`L892NaY6%zk?dUFR(ML_n}wO2UYGklO6_}lO73W zD|4X+nhT|wl~Dd+4b)7phMG_XlqR=BEm7h{BC^W&;R5&tYzxPJ<QdOmsDimr11N=q z;0o9V-VYnWov<Z*531uIptkEDP#yOC*qcZuRDXjYOP#Prn2hmIBb^G>@TqV%j6zxQ z^H2@#gqry-sI~kIs{G$jw$u6(ZvZw_y<SlHnNan{!wztP%LFcyh&<ah(1z>b1o$MB zan{+(7J_Y{)_f#XM>Ak5JQa?I^I=c8!N^~ND)%as=H7<V)Q9k7_zl#*Ke77m^Niv= z*opLQP}_1lluEyaU0|(GJ@$rONl$~_U<_(t*Fu%M6Uy<t1l8eNP%7UKWm`YM-munZ z=wC)NfQTBH3f15Om<i8?HQ;?v4Q_!d_as~hUx8Zd%>7zO+%!}NTcKwDBGm4B9oB+h zKn>(uBmZMR)zm<f&%L#83)Mk4sF5EBrI9gE+a(vOfwN$3_%En(mqBUlX4nnBXwu(7 zwb%9w?{eA;jwd|_4uLm*!La2R-bBz5eh*cl@d59K(g|jfUIJ&rC!mb9@t2-$w1Z0b zg<awCP}^|<tPPhKMxoj*hc)3vMt*66h^%-W)C})~QsEA$0lg2U!aty9-sGUC@(!>! z>F!W#J`>&xOQ4Ll>sOxb42CC=o&vRv&xbO;bx`dlb`!CQ95fmAzxGr$2@XYG3Z<(1 z;c@T_=z|@;@y?7XhO6K}<d4Bq;kR%Qobau;E6#%5N#6!l?j^_o64oIi8exm?ytSJK zdy$UAo^ZY4^RNf$?_fjN`g^Zj52%^UgtC=<sQe3|cF)zY5!__*A2;a_U{jgx*F@y; ztRK9?q8;o^(q}jmO2u)gj;?`C;k{6rc^XPHzry)2^GDCoTm-eG>!GZFE0j$<Znzyb zqkrpVBK_beunuhUlQ-hFurcZ0Q1W3=Ga6&$^I>z+Ay^llW8{}W&G-h`9Nq)f@zYSA z?*&*Neh%G#|ND~>)cCj8VH4Pl0-d4!!w{GXXF<(m4pf5+p!`EF><@!59bN}D(A(f? zDcA(mQnvgBHx5TY8F%ep(Z5vP{Z|^L;6&Jm^i98Ugu<OrD(>>T=OIsrQ%Ii$HGmgj z6Zjg`lI(-p6+go_Va6YxWBM8PC0+WbWi5sq;ED8C<1h5DQ*Zd+{ObmIJ=FI28$JS0 zI^+%DXE=^@rj_D$5QoQ+z6lP3Z$d3$os<;!UpAfq&!vOcp$0T3%`3kW&LzDC%1G-c zYNWWTY6mrgfiMG(g=#P#%81s%)^IbFP3$my2TB89!c6!ll&$oxnd0uEK~PI}22}lA zlU@PUUt$#zEx|gdwb=$W({pR3xUb(Yz)__CfH%Y8wNtE1;770?EUc5_?w+MkwsHm3 zfbNAp_!QLf`z@65)vcT28fOoP#uL`@L}VP-K@Fr5s=|I4hE{rtd!LU$8P#Sem;D@+ zpV$Xw?G5UsxURJWyq|OzC>z-gwWJ@w8L(ab6xX;z(Ea|ul873(1?up40IK31C{=z8 z)8GNauc55|d#HLp!)IWv2Hw)V0JUWALbdk;8~|H4^aeZ$YANEdh4%j%Mc@Xgh97|| z;1lqYRE&^64vZzeqe+UpI~q3iIvNUxBcBXa|6(`{-V8Ux_o0?(bu(|RuYny&-wow2 zUWAEaB5xA0;n?Qh8s$M5%_>O$)+MkeTnjb8D`9hZt&!gg8<2hw$~d2aD)$D|O!q(; z=l3SRQwy(L{}w3;SA`=HsKc31sy`LVm=?qS@GPhfH$g4QGf*AugmO5$pqA)8s2P6* z<v$KWEz#(f9<w03(w!`@u1H1yLz&`3NS`4b&~(jn2Jyd$UxEAsf}ZyD@)hz=i9byE zMnayR<g;<yL7#8RdO6-(biHcr4l?=IOQ!XoYD&s#e?qv00-ZG-^SlUuMSdY1WEx`k zS#>Bg*QBMT^~B#Wjpq@cglr$_JB@4}vVnw|$ooS*i3`Y_Na9s5ebnz4q@E&NN|_my z*+6`%smOLd{DhHZ61o#QFsKX2k@u_qY$5L%1a}k8B7U!zpU?+RYcWC3*<|Rs$x!i2 z^}}<2wFWsTtzQY;S={Ga!v`pHrioVvC$m+9HgzVft=9Hnt^b=;8e@c)8}2rp-C}qU z`HzGqCZ8>BEhKbewt6lx<uGvTUCNw|>~vGsEx^wc1ji)SQ?7!rG(n;Hrt*%elCZvM zScl8kl+nJedj3V^I_i8+Xh(RA^kNu<w^R0I;(9(Ov?Z(}Y)5{b+M(SK;q}P$BuYpu zqQX0-(w(Hs2}1~aa*bT^4B~6yd*uB}ypphlcm*=Nuyrdz&uC<0P5c*#A99}z<jYNb z3cSy=f6j3)-x^N=J;h|+Oqfl27wMIRF{EE3{TX4QDfbPsFNxnu{+GlXLwPp&kb#8$ z$aDyGfIks9Qrxqp2$}b={&yjKl$2cPM&f_K$7ysp;Z(vqUP<@&BJxX38t?2rJx#g_ z&mosHt9sU{O;h$7cr{_N$vitjq6djL=&0&>nuy%(tK_XFM2I&eJVelQKV|jrII5n> z#21oCV{Lyl@;WBdq3j$&E5Z=+>caYj=9GO4CT^hMJwzsu`3HQKpc{*xw#d4Ye%mxU z6uv}SPbVYuQb&I8Ab%Hmmzi?PdC|nz5g$hQ+@zn^`kzeV1S&Lu_rSI=N43mTLHZHo z=R-aH2saX*RDvf$o}OhMESs5CJ&#dmJBi<5Gr~;D7Q^|-qA*?ie+h{z2~82aO?ZIN zhEVks5;>0aZPjEWiN9+KG&CLFqI}|CgROLQDe0D`&WXro6S9#vM%E1uAS~1V*Yi3| zG4bc&3c@@JJWk$EZVB%5q=U$>hgHu2Q>PuQN9UVO-bbWI6MvHMk|{jPl%0+2JmQzb zM4ZSs1a8dM^9Y*5+NR*&q<az%Ne(xWcP`-&=>pgqnXb`#J|dh+`ZJTxCjCA^H=%9} ztQBDn@lk~L2zm}`{ZA*dhtQYGFH*S?@!Ay5BK<LOJuQeoO3?Eep*#5lO-GX5NZuyG zdBpD_tRmja<j3IOD#UZEX`>Iay`(#9{gVjO2&W-Dg)pCZ6Suhc^A$2ZnS?HMT;`@Q zRN^<1cezOqqn@6}OnEQGYaQ~Zk@Y4lH^Qjla;?8!2=vq<emjLm5+4b#BD_YZOGi(d z#yi5Mq_>&$2XF@EUr_@*od^#SZYPYO?o!x{^8HNtf0NdeMczD^xR#$s2+tCZC+s9t zf36@Ar?8&Mrjbc-93hub4|zAjHH6y;wdia#ta>gX5+<BOc$u)1ww^Za)aU-`-e{4# zcXaV*LW}~#Os6Nq`%HWt@{>&EvyhD=q|o6gWUYyZsM{U3Cp`=5$soTAjFM)x-DfKC ztBAeD{nLsY;d=;snZ_mhfS~^>TF(LUKJlVfDrNSPo?yxzM|?kdT`7B|k=;pp2w@d@ zwFr9Z69y12CVi8U>Ayd*7Er0L5s<X}q-~RKVg?}j2>tN%CVxEnKf`~Sa?OxGLi%^Y zVuGF!Ou}bOTC!P$u&Gmnc;b7Lxd36R>FjT0Pm!KTf!4@UO$CzH>x4t(Pa*6ub)SN5 zDE9*89wgp{uu+A1xN#qTHX=_~AJ9Ar>sk`8Q|JJaJa`Ua5%D$1OR9=h<Ap~mTR%{C zI(auE`;_=D_%`Y7#2+^0J|R9FSu@xf)<m8{+<T;d5{0I40d!2}gOU>-CX7Wsfua#O zjr0koy5J+N7t#dO^9yBeApAhka~JGP9`7PnE5aRwKM4~FjT5M&0pZ^yVqSTx9`O|f zKS9skG%$^@-=yCpehs1ODJOEZNpFBFDYt|$iSQBO3qsZN7Ljbqlp$+LJkf!lR}oYa zhF2@tfppdLHS$+T--X~$!e&AbWTz9x5-+b-?+oIXQ1%JJ3xwZI-dU7uK$uJVbHc}j zbnVE&ra%j{P(nP9LVA89ejg1K!Ed3SlZbyu{59e$jZE_0`r+A+Y#j8vDYQy@4DpZQ zN$?B!5aD9t=M%QcBkV*_N~n5T6PZqWD<OvtkB9pRjTqp$P){*oDB%>7t^;e3{~_gl z#4jT+i;zQnFdR?#nY;@~x1?N6!fm9ll+ox6D1dt2hrQq{CN0@T<bMdyLv{viL|V@~ z@Ko3dwkPjh!V1#A5Uw(1))3!g;zZq#R^lPTR)Xx`T2E+0<u~9t6y8KUjc_q>25Rji z=vhEWB40|V{w$*0iG;t7k*iEsLhu;*9j5Zjsr28N%pHWA5C&*unJFBACy;&s`5Yrt zy?(^6C3GYdk^dXA)i48=B72sg=Oy?j^2cEf!l{HO2#*r<bR(~i&i^$CzDIDiE5>_~ zK8?&<NH-zAm-uw}E#XUKYvDFRCSfLdJqY>4FEaIW;lGgGjchgXQ81tMy~MvG-WT3X z_!QY`(tjg_4M}`Q{5ryO#AhJWvjMha4K9O~q{*^uel8^4l=yDq_Y$TM*Ru!tUn<1& z9ASispNV`k@viV7AwXUk=?2}2yl)Df;<PyFZ(nhlJv$f=1?-7qtnQ;DN0LSU{J>F? zY5t;s)9B=uHSCcyCOX|tzNpdYXuuy2*s+30G@eruPu_F#^C`{zCE=U`ducFUV2>X& z%8tcLvYp})GbfK5t$J4XY18ac)3dVN$ha9Urn#Iwa=ce!dT}6Zj}8=-*m;pqZXjxP zcO_G&j5Y%2xe?<U&zdu4hTStS618b@+>BmM-BV^XEsB(CJbr4~IsTl2K+KtQ$~n%# zQ>N9m8EkI0Kc_I+f8^ZM+Wlrsn>ePI<@CR%g>&QRJCj*s+Nac5T2hdmEF3#8#rbsH zhPLU`B5^wyj>Y|<P$1VD<&OpIe3URWJy|h+cuI{)S<|OE`z8cu`HSNv(O@{=j`@p< zLxGsj=d(ut7opvA`Op=;(q{!?C82l>9fd-6cEDa54aVb4IUcb?k$hi#Sv=jDG|}%g zn{-Md#~<zycS{76zkEf8<!UB35D(<U1G#p*z#r%5=vkwuD8gXO9j^@|CGlL=D%Y1j zJsc`KT19q#ARLG)*IPEXg8{yQgMIx6+BD?PmR9{y`Y0(bX06ps`n&)d&50Bh1;V*@ zZXgthO9Sx&=f0UOyZK{wkYQ_rVa+O>&w?=3k{F$2m$|F!bEZzZyH-iD#^wAl=};m+ z5OZguE{K?URT+`j8V&Tdmlgyuv9d@BYaCXaYBGSu(Fv;+bw}dP!Zkx_Co?k;_GgCz znfbZdGD$m991ljqnx5(^uZ70Xs8xJSAiIRg__KY^SCiYfkUB#QGZwc?1JM{2Sz_n! z$z2l#kqF)A24aQrNHOiiqh(At$FIh%(Oz!V{-~A_^2cJq9BXta;E(bf%qg_93WLRV zc1b+WsMH@hEL8Nct+1a$k)_PgpOaG(_2-nS-ppevVj_9La4=S28l|n#k#HDO@vO@) zg9+H7ATx>N*}0LW;ZVe%E6vz(xq+DDo3c2e!hSoaBo>bp+0z2?rI9ED3`9$VIRU$8 zP9&Tc%rA+ul%+wx9rp4U#k~BOZ&^{OmsPD>^*AGn!G?1q$F_ECxkz4KFsFUcAF`(f z!~eC0bHS8T(={WOi&aRzJ0&wUftAHsg`8+vF*9}jS#}`DFVk>SzId=u+n^|j-l<2o zC86B^tmU((`U_>}7`tnMg@Lk|cBIXu<9?ihtG}E`6x*lAU@XU`NTVYg&h74s>HB<v zWdVGmY%CkkQ&=2fK9=#AZ1a3s+mRgSz3Gc;&}_gtamFa8-;6^}kC_?GkMTfbri>n% z?kt!&H@#ZLyJnu;nURHJL#;6ZEs!-P7;_gimJ<!&R$_i^HhFO7@U(VC#jIH*914a5 z*a@rcY9KFC63$I_pVKd;<v9F6$R3ZAn;OXtSYzEilj|&=`>k`vygOP3!<wz95Voh= z&hiz>zvfk@wxDJ8WcEPM-oEl+aWChQ(=YDgFD}NCi~Vu=V^>r8QGYRe7#a54WBH06 z&I$8-$Z~TryI4t(jfX*w3Y7%nkqCw#_2&j7K4<p)ag$}P|GO<~9NJ(s*l@8w81;Ob zY|x#jyO7Zc9zE);op1N3RyrDRj}H|+>IfN{zI=uA()_m0&+|tuP_ynlkKXf0m#?VS zmRscT#{Z98Ha8wj+gx_#4JYPv7A(kVc|_o|XE1)x%$<i9+*P0BG)ulq4$@hC#)dSG zx#XcU+N9L71JP(Cnyj~IYDzPk)x=4~{o!0|;tXpd4JJ=pysl<b<CUfu;|qp;&aZ*l z&V;;KV}d!+NO3^~v*<Z?S#cm5#H(}c^|Gc0gLW8mAmZ*EJ12m9Wsl+wZF-PBc&0Se zN#@-;Zcdh^dxABM8OmKw3k35EvLjIhM|qLMY2!6WFN<4Md)_I|k2+uGUr{i7hTBQj z*s(KaO`mEP(<TFxo7A~deJq|i;~&jBThnLH@MUAL=-5<ulSiC@H9ap+L00UXUC<_x z$IV3>)*lMS%e1#;g}FzzHjbB_61O-(wSkI5{xbO#xt>@cZkH7M>~R=YWU1>T@=Job z983W>9pQ|nl+Gq@64e`s2F@%AVtIjZO59mn(AwEr@MaIJ)^E>4^$cE^Q_W1QYDRHk za6ljVIF3S2zOq<6P}Cz<ZQm66!`$Mc?j?rXkF#<~S;BRy7XG*tO?vLkt7+#&@%DaH zP|ES;p5<(WYCc4x$;XXxFtP_ifk3fNVm4~DBpjC5?_M{2ZsWng(yFu4OGip#M1oFg zVf*%4ER>`h8L|jTuRjT#wuP_sJ9-0Jv;JXcx@)t1#fao5g`;Yu2E$JG;;GKC;;pqp z?&au;i@S`-;%4Grl+5Z?tr$HPdQQU61#;pTO-@%rD~pAy=3c)kKG?v$NOBa*J>)vO z&YV#r$fk1IL<iLI&YFp1lB1*dq@>$iTDd1+L(Z46Gcs~=0<l<LNyznnEV6g_R@>mt znD|`@_aft7*w`>tdJl#(b6KHKq*yx<gAa0CablZmanvsI=S<J?rQ7MVaaV4_bqksv z*MlgP)GOy02vpnB?ta2CRULoXiGYkNhOugZn5bMY%FtS{wTZFwO9GjB947Ai%I<wO z-LlnmDfEY85xX$VRl+N&i!DAoH-MMVrAgcgtze4W9db)p_lUbcWVcLc>cU6}lbl=V z4r~^;sd(J)b~|#`R8HfZn9t^_$r-|l8<f{^jh)jZ7*LCz1uhNP!J^`*?y1LUDEURa zZ))O*N@D+D4}QCpeUFZ#46^z;Y3ydGB}L~3^8DQ0ZM};ygaWBM7^Vmd9Yk@y^f3{4 zJ(v1fHEk2OyQXZ7++bcFDnqP_Bb-J{-)Zf7?Vhn-c7aaLY@OELVZ3K)gPLuRI)rT3 zwHX^sestEvl;n)F`=vT-R_=99JEvaa@WG5h$OgC=7oap+3>vG+jhOS^?MyEn_^?3K zy%KRtVX#5`ftE2BTg{0_qGf&EmBJIcmnT`W^h1NpE|^&wjF!agSR^mL)ZZLKpxbQj zzJ;=3t%5I^d(Q1Cj(y&;R>xdq=D3%B?lPOt`>cn1=a|T4k4r7SCvf=sp^gr}e)*hB zSItgnfvR6$WPiqkx(9yMh1L#a6>uMScP9okj!_qJxuQAO6tzS9;>)URair(jb>XLo zJL&Q6J5h*RZ(N%)=CkJdL(aZcJJRM%80Bm{e^b3_bFy5w%#u3OFF3hXL8K^v<5<dO z4d=3lxB|KFldCRRk(TUv@ee6Z$JPDDgmU^1a4o<Jv%jNOBwk!~E(OD`2_6=i%c@4? z8uj62aqe6_cC>4GUWFL07~k!-^N*D*HvLlL&n?g6J14Bk@3wq}jW>u|hA~Bog5i>Q z;7BaytY6cw!Eu@W2WJi(WcNRD=)mKhch~GnJI;4pa?{!_DKol{uH01QFOP&HvC2*H z%A{RXSrLnb`0?`}o@^?zyBAh&EUw%b4ix@p7K%7F$9eVAj$MMdmO_7sc4>zR;!lbq ziU;kyK&bLDUOAnwFJ0YchChmPi3JO>;!x$rcr;Mp7V{^UUN$AQ#l&!~##9k=zenf- zmCZ^%eMQN<|Km>*uKK$dv7h{QQ6N8D7F2tFOK#vl#m+Eiev=vA_larxHj!BnDDUN5 z>4e%<o)Zn1Rc<UqHOb8UvPjfZ7p+B|Z=4;@`&Sk;_Or2Kv2uSTRO*k>O>)>(dsADu z-z(IRUFa{)LARy;UOuPqH6JB=U%T9Vxwz%JAt}yp*MFXp?tF4Xhm`c>p&J&YCZ_V@ zQCU%ld&w``e*cnq<wiU&UslTeMgDlUKfHJ4?cDDEf6JJU7bDBLYX)+QSg|s^n)zU1 z=T%lL31yQG#LDf;P0><6p9n1J%~^EQRHyIFi=2DccTSA7msD0Tf<l`a|C1*Cb|B<0 z#m)NCM|zgO1TPv61@eQsQj`ZPlS_Czjrln<%5CpMh0UyNKW3rrT2)F0f!)Q*v6Z-u zx)as(?JvZ(?7~Qqj*@I{ATdTflHu`29FMDIGYVC#R?m)CZjEAQ`Vdisy9&vmqr3$d z;c#Q+;mV2-pDrqsKFbUqwR@Mi%z$&l&08BwDV(OPvCMK~EIG5HdkUJ-hYt68PYc|= z54XmFp${V1oj+Gr=d;I_$L-2(G#lW%N^a%GP+6e7fcNX%zHDX0ephSm9QLkUf9$7^ zy(@3bEN08Fg!o)82soJB#f&9F{Zc>mvjfJ)B6=U25X|PCn47m$oQEnaw}ySz=$M}q zg4q^YV|kehZ@)ZRP5~KH<#kJ{R)AB<s~B+B-qJfUGFI-+j7y2x@l})JR(oU<7yOs} z(dLuM<Y47vQBQH!WWRhfhnC--94uhu+T{6>qU@+2QDG$N5B5cQyvf8voQHu#mEp<@ z1@c|1@|vpL$j)NHvIBX6B>_#u+iCyUK4mer%B>;Z7USD5w{viEw5ks)l@;<)l@&N4 z_UhplP?+=kD~(e!oSnCvmC#5r473om3s~^VWI1MN$I2t2e0TljVAMj-|Nem`(`>k~ zCL(*Qh(^j?yW`UfOB3=Jx;_v+9Q)}dlTIcb-OI663iJx#4@F}C@fAjukFM*SyrHz+ zQC%MGTapiK*qB;v<9p5xHG7T-x6K=+_O}m9WA~Mn^Qp#v<g<<TQI*s9A9bsYFj#(M z7aaLk(an}kasLVNrd3{862gPBHXQg-=E(_D?#~QlBu~G+eafhDe1nrhIcDTJ_`G%W zslp3xvDWLKOE~NAu$`;!7&~ODKR@giKDq^_=bk8)Tk-RONRfL^Vy?b)?d%*U<<54F zeP@g0J9jiqarWKWlj~EE1J0J3iZR$yzVW!9f;w+3Zjxojc-SpDmv0<9Xci`1j)eva zhgy>r*S_6Ys5p1NaM||DIdgL{T*?GPnyo+i$;LO*T6s&r30`&2l4Fk)CLh@3Pw8{? z=b`HEhc5KF=&(B~-H^FC#Tj$o1FhMJRiBKOukdkQj8;}~>iCl1-FI_Z245R+ZnF5Q zMb`Tct?ByYenGnQ!3PKTEbnFG$(FCcciCJ6@`8ojJXu5jNKyTgz{jJ*=9J-#+~Vs$ z4Y$k+(}BA=4m(vZb@&TYCWFf5T=6;KEwdB<v%73>+iSwrd$TwgrRPxK$W%(@NunWi zjg#f7hE|KUQC-K61JKqDR&LW@17ziS3_u5?vvW)L32IN{&dv`0&$F_SV+#G1a-(oB zIn`C*ez@wmwW|}^I^3NUS38a<^bzC=df4CU%;PLQ>grT&Y79*Z`G2iz*44S-rIz)o zP1yNhYvlMz-eusho~krh8sOVkbxj@ZQk=;TebV6Yap`^y>+|rdX{>efw}($lX`>U1 zJ$8(t<oiR(ncJ47G^xA@hr?f7irmj*ToRHyAHBI&jR|-NXUQ|yT~S#!IatV732qNP z3oAEq?nSjr%*QZp?Ur#=tS4XDSU~MbxvmJyhv~Zh#dXk;!lBM(l@+OTvYdX;wi`X+ zzkB+m^$!^pF>7{&mb0^ud>Whi?4`9m@9%TIdG^KxcEYDFw_m<<P1R>Izw7NdTQEA_ zbNCFU+ljXg;w9n2{}{Dj_UE?azC*Z|0RK^+&9GC?HT$f|!K$xj{NI7boy)g3PmC?l zamj1DELf)&pM!GvQpbr{?TW&yoxl9BUtt_MD(?1doK2OVcdK(4qb`{>mq~ka<<`<b zjQdEnZLuw0R3cY>e3HA)k>cLTnV}gS({7;}!Hc@L*Fey@b$dqgz3n4Y#_1zlKl7>0 z4C07UR^PLJ>wnp0yXS{wPB521U7dK{&&9**!^#m?Qy3@@;1iZ`{mO7o+HrQG>VU=t zR8??qdxg61h045l8CugT)KR`Wz(*|eZljHql#|i*Pbw<KIHf*+afD00HaD+dF|I11 zzP7)JcP0M9L|G==!M%^?m0$ytk63%v4bFLFM|Z9e`bNhgQV}xW>v%JZ96lo}k{|47 zo09k+FE__JDQi@)JRXVZQWmoPh5jfv&BI>I&z;C`Tn<j)pI#G~@(A4x%)5b|OnvdY zn#paiHb_Z~J#38bB-y}v!_@g-b*jnotZKzPi_?cd_Z`dCqdo^7{l?|9s!rRZoTW8Y zm&yDx-5a@qmnYxa)hMOIG4FTPRZ!}8_Pn;R#zcIN)BE-54UT$yP8PmCB-P2hua#5r zX1K$DxX8oS$gXf2?%ta)mwx_y7_CfZvqbK#-MwIN%L?&A=ANg1K1_1MbUzhlR=q}{ z!;Exp9atA`CKQiw3q7LIKy>@%>|zY>uoL71p-#$@B93s^zU+i1E&n2G^Io^8!ZF0h zlpw}d5M)g5{o8D;D7$%LtcY8mE;LJcuh<lKHzeQ6xeTkgPQg$}hf<zCnDHerli?Rv zZnN{X2MXN|rg|R^r+YrvZ&!Ugta=OM2FnW?lXjg4#=AAVr1CM{E&q#J;=EPh7XtB& z<i6b(r#g3j)BCic-Wg248+AkF;>f9C>z$vk4;!P*o1=MKTr1Ab$U~%;F#d=d_N8ZO z*T=)Wo4H##7n9a@tK7E4A0Od__S{j6LyCzeo4j*d&EEfqJBJldZu#)Ulw`Ay_oOEO z-ixIlzFQq-kkwCbcXbQ3-*rR86vB*BgJNlPO3F~AX>SMc>h9iamqhX-?!Qu~tjJfX zm|v@h^%P@&L|Oj6R2i#lskFi01lX#&WP3hHlP#sOl30*FlcpcLX#4&6U|v}{RCyz( z$w+94KUw?JgDFnx{=4{av1z|PT<qLGEM>&V!xvuZ{U1LQX3AC~yoB>XqT1T~oSI*h z=yY^H1!jBRQCBeY-o_YoX#E%8;N5uL%YPx`<ew!33i;se9mGNN4Y9I9r<W|ld&lMP zL~>;`=)S!(8s46=2UZ<nD3!|rci3?OZld02g32TxvgC;LM$W%<(RB~|EBm5DN{xB? z%6Qd*J@xs@nCZQxM2eh(FUR!JUOW7L*01WLqjw{yx)N7?dc5t+v>M4bzBwh;Y4!aH z>LV!>XpMSBWs*({&RpNw}<33&_GsQHM`^_rUhPQ2LyQzn`(-|GGY3zz=_%^jSxa iFxxx2^%bq^&~q04P^N2UWism5x5p}S=;bKw^8W)?gA33A delta 18303 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 Br<(u( diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo index 9f20110f9de6a6fa23ec74fce6806faf1d7c695b..63d82220c256cda72cbc9cf550ff5f36d8da6bb4 100644 GIT binary patch delta 17509 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 delta 16937 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 diff --git a/bin/resources/ru/cemu.mo b/bin/resources/ru/cemu.mo index f89098fdfc7ffa83f01393cda3ecb5ffe9f4ea3f..619ba6783440be10ac2b2c0c77cda8523926e8da 100644 GIT binary patch literal 89284 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 N&hx9^FD!o_{Sz8)Lk0i< literal 46500 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 diff --git a/bin/resources/zh/cemu.mo b/bin/resources/zh/cemu.mo index f9825b24febbfff53509fcd05588d31d42fd36e4..69e06e572cbc240cd3fa396bb0fb323e9dfb62cd 100644 GIT binary patch delta 18614 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 delta 17359 zcmY-02YeMpyT|cOLJPfzgmyx2p%<wVdJVlO9TcT1f{GxXfIt$8NjN|#L24inLJK97 zfHbL!6+vtW=OnSAVi)E9{%0m$-u=9j?>x`W&dkovZb0t6em-d1i6HkErSq+FINl9( zoJzQ?yyIL5a-0VZly#g6?Hs2VhGJ2yjYY6I24hESFY93IXjHpru_DgG5}1I+aT6AH z9JjOAo;ZdY;4<dJPp~w8hK2ARmcl<UKNfE9IOVV;*1}p?68mCV9BrM6HHc$T1E*sF zJb{H7-#JUAAc=QTcXS;!&{wE|ezx%+s0#~sFcT|<Wr%Aacj7#TnrLU#b$w9%4Yct{ z)Hvf&D>E4jF~0K>6)lY$HE=wt;bznw?L@82G1MJjK;6mbr~$r14fq>sA`h$uA2<D$ z#+u}-p(fY^b=?4TYoHNS^o%Fi1~ae%@f;g_P!mW-|AbJF=osqyQ>YubfLi)btlyyq z4CrW9sxa!#tDqijy^gHEX55~HCeQ=5q(f01Pev{I3#g7>LCt&tYQSX}g2~9fbq=E1 zT|rIcBh-MosQj;}{{FRb{z$i3vf`1ZqfpcUO;C5-7PZtpP!|qE4Ky0{s9waX7=yZ_ z9X5XqHG!+BmAs8=_ZRAh!#bH8tm&qrXBUB$u^Fnt0Mx)EQ7bY9we)H~027e6)cG2< z5<b**zoAwxptHFy7^@IhMqS?y+hAXuhwfM^p;QWVF$20#kD?I<VS5`#;$Y%_s5{+; z8t4ed;3-tc-Mg9zEkLzffy~KCL7F%@$Raoq-IxsHJI_(kQtifYJdOGB4(d_lVm`c& z>hKTLqbbteENKO7LHro1-E`D-Gf_)F4|PMUu^?_k?S<WbdH#o}Xo*i+KR{jJLv5a4 zPy;$W%n}zvy@nM~=Nn=HY=c^{NYq5Tq9!sNE8%z>yHSrM4nrB=Nu{D^a|X4!K1A*6 zZ%`cv_B0&^quNzM?Ue{@iM>%PxB#EQB#gp`I2ogQIZkUlibL=|24m0Otp8dngQyh3 z@2$U~R^%b-QIzRpmM+v9j>^}yHbQ;lTiEjhQ2jlD+MMIDD87IN(T(aqrVr2maVqQW ziCb8h_>OJxgEg?Pc_igAiSsqEF`hu0INu{r$!WuF>zOY=J%aV9mDz*!@GNTJACQlv z6WEXS?@uMNpIOossP}pwYNlsU6S|Cg6dz+%{0v?A05wo$I!VXISPZ|ww)h>YzdF3L zTCp~$J<<_%T^BbM4Kxh3WMfbbpGED3>DEQ4cFCxTY{NR3hT8QXVG+D(&wEk*eTOCS zS8Rd>2AYXIh7*b1v#3m^atqa<#~`!2=b$c3ux>^d@c}G>A7X91gBrL9ABQjuLGAXY zsJEmaYO_9#rSWx}Uybzdc6L+Ye@+%Z%3^7@s8*mhPQVE4hKb0Ac5YxPtTxoNYmQ}z z+n@%B!udE3C*d8`Bk4QLtl$XL2X6wFXMATK6%D)+wRv`;Iy!?P_%Uii-=p@zeXNiF zVpFU)+{B|$n=B5S;X!POIoJ|Ip5TqaC~S_iu_@y_=~P<bSE!CFJZYYB7i(|SGahK; zVOWlMG-@Sgp$1%xy8amIk@!$I@-ylN^N%nq5r##H>!4e^u?3Zi*cz+j5G;?cAm3;w z0X6e?unqo>y3?j3%|u68pF-`8DX1l%Yx8laPkVxm4`5s3qa#^=?dD(Yi3g~e2aPg! zTmr+0%c7ofBh(#rMm>t5sFfLK<B1qdJj2FwQFpo+_4au1DO_)3XEf`tU0!Ik`9oqT zY6UuAO&pJ!U@X?cc+?#pw!V!T;5ur8cTfZUZu1XOD^YX|zo0M-Pv9Wb>siu0)^rex z>bMqGz?P_v`e7gr!*qNSHK9V|%(E|n6^X-9E72acf}Kzki^AeK0yUAzs1=%x8qd9e zik5N(YBOy>E%{#59bHCko-a@r-bW4a4=%>w@s9H{CZJx|Li`wnQ!z5Yao$4RNZ`}v zdr=tGUsKGl_rE(8Ek$3{1cstk;91O%^HFykgKGB%Y63~9j<=%Pr=ccz4z&{RT0ca+ zEjLgr@&jr`{>G+y{|iqv9kxde&=b|cK&*+w?D=`9C5uB%bS<iV8mj$4EPzK*6MPFb z!K<kAAE8$ICTc==aRlQ#f7u2@pD~+p1nPpRsAoR|^(baxD8{0ea0{xVBdGT8U?uzj zJK#OkGjI5;+1%|=@i^4PpF_6>m`x=J=b~aas^J3EKnbWjPeD!O5NZi8+4u%(MZQBl zihr>NhD<Um&<-`xE;fD=^(e+qV*RyL&)LLWEI=HK;TVta;eON|E|_edaSRqBPDH*% z&N>_ahMM3*EQn>Mn06IW{no%>tdIGz#T3@R3YE4b^u>D;b>R%u1+Snkh(_JP60D4y zP)mIh)$SJRi}p3@&Z<l`)<^aC7&gO>*ac@{XFTe*4gNtbdH!jp!!qb14#UFO7Inwn z?D>8+9*P=Z3~Gg*K}~Eb>Ps1iMKBrl2ve~bW};TW{WcZd!By0SA7Tjph%aKH>1GeS zhU(CRdbZoK7+%3@coQ|i1Jp_ueU4w<SRJ)#2Vixag8oN~_4WSmr=o_Rqkan(d0y`& z%Y%{l66z6Uq9*nU*28<K4^`L<^I>U$n#d^B1fy-7f?BzYSOVWi^>-VC_5M3Am=lGu z6k!-@$s1r%Y=yehE~q^)5Os&oVsUh1Nqhr!-9~JIyHL;iE3AVxXPO&`LR~)*^J)Bf zRP+cIpq^zMYNm;(Z~JC@KFj9MSwFJ*yQpXUEowr)qxvZ@%S^mD>P9MI8LW#s-yYps znyxkxWgUuIiP5MFpGRFd5A}UWL``@l>g`x>;}q00-i=y`_fRW$3-!!%aXS`aV`;A) zc#-vYQMpE<Y5<!KTN9V$H%dF~g#$4LUHGZ>Z&bTVv*{Fv;!y1PifNaGeTg%%HvJZu z<M{uc(QvNmcREILK5Z`R{|1%sNi@J$Up1R>CpICzjV-avJhLf#VG8j)tc}%Qqdf!k zMs2#+>8}!|Vp}|qdL#kSW)qe~tw<<V#oBHvHK}w*ZK7$Y2Ctzu$uiVZZbdz+J*Xu= zg4$H4QTcCBD{>$8Y$N8I{#sx@;%*p(y|Enj!%paanu>qM*p9?`d>sQ9upx0VKXgGb zNj>wbsEO7>?U7cfJ<=0v;ZXGN71VW!s2kXUQFy@S0~Y$vyPXg!TA~OXgzZolB$x)y zdaO*m-{!Aje&SE9U!iWmhg$NWMP>ybN3CFA)QUZYHE=p=g*;e5@Bex#K_s?Vx7!B0 za0B@Rs5|Mu*gUg|sHI<w`faxtwIUZ$D{>Rn-?z9L?<o$XeGD6vcuAc3P+h@djPLwF zr4~NGHduX$`DBj9_QbRCEFQ*uIA^JO=58!Y9E<sJ19rl#s0sV553m4n>1AeOp{RZ; zqgxkNvxz#W4kK*b$=V&YWPPwO4n|!!2K5Pk8nq(Ps1Mgp)FVBL+RPVG?XRN-{tW$} z;AO0T0}{@1^974QHE4|*umiGkPB+vY^h9l<{#ZSLPc3Tc&%R*>{s=YjT`Y?Ctxmj& zi&?`^*VT?^{asWZBcW&a1ZoAQU<r&y?dAk5g4<9N+=ps^$a=wg6V>ip)aLsg>tpc* zGhiE3|Glka-Bh%MFQP6?uqInmtp}}dp*px~<J+ib{XOc=gAz@Dr7?s!0yWXDsQ!mp zC!qRqKW7sQtqIm-)Q2S%OW}U&SyV?KqXxK*HSkXxS6N}&x5QxbJy7ijT1R6E;>lQw z@trwTw0V}9gmVaW!3FDwHvc8+4u8UMbUbFDYS!kcJL-mdL?cjpXskW|ENa4YP_OL@ zETZ>6&7L@d%*eTB<J+hK|Fro+D@_Mss0lW;cChwB-Pur7KNC@VU^eOz$D{h&g__7w zETQ-ReJZ-(Hmc+M)`vD<WR<y~GU|K_RQpIQi-T<bS=5(smdz((b>i(9ikDFnx{I37 z-{{t-wLG6Gy^qaNugxITKx0vRU@BI`7i~TfHGvJ*y{J1ojZffvs4rl>B=cv+hN%7` zQLk}d)Hs8aSbsf>aW*jr>k-G`3_O6kqZ(_>1(B#FjzV=b8cX8SI1pb#b$rR5zmDUG zKgSOE_*yfe*RTw6;#$_fHkA|-jqx39i2q_!j96!0ud%2VcnLM|3e-SrZM+>dv2>fy z!qUW-QT>08x-S2Eb6ruaKpg6(qNQw#dR@9$qfi$NM?K>))>){5<52BaTQ{MWco&A@ zVbt~STW_Eq$(L9j?_e=>|3yU?6iPN1R7dTFdZ^c|Cziu0SP|!A2yQ?Pa0qp$C#=^n zg7|A}f+hJCS_wPhGdKqO;su<k_rKhmX367l1Sc|4cUX2Ke?i1>)IbYS`K735x5nmo z+k6J<eSgd5FIul*DEaF){?Yn77S;RzufM|bZ88H@Kt0Q<*bW<^CNLGX<TFusJP*|_ z0X4BrxCqlxU&Lmc%^y04V@u*SsAql+we&fX@txnOG{AgY%$+qs4crxn;R4hn_|%^N z5jAkWt;Vvb3D!V8qWaeE_WT4)AwSc`q1()a>Y`hlVJsEh*~=J&^HBpVLcRa1Py@Va zO+jtS-PY4Kf8F{y*5LeI8wWGGt}kf~u~tdZ^XEiO5;d_c>NOmP18@cQ$GfQSLW}KY zbB;wVxf?anWvHc1w(%j<CO(h4vAgKPA5fdI#18Wa8|+~HHPgpQ=q-2(wYl7=jy>23 zx1yf857ptH*c*dV&8F>#Er`=lH*^~{!QavUsCJs`%i<jJwNW>?#Z5&Gf58G6u**!S z5Nf~(Ye(xKREHB$1HFX0ZW*c{4~F9=Y=md9F8*w-lxFr?H&os|lS(9&4XBm)67>l3 z?KT~?K*hss9E-ukyHEokv>vyf!OG+>VNuLR_5TYtz?SLe`U%L5xSeTK^nT96Vwi+_ zmMN%#PTKe))+fG=y7OXt%r35uy3;4I87@KH$Vt>@zJr=*HflmYpdRtx=>PkFiM{4d zLs1ReqBdP88+)wlP#tVWO<)h|PS0Tp{MzP!Ms2b}`%Hf|QP(xFwzhV|;(GrFQ7MAs zu{%yjP2@1@!Yr(Xm$451gnD*i`%SwpsC;i!`&n2D=iAtWn(!v{_iOW6=+*_7sYKu> z$Tz~tf50@{iG_*xq1qis4SdnYKVu;ALyW_~40GKwEKTe|eUP@>{2A*d)T4bbgXgb~ zKP4fvt-qj_G~l3VSPIo)Mbutth-I(`>H{_sb!RhC@BJKHfNOC8Ryt(<c03(*qp27g z!0&`ZtiJ~S@v!+j**~ZbT}MoZt*jkT6Y7e(qaijw!#dmQMoo0Fjn|?!^Cr|CAGP_j z*2}s;9lwvo@HVQ0d)ONTGtGrjs7EmzwW}wf-t%WrHxP&FXSI#DquOVnR_3JjJeDH9 zYGd~oR5UX$YS-RFO`zye^VjjJ=wDjYKyy&-V^9-GM73Xq`hupSHrrY2O;o#kSPk!^ zz6a%x`TKD@!>MRV$D;;%4a?#p)P?J8{!JUFpeC{tHSl2@UqZc(*KPh6tUz4wxY31S z#H~>)HyHhY|9{Cgn1}u)L_Lb_s3kmT^QTaEeht;}O=O=sKcgnT{e+oVIx0SfL+~`} zx{xe0;c!&k91H0E??A=BxlngDL?>`O*2GCz8RKm}-Fgt!(Q(urokvY98+E77Nz+e7 zY(`iOwPHi9lhFVEze+_N#9>=ZL%k;1sEL$2WhPb$71za3Y>b*fFPk519fAH2nvJJh zU&N}ke--tJlTY#f>(2L+(4C*QzKa^@mi24YYnh9>1LrL|fikGgmVnA{$0~Rf72mM& zx2Sf7Pn+HELj8_tdfILNd_IgsZ4z#*jytd-Ua@AQmN4jy`NL`}>_|Kj)ourB>CdA2 z`3yB+zO&|h7;3<lHXdQ)m)%r!r^%=pZ$({r7`22~Q61kv-Pup5b|udlt6N*5uIq_v zKgK#6wZe(072b?Ba1S;{_lHz8<9z2$!v?4U+My=a5A}?PqB<Ch>TnvW-8@vg1REz| zIPosjbyrbu({<E!cTw$sK^NmYe^b$Jt@O6p<*iT+hM+naY2%63X{do`p(YfA`t7&U zp3k!RbJq8*U!We<w>B<#LD%#A%Tm#jgyU}PhL!L>YDr68<e%uU7PiFjOUB{Yl{g-? z<R4pqz(&LcFY~Xa*b0YW`737kPsK>$Wmv`?z`s;dX-DGgtLC+<`i}X-WM5oN{&lQ| zh2J$DM__H@QK<YP)RL#-BD{>1vHvw5AN@~9P4MJ<=FbtIpf>Zr=++%Jecvo~57aXn zh#Fu#>cS<cCEse#AI4I|Z=)W;4Gc!F^&VCy{u2ve<qyn%*sOty$Dpp8_yOy$#7ui) z4wfdKkL__K>QQ`v+O79c@4f3o;|45E9QKjvI0Dsib5uUk#)DDMew>Zxp>BN1M?C-H zR5p>&v)zmOviZ=3&c|j&!m$%!6Vw2+P#w-ky_Oqn{)F`s29dvx<?vI~4g83@KK#15 zzM-3nmaZ3S7Y|0Q$UN&pR7Xp#@iy<lIpo)2SuFR7*~Ilwo39^gf^IB>>#ciH{hh^^ zb^ZbsADiUy8)g&L`qXsP12xlWHlBm^h&Q74#AWP*cQ6eb-!c>b40S_yP<Q^l%?EvE z-j>3se1D_cnMg$!yo!2ei%~ONi&}{d)@`VP)9m?ksOv7;^B<rleAC80EJgf1y6_>Y zf7j>6W`248-Kc25Cr|^9Le20g497XBj+5>Aoz{J*>yDr%avVqFd1OJIsr=ZFdPPSE znvMK3xm}c8a&zz%z5gMcTu<T;ijFhH`mftjn0K78_2%R&Q1Xs3)W;L#;GdL{)aO#l z+xC14{NIEjoGVSy@h<95zujr$?r(C=pLmC4A(A8UF^Y~D<PrOiL;M^_{ER&}g0^o^ zABAfv)2WxD@bWmviSv&8R4x!##$%MnX)}emWT3vk9N&=0JC4{2A3bLsr34M9+0LFO zo=N?A+E>PfxQ5&$ENlBXVlBt{OPtd&n%rZkuc3~HxPbHi|Ni?LiJc@0kT{7A@gRkb z>;K1wIO^Y%|A%-4K8}4U@svQ$)g!kLb$m}5N4(g^A5#B>GK+GS(wm~A2Io6cZtMOd zDa#3_<6as}rrrU!6K|(pj(U5_o5VUcQQjubJI2$d<Rh{2-%^T_AB+c;;P{c!ly)n~ zx2J5RxSt@ogGv@9g%WN%dY<|M>a($y&HaKKDR(Jj)FVea^&cn?sPCi{rT*yAl;HVC z6Vx}*r;fvV{-1I|h>4w8PQ*}Fk~?oZ>dv{p?D@hr_c8HR+lfy7OMX6O$s@VfiJ!3T z)kdG`49c^V`ZnJ!fc5XEB#ArN9hXz8Q=dZdP}gVq(W5tm%p>2%CZnn6BNt%@pG&M` zDCHid3-MUawW6L%sY87WKB;zk{)r^?2MHb5?8Uvv4W%A#(*FNbj0?mw$mhcen20)_ z!?CD;h#o`!A-173p{}DP^{teG)b$sdYf5rVrMMIMF@clAC_d_A@mq?1E9#g=aS`|O zN9M<G#4l2Ggjv5dp_9g-gUEeD`~#&m^-|>4qrR@6+4dglG3NfM+2aOQqrq&-t9JHz z=VX3LJm<W&lU#ht)^&aoC4ut00*+Y9Va~tqkIa9}9A@hV*8dp>@)C{2W%gtg7xlLj zsE-ARTjC}>M$yrb5<z*1GML;A$`iDif(>jNxAi=2DpEF3^vIjrzL%)M84{0^xQj1P zhS-x0sOwl|<H;s;7IV!pn>#_h5TzJJ2Y(#!A72qKpq8O_wyjP?Q?Ej4WZS!kKXOtH z`Vv2dKiLM#>8MFug>uS-PB(kqVd9@{t{S-)XrterqbSFz*QV%bjD<NrgZe@0ZK-z( zu;2f#1p0qK($SHkYAb&1!7ViASNDHM5#nH?Hk|KFeZLxWET+_@-js5mypHCSn#6r= zoI;%6)+>@PrSaWVE|Yu=Yv4u7I_m9kn|k4BLD^0G29~ANr#QqFX!kZHiEuda14=4! z-f@+<E=5OqN|3+Cr<^v=sjkh?jNr1pXg`gED2>S%w7G)Bw<z<;)uZTGO8JMJjxfpq ze~s6SYkJu0Uc=MmyVzJw{-zYO@#kEx@%=|}f>qRBwVh72jV2P`ru4V@fj0j>@jTj> zCT>J&LEN7BBBdlnhyGSog3^HVzj8j75@4^_c@DR8lw<~F1<?R3Pnku14-Gd{zN7vo zxxLhvQH~NnddwymKt9bT*U<JATh|}QCUR~l<<TQQaTD5Y3*`Pkv1gtpv6iCaOMH?> z1F7euyg*%lBkD}aJ6<6-jdp>Qoz&0S6Q`+<qg)}MOwsW&aaHOOlupz;ky}m~qV-=! z@<|Nm#9&I^5lp_bjo+fK<D`vG6Q3n6fyePXoQUgeyI-ikMcbv6%hYuwaBd}W0H)b? z?uDGlqU==*j&q#&8t2fkGWBdqH{xN$i|j=oSYIcXMcu{uIJ}AP;Hy}dl6Pz%w}`en zwqOqoHL=^dNabx3@9QkbT-#88YgxsGwTVBV{s(m(VZ?oGn|}DC%@?PhPH94$XK^bf zlXI)>`6P@a_rC`p<(D{7hSHOA)t*hEe!$bDSsC|uY8|-j6lP*oN?FQ2;)UqLY_1BX zKGR-(1$&V{VdHdsk@GqdtY1*SM7<K_3~h%KkHR{Xt<>McdwJhSVy7aBJ(QzdaEkJV zE+&5T7)Nl3d`I%_s2`xNV+5sxzxMw>f;jgp;qSEVf>(&2rfesEnbMlv*ZT8O-Z7t( zUlBZqI(Fk`8YbCTx!)*_RN<&<a3<4cmTglH%TPk?xlC&$Z3fx;HtJ_-^EVdJ$63cm z1UD%YsP`oIGA<>~JKnREOXSvYz8z&XWwXui&TEJs@;aJPPEu-7&e>dB>hDnBWMg-0 zDm&<O2`6+MdSuX|#62lFlmq08VQ-3#>%^sT64%eOohg5w`bJE~huD>NA(TFpk10A< z;4|1Z@A<z(<5ZGyBx_Os5PJ|;r@or{N{Wt|wgc6tbFKoerNmR8!MPUr=n+kDgxoI5 z7xvnH<XTexrR>lj^Ii6$nj}7_G$wwE(ucZ^W?cB1z4$My@)z`j<0pf&3kPxCTbOKf zb25gsA06bm)bYuTZjn<1GR}2673_)c^QR{~D#SA^YH`NDQJeC4QU;yRa1Va2NXCOP zy@NgDCY=tPJu9RA<hY=W2Gc*PR;+16<JJ*PTe_OGY}2?&MzO@<0Rb-0(<@wVSDjjs zbscY(#~ZiYo0;H?Jz_GxBgef<Hu+ZX^KICiy?&2L=5EZ$-IC~v?BCPGcaJY}MGhJ6 zvUz`3zSZg33yyNWQ(dQ4=l(++-_~f~#uVTBgLFo^b6x(KG5T(n=Wb@YZ?!ANr0!-O z_az_8*_)n|8f`M(ZOgr}hh3g9&%P;Sx-iL@c<;VMTaVq8yLOwY-_2Z_la=*oJ$ujQ z?1aVMwW%hVleOEs;D74oeq942``Q-Ek9ZgDV|bS*Vr7^Xr7P1su{!5uqIdmCZ`KxH z{EEBB5C2a-VR`lr-4HEvmhRWJ{(5e7v^VCMsryzQ&Pj^*9zC3$wI_G8F7H;?@vV+A zU8$b2%3Mg@yC~JWKVEG-St~=TQt4jT$&Fr@y)rdt&q;bP8Slv>{(1hmGK5E1a8<jJ zHvhl#<l0(2nzn4Nhs8f!?eQ+zNxL2lv*k@5EcKqu`=rOaD8)X{|Kk5fBjaiJan-@D zh7B8<3w#UXz1wzpV|SWa)4-RMV(Pt^>_(gKP1jjjbnV-`)wem8Yy0G7OjmvCdcs#% zu4zx~+2_q#MMvIKNhaf6yvUor#y&RB*wtkpBj1-hit5&xA5pHTZv7o!oX2}2n!U-z zobJb5{FxkociFp=yzyIIp3hcSC}z&)M#trDKI-y3TpiMfOn+vSxH%_bVRqbFa|dL+ zu}8G!Gc$d$@!r^^oXy%b1Ih1Q$b`&>BFx@#oZV;N49}>fkkO`@Z~hjxt*Ph6EYI1i ziP8S<sfA_;b+sUsuI!o*B2Hh!D*bmGWX#fWkDXn1uXwH``8Opevo}QN#2qj*A@r>| znZ50pH<M?w*!O0_e|c|8lJAYYC#ybFPG!fZ|97ioya{`~OLTkcboX{|yk30OkIv6~ zIrVN1CUfj4Z<A-_nxdl~O_;hb>2U6fjk(bqy~noOjQ^QG`U=vA_ehF&kw&0?H*>jf z<zC;i`Pu6h`qv?6-|K90m*>4TWed<`2-~jMttu6%4&@Fvrer5Cb@lJx*+n~+RQKp{ ztu0^a(evgD^hksLYb*15Oj}!}x@lHx*rX|wT)CSM<!;#G*v(O=)-X2h`ope_w6)<u zCB4U1G7a;g;^XAX_#(MpV1O&5(8liqS8N#@xO$&w{Fcczyj%C>&i6RJ^sV0HRP%AO z-?;3!SYPsPUFu2M+L_^Vwl>RXwJn@ZJj1u2q^t5fCIxunQjceh+8Lzm*tAysOiHU- z(tN}o8T?|}7x|m#?AYpkW2txB{+xp;_fE`rOl(@2y`j-~r|cR3v_IZIiLZM`hm5d* zj7bN3sh)A@sD_)G8QQ?#+Pfz%XX_&KcINe9bN=r%XZ&65$($R+b#+f%^c2pjNv%=V zo?xbWH#6Foo|5q(D<&x8*6BV0Wm#liK=!=8Q{FvU+6|u8XW!+Lg69q~gA?cOsi(Ay zRoc*&&w<&wE^ppvH0u9T$@{#^^abT@v8~L?x-uSKT(4hT>n^wC=X;mK`1#Fc9&|>* hD?b-2o*d_m-}~Pq^yIsl#+ki0U)2iKzm-<-{{T+uaXSD2 From 149fe10a4e0006963956d5dc9d103dc731c1cb6a Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Fri, 24 May 2024 16:48:17 -0700 Subject: [PATCH 114/130] CI+MacOS: Use libusb dylib from vcpkg (#1219) --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b78b1f..d5843c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,7 +100,7 @@ if (MACOS_BUNDLE) COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" - COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + COMMAND bash -c "install_name_tool -change /Users/runner/work/Cemu/Cemu/build/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES From aadd2f4a1af788d208a38a2d23161a4de517083a Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 25 May 2024 01:48:53 +0200 Subject: [PATCH 115/130] Input: Assign profile name correctly on save (#1217) --- src/input/InputManager.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index d928e46..64b238f 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -472,15 +472,12 @@ bool InputManager::save(size_t player_index, std::string_view filename) emulated_controller->type_string() }.c_str()); + if(!is_default_file) + emulated_controller->m_profile_name = std::string{filename}; + if (emulated_controller->has_profile_name()) emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( emulated_controller->get_profile_name().c_str()); - else if (!is_default_file) - { - emulated_controller->m_profile_name = std::string{filename}; - emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( - emulated_controller->get_profile_name().c_str()); - } // custom settings emulated_controller->save(emulated_controller_node); From 1ee9d5c78c1c5216c92764977363fad38b0d4f0b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 27 May 2024 01:24:24 +0200 Subject: [PATCH 116/130] coreinit: Tweak JD2019 workaround to avoid XCX softlock --- src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 8 ++++---- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 6 +++--- src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp | 9 +++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp index 9e5de19..c144c38 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp @@ -310,7 +310,7 @@ namespace coreinit currentThread->mutexQueue.removeMutex(mutex); mutex->owner = nullptr; if (!mutex->threadQueue.isEmpty()) - mutex->threadQueue.wakeupSingleThreadWaitQueue(true); + mutex->threadQueue.wakeupSingleThreadWaitQueue(true, true); } // currentThread->cancelState = currentThread->cancelState & ~0x10000; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index fbf498d..b53d04e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -758,14 +758,14 @@ namespace coreinit } // returns true if thread runs on same core and has higher priority - bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread) + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround) { uint32 coreIndex = OSGetCoreId(); if (!newThread->context.hasCoreAffinitySet(coreIndex)) return false; // special case: if current and new thread are running only on the same core then reschedule even if priority is equal // this resolves a deadlock in Just Dance 2019 where one thread would always reacquire the same mutex within it's timeslice, blocking another thread on the same core from acquiring it - if ((1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) + if (sharedPriorityAndAffinityWorkaround && (1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) return true; // otherwise reschedule if new thread has higher priority return newThread->effectivePriority < currentThread->effectivePriority; @@ -791,7 +791,7 @@ namespace coreinit // todo - only set this once? thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); // reschedule if thread has higher priority - if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, false)) PPCCore_switchToSchedulerWithLock(); } return previousSuspendCount; @@ -948,7 +948,7 @@ namespace coreinit OSThread_t* currentThread = OSGetCurrentThread(); if (currentThread && currentThread != thread) { - if (__OSCoreShouldSwitchToThread(currentThread, thread)) + if (__OSCoreShouldSwitchToThread(currentThread, thread, false)) PPCCore_switchToSchedulerWithLock(); } __OSUnlockScheduler(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index fdbcfea..8b144bd 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -126,8 +126,8 @@ namespace coreinit // counterparts for queueAndWait void cancelWait(OSThread_t* thread); - void wakeupEntireWaitQueue(bool reschedule); - void wakeupSingleThreadWaitQueue(bool reschedule); + void wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); + void wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); private: OSThread_t* takeFirstFromQueue(size_t linkOffset) @@ -611,7 +611,7 @@ namespace coreinit // internal void __OSAddReadyThreadToRunQueue(OSThread_t* thread); - bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread); + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround); void __OSQueueThreadDeallocation(OSThread_t* thread); bool __OSIsThreadActive(OSThread_t* thread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp index 68cb22b..b33aa88 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp @@ -128,7 +128,8 @@ namespace coreinit // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) - void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule) + // sharedPriorityAndAffinityWorkaround is currently a hack/placeholder for some special cases. A proper fix likely involves handling all the nuances of thread effective priority + void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); bool shouldReschedule = false; @@ -139,7 +140,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) @@ -148,7 +149,7 @@ namespace coreinit // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) - void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule) + void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink)); @@ -159,7 +160,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) From da8fd5b7c7ba51816d477e00f22d9a2cc56cb60b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 29 May 2024 00:07:37 +0200 Subject: [PATCH 117/130] nn_save: Refactor and modernize code --- .../Interpreter/PPCInterpreterMain.cpp | 2 +- src/Cafe/HW/Espresso/PPCState.h | 2 +- src/Cafe/OS/libs/nn_save/nn_save.cpp | 1195 +++++------------ src/Common/MemPtr.h | 2 +- src/Common/StackAllocator.h | 9 +- 5 files changed, 323 insertions(+), 887 deletions(-) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp index a9ab49a..ace1601 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -90,7 +90,7 @@ uint8* PPCInterpreterGetStackPointer() return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]); } -uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset) +uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset); diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index 134f73a..c315ed0 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -213,7 +213,7 @@ void PPCTimer_start(); // core info and control extern uint32 ppcThreadQuantum; -uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); uint8* PPCInterpreterGetStackPointer(); void PPCInterpreterModifyStackPointer(sint32 offset); diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 09d4413..31f8ac8 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -160,39 +160,60 @@ namespace save return FS_RESULT::FATAL_ERROR; } - typedef struct + struct AsyncResultData { - coreinit::OSEvent* event; - SAVEStatus returnStatus; + MEMPTR<coreinit::OSEvent> event; + betype<SAVEStatus> returnStatus; + }; - MEMPTR<OSThread_t> thread; // own stuff until cond + event rewritten - } AsyncCallbackParam_t; + void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU); - void AsyncCallback(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 returnStatus, void* p) + struct AsyncToSyncWrapper : public FSAsyncParams { - cemu_assert_debug(p && ((AsyncCallbackParam_t*)p)->event); + AsyncToSyncWrapper() + { + coreinit::OSInitEvent(&_event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + ioMsgQueue = nullptr; + userContext = &_result; + userCallback = RPLLoader_MakePPCCallable(SaveAsyncFinishCallback); + _result.returnStatus = 0; + _result.event = &_event; + } - AsyncCallbackParam_t* param = (AsyncCallbackParam_t*)p; - param->returnStatus = returnStatus; - coreinit::OSSignalEvent(param->event); - } + ~AsyncToSyncWrapper() + { - void AsyncCallback(PPCInterpreter_t* hCPU) + } + + FSAsyncParams* GetAsyncParams() + { + return this; + } + + SAVEStatus GetResult() + { + return _result.returnStatus; + } + + void WaitForEvent() + { + coreinit::OSWaitEvent(&_event); + } + private: + coreinit::OSEvent _event; + AsyncResultData _result; + }; + + void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(client, coreinit::FSClient_t, 0); ppcDefineParamMEMPTR(block, coreinit::FSCmdBlock_t, 1); ppcDefineParamU32(returnStatus, 2); ppcDefineParamMEMPTR(userContext, void, 3); - MEMPTR<AsyncCallbackParam_t> param{ userContext }; - - // wait till thread is actually suspended - OSThread_t* thread = param->thread.GetPtr(); - while (thread->suspendCounter == 0 || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) - coreinit::OSYieldThread(); - - param->returnStatus = returnStatus; - coreinit_resumeThread(param->thread.GetPtr(), 1000); + MEMPTR<AsyncResultData> resultPtr{ userContext }; + resultPtr->returnStatus = returnStatus; + coreinit::OSSignalEvent(resultPtr->event); osLib_returnFromFunction(hCPU, 0); } @@ -320,18 +341,16 @@ namespace save return SAVE_STATUS_OK; } - SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + SAVEStatus SAVEInitSaveDir(uint8 accountSlot) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); + acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); + result = ConvertACPToSaveStatus(status); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -340,27 +359,6 @@ namespace save return result; } - SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -383,6 +381,69 @@ namespace save return result; } + SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -430,25 +491,12 @@ namespace save SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetPointer(); - - SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) @@ -475,27 +523,6 @@ namespace save return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) - result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -517,6 +544,16 @@ namespace save return result; } + SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -538,12 +575,28 @@ namespace save return result; } + SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } + SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); + } + SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); @@ -562,644 +615,6 @@ namespace save return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEInitSaveDir(uint8 accountSlot) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); - result = ConvertACPToSaveStatus(status); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - return result; - } - - SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetFreeSpaceSize(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVEGetFreeSpaceSize(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSize(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetFreeSpaceSizeAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVEGetFreeSpaceSizeAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSizeAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEInit(PPCInterpreter_t* hCPU) - { - const SAVEStatus result = SAVEInit(); - cemuLog_log(LogType::Save, "SAVEInit() -> {:x}", result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVERemoveAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVERemoveAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVERemove(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVERemove(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullOldPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) - { - char fullNewPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParams*)asyncParams); - } - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVERenameAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(oldPath, const char, 3); - ppcDefineParamMEMPTR(newPath, const char, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVERenameAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVERenameAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullOldPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) - { - char fullNewPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRename(client, block, fullOldPath, fullNewPath, errHandling); - } - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVEOpenDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVEOpenDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEOpenDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); - ppcDefineParamU32(errHandling, 5); - - const SAVEStatus result = SAVEOpenDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling); - cemuLog_log(LogType::Save, "SAVEOpenDir(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVEOpenDirOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); - - const SAVEStatus result = SAVEOpenDirOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplicationAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEOpenDirOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenDirOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling); - cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplication(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); - } - - void export_SAVEOpenDirOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); - } - - void export_SAVEOpenDirOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); - } - - void export_SAVEOpenDirOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); - } - - void export_SAVEOpenDirOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEMakeDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVEMakeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEMakeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEMakeDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVEMakeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEMakeDir(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetPointer(); - - SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEInitSaveDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamU8(accountSlot, 0); - const SAVEStatus result = SAVEInitSaveDir(accountSlot); - cemuLog_log(LogType::Save, "SAVEInitSaveDir({:x}) -> {:x}", accountSlot, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetStatAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(stat, FSStat_t, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVEGetStatAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEGetStatAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetStat(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(stat, FSStat_t, 4); - ppcDefineParamU32(errHandling, 5); - - const SAVEStatus result = SAVEGetStat(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEGetStat(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetStatOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetStatOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEGetStatOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - - void export_SAVEGetStatOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(stat, FSStat_t, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); - } - - void export_SAVEGetStatOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(stat, FSStat_t, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEGetStatOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - - - void export_SAVEGetStatOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEGetStatOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { //peterBreak(); @@ -1208,21 +623,140 @@ namespace save return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } - void export_SAVEGetStatOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) + result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; } + SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullOldPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) + { + char fullNewPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) + result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, asyncParams); + } + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVERenameAsync(client, block, accountSlot, oldPath, newPath, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } SAVEStatus SAVEGetSharedDataTitlePath(uint64 titleId, const char* dataFileName, char* output, sint32 outputLength) { @@ -1234,17 +768,6 @@ namespace save return result; } - void export_SAVEGetSharedDataTitlePath(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(titleId, 0); - ppcDefineParamMEMPTR(dataFileName, const char, 2); - ppcDefineParamMEMPTR(output, char, 3); - ppcDefineParamS32(outputLength, 4); - const SAVEStatus result = SAVEGetSharedDataTitlePath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); - cemuLog_log(LogType::Save, "SAVEGetSharedDataTitlePath(0x{:x}, {}, {}, 0x{:x}) -> {:x}", titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEGetSharedSaveDataPath(uint64 titleId, const char* dataFileName, char* output, uint32 outputLength) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1255,16 +778,6 @@ namespace save return result; } - void export_SAVEGetSharedSaveDataPath(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(titleId, 0); - ppcDefineParamMEMPTR(dataFileName, const char, 2); - ppcDefineParamMEMPTR(output, char, 3); - ppcDefineParamU32(outputLength, 4); - const SAVEStatus result = SAVEGetSharedSaveDataPath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1284,52 +797,14 @@ namespace save return result; } - void export_SAVEChangeDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - const SAVEStatus result = SAVEChangeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEChangeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEChangeDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - const SAVEStatus result = SAVEChangeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEChangeDir(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) @@ -1355,71 +830,45 @@ namespace save return result; } - void export_SAVEFlushQuotaAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamU32(errHandling, 3); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 4); - const SAVEStatus result = SAVEFlushQuotaAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEFlushQuotaAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - return status; - } - - void export_SAVEFlushQuota(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamU32(errHandling, 3); - const SAVEStatus result = SAVEFlushQuota(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling); - cemuLog_log(LogType::Save, "SAVEFlushQuota(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } void load() { + cafeExportRegister("nn_save", SAVEInit, LogType::Save); + cafeExportRegister("nn_save", SAVEInitSaveDir, LogType::Save); + cafeExportRegister("nn_save", SAVEGetSharedDataTitlePath, LogType::Save); + cafeExportRegister("nn_save", SAVEGetSharedSaveDataPath, LogType::Save); - osLib_addFunction("nn_save", "SAVEInit", export_SAVEInit); - osLib_addFunction("nn_save", "SAVEInitSaveDir", export_SAVEInitSaveDir); - osLib_addFunction("nn_save", "SAVEGetSharedDataTitlePath", export_SAVEGetSharedDataTitlePath); - osLib_addFunction("nn_save", "SAVEGetSharedSaveDataPath", export_SAVEGetSharedSaveDataPath); + cafeExportRegister("nn_save", SAVEGetFreeSpaceSize, LogType::Save); + cafeExportRegister("nn_save", SAVEGetFreeSpaceSizeAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEMakeDir, LogType::Save); + cafeExportRegister("nn_save", SAVEMakeDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVERemove, LogType::Save); + cafeExportRegister("nn_save", SAVERemoveAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEChangeDir, LogType::Save); + cafeExportRegister("nn_save", SAVEChangeDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVERename, LogType::Save); + cafeExportRegister("nn_save", SAVERenameAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEFlushQuota, LogType::Save); + cafeExportRegister("nn_save", SAVEFlushQuotaAsync, LogType::Save); - // sync functions - osLib_addFunction("nn_save", "SAVEGetFreeSpaceSize", export_SAVEGetFreeSpaceSize); - osLib_addFunction("nn_save", "SAVEMakeDir", export_SAVEMakeDir); - osLib_addFunction("nn_save", "SAVERemove", export_SAVERemove); - osLib_addFunction("nn_save", "SAVEChangeDir", export_SAVEChangeDir); - osLib_addFunction("nn_save", "SAVEFlushQuota", export_SAVEFlushQuota); - - osLib_addFunction("nn_save", "SAVEGetStat", export_SAVEGetStat); - osLib_addFunction("nn_save", "SAVEGetStatOtherApplication", export_SAVEGetStatOtherApplication); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplication", export_SAVEGetStatOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariation", export_SAVEGetStatOtherNormalApplicationVariation); + cafeExportRegister("nn_save", SAVEGetStat, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFile, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileAsync, LogType::Save); @@ -1430,30 +879,14 @@ namespace save cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariation, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariationAsync, LogType::Save); - osLib_addFunction("nn_save", "SAVEOpenDir", export_SAVEOpenDir); - osLib_addFunction("nn_save", "SAVEOpenDirOtherApplication", export_SAVEOpenDirOtherApplication); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplication", export_SAVEOpenDirOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariation", export_SAVEOpenDirOtherNormalApplicationVariation); - - // async functions - osLib_addFunction("nn_save", "SAVEGetFreeSpaceSizeAsync", export_SAVEGetFreeSpaceSizeAsync); - osLib_addFunction("nn_save", "SAVEMakeDirAsync", export_SAVEMakeDirAsync); - osLib_addFunction("nn_save", "SAVERemoveAsync", export_SAVERemoveAsync); - osLib_addFunction("nn_save", "SAVERenameAsync", export_SAVERenameAsync); - cafeExportRegister("nn_save", SAVERename, LogType::Save); - - osLib_addFunction("nn_save", "SAVEChangeDirAsync", export_SAVEChangeDirAsync); - osLib_addFunction("nn_save", "SAVEFlushQuotaAsync", export_SAVEFlushQuotaAsync); - - osLib_addFunction("nn_save", "SAVEGetStatAsync", export_SAVEGetStatAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherApplicationAsync", export_SAVEGetStatOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationAsync", export_SAVEGetStatOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariationAsync", export_SAVEGetStatOtherNormalApplicationVariationAsync); - - osLib_addFunction("nn_save", "SAVEOpenDirAsync", export_SAVEOpenDirAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherApplicationAsync", export_SAVEOpenDirOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariationAsync", export_SAVEOpenDirOtherNormalApplicationVariationAsync); + cafeExportRegister("nn_save", SAVEOpenDir, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariationAsync, LogType::Save); } void ResetToDefaultState() diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index b2362d0..5fb7347 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -11,7 +11,7 @@ using PAddr = uint32; // physical address extern uint8* memory_base; extern uint8* PPCInterpreterGetStackPointer(); -extern uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); extern void PPCInterpreterModifyStackPointer(sint32 offset); class MEMPTRBase {}; diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h index 1dc52d5..856224c 100644 --- a/src/Common/StackAllocator.h +++ b/src/Common/StackAllocator.h @@ -10,14 +10,16 @@ public: explicit StackAllocator(const uint32 items) { + m_items = items; m_modified_size = count * sizeof(T) * items + kStaticMemOffset * 2; - - auto tmp = PPCInterpreterGetStackPointer(); - m_ptr = (T*)(PPCInterpreterGetAndModifyStackPointer(m_modified_size) + kStaticMemOffset); + m_modified_size = (m_modified_size/8+7) * 8; // pad to 8 bytes + m_ptr = new(PPCInterpreter_PushAndReturnStackPointer(m_modified_size) + kStaticMemOffset) T[count * items](); } ~StackAllocator() { + for (size_t i = 0; i < count * m_items; ++i) + m_ptr[i].~T(); PPCInterpreterModifyStackPointer(-m_modified_size); } @@ -64,4 +66,5 @@ private: T* m_ptr; sint32 m_modified_size; + uint32 m_items; }; \ No newline at end of file From f576269ed0e52f5487a1e8ad19a109b5d4214bf0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 29 May 2024 00:34:11 +0200 Subject: [PATCH 118/130] Refactor legacy method of emulating thread events --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 18 --------- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 5 --- src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp | 13 ++++--- src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 38 +++++++++---------- 4 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index b53d04e..2f3808b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1608,21 +1608,3 @@ namespace coreinit } } - -void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count) -{ - // for legacy source - OSThreadBE->suspendCounter += count; -} - -void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count) -{ - __OSLockScheduler(); - coreinit::__OSResumeThreadInternal(OSThreadBE, count); - __OSUnlockScheduler(); -} - -OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU) -{ - return coreinit::__currentCoreThread[PPCInterpreter_getCoreIndex(hCPU)]; -} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index 8b144bd..df787bf 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -621,11 +621,6 @@ namespace coreinit #pragma pack() // deprecated / clean up required -void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count = 1); -void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count = 1); - -OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU); - extern MPTR activeThread[256]; extern sint32 activeThreadCount; diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp index a69f32a..eb0178f 100644 --- a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp @@ -40,7 +40,7 @@ namespace nn static_assert(offsetof(nnIdbeEncryptedIcon_t, iconData) == 0x22, ""); static_assert(sizeof(nnIdbeEncryptedIcon_t) == 0x12082); - void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, OSThread_t* thread) + void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, coreinit::OSEvent* event) { std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(ActiveSettings::GetNetworkService(), titleId); if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) @@ -48,11 +48,11 @@ namespace nn // icon does not exist or has the wrong size cemuLog_log(LogType::Force, "IDBE: Failed to retrieve icon for title {:016x}", titleId); memset(iconOut, 0, sizeof(nnIdbeEncryptedIcon_t)); - coreinit_resumeThread(thread); + coreinit::OSSignalEvent(event); return; } memcpy(iconOut, idbeData.data(), sizeof(nnIdbeEncryptedIcon_t)); - coreinit_resumeThread(thread); + coreinit::OSSignalEvent(event); } void export_DownloadIconFile(PPCInterpreter_t* hCPU) @@ -62,9 +62,10 @@ namespace nn ppcDefineParamU32(uknR7, 4); ppcDefineParamU32(uknR8, 5); - auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, coreinit::OSGetCurrentThread()); - coreinit::OSSuspendThread(coreinit::OSGetCurrentThread()); - PPCCore_switchToScheduler(); + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, &event); + coreinit::OSWaitEvent(&event); osLib_returnFromFunction(hCPU, 1); } diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index ba3e3b9..ff5c4f4 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -429,8 +429,7 @@ namespace nsyshid // handler for synchronous HIDSetReport transfers sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8* reportData, sint32 length, - uint8* originalData, - sint32 originalLength, OSThread_t* osThread) + uint8* originalData, sint32 originalLength, coreinit::OSEvent* event) { _debugPrintHex("_hidSetReportSync Begin", reportData, length); sint32 returnCode = 0; @@ -440,7 +439,7 @@ namespace nsyshid } free(reportData); cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -484,11 +483,12 @@ namespace nsyshid sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { + // synchronous + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportData, - paddedLength + 1, data, dataLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + paddedLength + 1, data, dataLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } else @@ -557,10 +557,10 @@ namespace nsyshid sint32 _hidReadSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, - OSThread_t* osThread) + coreinit::OSEvent* event) { sint32 returnCode = _hidReadInternalSync(device, data, maxLength); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -591,10 +591,10 @@ namespace nsyshid else { // synchronous transfer - std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } @@ -654,10 +654,10 @@ namespace nsyshid sint32 _hidWriteSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, - OSThread_t* osThread) + coreinit::OSEvent* event) { sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -688,10 +688,10 @@ namespace nsyshid else { // synchronous transfer - std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } From d33337d5394754a738989fe4736abc534cefe8cb Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Tue, 28 May 2024 23:36:12 +0100 Subject: [PATCH 119/130] Fix GamePad window size (#1224) --- src/gui/PadViewFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index 744df32..f2da2ca 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -72,7 +72,7 @@ void PadViewFrame::InitializeRenderCanvas() m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); } - SetSizer(sizer); + SetSizerAndFit(sizer); Layout(); m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this); From 5f825a1fa8eacf87b18a5b5666080c7c0dd55926 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 2 Jun 2024 20:29:10 +0200 Subject: [PATCH 120/130] Latte: Always allow views with the same format as base texture Fixes crash/assert in VC N64 titles --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 3754fb1..d885289 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -235,6 +235,9 @@ void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture) // if this function returns false, textures will not be synchronized even if their data overlaps bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) { + if(formatA == formatB) + return true; // if the format is identical then compatibility must be guaranteed (otherwise we can't create the necessary default view of a texture) + // todo - find a better way to handle this for (sint32 swap = 0; swap < 2; swap++) { From 16070458edddb8bd73baf3cbb1678f5a8b2dbc5e Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:39:06 +0200 Subject: [PATCH 121/130] Logging: Restructure menu + allow toggeling APIErrors logtype The logtype "APIErrors" previously was always enabled. This option is intended to help homebrew developers notice mistakes in how they use CafeOS API. But some commercial games trigger these a lot and cause log.txt bloat (e.g. seen in XCX). Thus this commit changes it so that it's off by default and instead can be toggled if desired. Additionally in this commit: - COS module logging options are no longer translatable (our debug logging is fundamentally English) - Restructured the log menu and moved the logging options that are mainly of interest to Cemu devs into a separate submenu --- src/Cafe/OS/libs/gx2/GX2_Command.cpp | 8 ----- src/Cafe/OS/libs/gx2/GX2_Command.h | 4 +-- src/Cafe/OS/libs/gx2/GX2_ContextState.cpp | 3 +- src/Cafe/OS/libs/gx2/GX2_Shader.cpp | 2 +- src/Cemu/Logging/CemuLogging.cpp | 1 + src/Cemu/Logging/CemuLogging.h | 2 +- src/gui/MainWindow.cpp | 44 +++++++++++++---------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp index 804e3da..ec96a4f 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -154,14 +154,6 @@ namespace GX2 return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL; } - bool GX2WriteGather_isDisplayListActive() - { - uint32 coreIndex = coreinit::OSGetCoreId(); - if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) - return true; - return false; - } - uint32 GX2WriteGather_getReadWriteDistance() { uint32 coreIndex = sGX2MainCoreIndex; diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.h b/src/Cafe/OS/libs/gx2/GX2_Command.h index 635680e..51c0492 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.h +++ b/src/Cafe/OS/libs/gx2/GX2_Command.h @@ -84,8 +84,6 @@ inline void gx2WriteGather_submit(Targs... args) namespace GX2 { - - bool GX2WriteGather_isDisplayListActive(); uint32 GX2WriteGather_getReadWriteDistance(); void GX2WriteGather_checkAndInsertWrapAroundMark(); @@ -96,6 +94,8 @@ namespace GX2 void GX2CallDisplayList(MPTR addr, uint32 size); void GX2DirectCallDisplayList(void* addr, uint32 size); + bool GX2GetDisplayListWriteStatus(); + void GX2Init_writeGather(); void GX2CommandInit(); void GX2CommandResetToDefaultState(); diff --git a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp index a4cfc93..cf150b4 100644 --- a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp @@ -291,8 +291,7 @@ void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU) void _GX2ContextCreateRestoreStateDL(GX2ContextState_t* gx2ContextState) { // begin display list - if (GX2::GX2WriteGather_isDisplayListActive()) - assert_dbg(); + cemu_assert_debug(!GX2::GX2GetDisplayListWriteStatus()); // must not already be writing to a display list GX2::GX2BeginDisplayList((void*)gx2ContextState->loadDL_buffer, sizeof(gx2ContextState->loadDL_buffer)); _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); uint32 displayListSize = GX2::GX2EndDisplayList((void*)gx2ContextState->loadDL_buffer); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index d004288..dfbbfcf 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -426,7 +426,7 @@ namespace GX2 } if((aluRegisterOffset+sizeInU32s) > 0x400) { - cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 0x400)", aluRegisterOffset, sizeInU32s); + cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 1024)", aluRegisterOffset, sizeInU32s); } if( (sizeInU32s&3) != 0) { diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index e49ece9..5cde2a7 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -36,6 +36,7 @@ struct _LogContext const std::map<LogType, std::string> g_logging_window_mapping { {LogType::UnsupportedAPI, "Unsupported API calls"}, + {LogType::APIErrors, "Invalid API usage"}, {LogType::CoreinitLogging, "Coreinit Logging"}, {LogType::CoreinitFile, "Coreinit File-Access"}, {LogType::CoreinitThreadSync, "Coreinit Thread-Synchronization"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 5fd652b..a671ce5 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -7,7 +7,7 @@ enum class LogType : sint32 // note: IDs must be in range 1-64 Force = 63, // always enabled Placeholder = 62, // always disabled - APIErrors = Force, // alias for Force. Logs bad parameters or other API usage mistakes or unintended errors in OS libs + APIErrors = 61, // Logs bad parameters or other API usage mistakes or unintended errors in OS libs. Intended for homebrew developers CoreinitFile = 0, GX2 = 1, diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 33e2cdc..03c69a7 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2191,27 +2191,35 @@ void MainWindow::RecreateMenu() m_menuBar->Append(nfcMenu, _("&NFC")); m_nfcMenuSeparator0 = nullptr; // debug->logging submenu - wxMenu* debugLoggingMenu = new wxMenu; + wxMenu* debugLoggingMenu = new wxMenu(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::UnsupportedAPI), _("&Unsupported API calls"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::UnsupportedAPI)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::APIErrors), _("&Invalid API usage"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::APIErrors)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitLogging), _("&Coreinit Logging (OSReport/OSConsole)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitLogging)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("&Coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("&Coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("&Coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("&Coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("&PRUDP (for NN FP)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("&Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("&Socket API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("&Save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("&H264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("&NFC API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("&NTAG API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); + debugLoggingMenu->AppendSeparator(); + + wxMenu* logCosModulesMenu = new wxMenu(); + logCosModulesMenu->AppendCheckItem(0, _("&Options below are for experts. Leave off if unsure"), wxEmptyString)->Enable(false); + logCosModulesMenu->AppendSeparator(); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("nn_save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("nn_nfp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("nn_fp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("nn_fp PRUDP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("nn_boss API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("nfc API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("ntag API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("nsysnet API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("h264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("gx2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); + + debugLoggingMenu->AppendSubMenu(logCosModulesMenu, _("&CafeOS modules logging")); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache)); From 6772b1993ff678050d9a064dbd8c625eede272a1 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:34:42 +0200 Subject: [PATCH 122/130] vcpkg: Update dependencies (#1229) --- dependencies/vcpkg | 2 +- .../sdl2/alsa-dep-fix.patch | 13 -- .../vcpkg_overlay_ports/sdl2/deps.patch | 13 -- .../vcpkg_overlay_ports/sdl2/portfile.cmake | 137 ------------------ dependencies/vcpkg_overlay_ports/sdl2/usage | 8 - .../vcpkg_overlay_ports/sdl2/vcpkg.json | 68 --------- .../vcpkg_overlay_ports/tiff/FindCMath.patch | 13 -- .../vcpkg_overlay_ports/tiff/portfile.cmake | 86 ----------- dependencies/vcpkg_overlay_ports/tiff/usage | 9 -- .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ------------- .../vcpkg_overlay_ports/tiff/vcpkg.json | 67 --------- .../dbus/cmake.dep.patch | 15 -- .../dbus/getpeereid.patch | 26 ---- .../dbus/libsystemd.patch | 15 -- .../dbus/pkgconfig.patch | 21 --- .../dbus/portfile.cmake | 88 ----------- .../vcpkg_overlay_ports_linux/dbus/vcpkg.json | 30 ---- .../sdl2/alsa-dep-fix.patch | 13 -- .../vcpkg_overlay_ports_linux/sdl2/deps.patch | 13 -- .../sdl2/portfile.cmake | 137 ------------------ .../vcpkg_overlay_ports_linux/sdl2/usage | 8 - .../vcpkg_overlay_ports_linux/sdl2/vcpkg.json | 68 --------- .../tiff/FindCMath.patch | 13 -- .../tiff/portfile.cmake | 86 ----------- .../vcpkg_overlay_ports_linux/tiff/usage | 9 -- .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ------------- .../vcpkg_overlay_ports_linux/tiff/vcpkg.json | 67 --------- .../sdl2/alsa-dep-fix.patch | 13 -- .../vcpkg_overlay_ports_mac/sdl2/deps.patch | 13 -- .../sdl2/portfile.cmake | 137 ------------------ .../vcpkg_overlay_ports_mac/sdl2/usage | 8 - .../vcpkg_overlay_ports_mac/sdl2/vcpkg.json | 68 --------- .../tiff/FindCMath.patch | 13 -- .../tiff/portfile.cmake | 86 ----------- .../vcpkg_overlay_ports_mac/tiff/usage | 9 -- .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ------------- .../vcpkg_overlay_ports_mac/tiff/vcpkg.json | 67 --------- vcpkg.json | 18 ++- 38 files changed, 18 insertions(+), 1751 deletions(-) delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/deps.patch delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/usage delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/usage delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/usage delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/usage delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/usage delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/usage delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json diff --git a/dependencies/vcpkg b/dependencies/vcpkg index cbf4a66..a4275b7 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit cbf4a6641528cee6f172328984576f51698de726 +Subproject commit a4275b7eee79fb24ec2e135481ef5fce8b41c339 diff --git a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b..0000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch deleted file mode 100644 index a8637d8..0000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake deleted file mode 100644 index 22685e6..0000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports/sdl2/usage b/dependencies/vcpkg_overlay_ports/sdl2/usage deleted file mode 100644 index 1cddcd4..0000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main> - $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json deleted file mode 100644 index 1f46037..0000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch deleted file mode 100644 index 70654cf..0000000 --- a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake deleted file mode 100644 index 426d8af..0000000 --- a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports/tiff/usage b/dependencies/vcpkg_overlay_ports/tiff/usage deleted file mode 100644 index d47265b..0000000 --- a/dependencies/vcpkg_overlay_ports/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7..0000000 --- a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a..0000000 --- a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch deleted file mode 100644 index ac827f0..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt -index 8cde1ffe0..d4d09f223 100644 ---- a/tools/CMakeLists.txt -+++ b/tools/CMakeLists.txt -@@ -91,7 +91,9 @@ endif() - add_executable(dbus-launch ${dbus_launch_SOURCES}) - target_link_libraries(dbus-launch ${DBUS_LIBRARIES}) - if(DBUS_BUILD_X11) -- target_link_libraries(dbus-launch ${X11_LIBRARIES} ) -+ find_package(Threads REQUIRED) -+ target_link_libraries(dbus-launch ${X11_LIBRARIES} ${X11_xcb_LIB} ${X11_Xau_LIB} ${X11_Xdmcp_LIB} Threads::Threads) -+ target_include_directories(dbus-launch PRIVATE ${X11_INCLUDE_DIR}) - endif() - install(TARGETS dbus-launch ${INSTALL_TARGETS_DEFAULT_ARGS}) - diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch deleted file mode 100644 index 5cd2309..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake -index b7f3702..e2336ba 100644 ---- a/cmake/ConfigureChecks.cmake -+++ b/cmake/ConfigureChecks.cmake -@@ -51,6 +51,7 @@ check_symbol_exists(closefrom "unistd.h" HAVE_CLOSEFROM) # - check_symbol_exists(environ "unistd.h" HAVE_DECL_ENVIRON) - check_symbol_exists(fstatfs "sys/vfs.h" HAVE_FSTATFS) - check_symbol_exists(getgrouplist "grp.h" HAVE_GETGROUPLIST) # dbus-sysdeps.c -+check_symbol_exists(getpeereid "sys/types.h;unistd.h" HAVE_GETPEEREID) # dbus-sysdeps.c, - check_symbol_exists(getpeerucred "ucred.h" HAVE_GETPEERUCRED) # dbus-sysdeps.c, dbus-sysdeps-win.c - check_symbol_exists(getpwnam_r "errno.h;pwd.h" HAVE_GETPWNAM_R) # dbus-sysdeps-util-unix.c - check_symbol_exists(getrandom "sys/random.h" HAVE_GETRANDOM) -diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake -index 77fc19c..2f25643 100644 ---- a/cmake/config.h.cmake -+++ b/cmake/config.h.cmake -@@ -140,6 +140,9 @@ - /* Define to 1 if you have getgrouplist */ - #cmakedefine HAVE_GETGROUPLIST 1 - -+/* Define to 1 if you have getpeereid */ -+#cmakedefine HAVE_GETPEEREID 1 -+ - /* Define to 1 if you have getpeerucred */ - #cmakedefine HAVE_GETPEERUCRED 1 - diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch deleted file mode 100644 index 74193dc..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index d3ec71b..932066a 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -141,6 +141,10 @@ if(DBUS_LINUX) - if(ENABLE_SYSTEMD AND SYSTEMD_FOUND) - set(DBUS_BUS_ENABLE_SYSTEMD ON) - set(HAVE_SYSTEMD ${SYSTEMD_FOUND}) -+ pkg_check_modules(SYSTEMD libsystemd IMPORTED_TARGET) -+ set(SYSTEMD_LIBRARIES PkgConfig::SYSTEMD CACHE INTERNAL "") -+ else() -+ set(SYSTEMD_LIBRARIES "" CACHE INTERNAL "") - endif() - option(ENABLE_USER_SESSION "enable user-session semantics for session bus under systemd" OFF) - set(DBUS_ENABLE_USER_SESSION ${ENABLE_USER_SESSION}) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch deleted file mode 100644 index 6358148..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index caef738..b878f42 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -724,11 +724,11 @@ add_custom_target(help-options - # - if(DBUS_ENABLE_PKGCONFIG) - set(PLATFORM_LIBS pthread ${LIBRT}) -- if(PKG_CONFIG_FOUND) -- # convert lists of link libraries into -lstdc++ -lm etc.. -- foreach(LIB ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) -- set(LIBDBUS_LIBS "${LIBDBUS_LIBS} -l${LIB}") -- endforeach() -+ if(1) -+ set(LIBDBUS_LIBS "${CMAKE_THREAD_LIBS_INIT}") -+ if(LIBRT) -+ string(APPEND LIBDBUS_LIBS " -lrt") -+ endif() - set(original_prefix "${CMAKE_INSTALL_PREFIX}") - if(DBUS_RELOCATABLE) - set(pkgconfig_prefix "\${pcfiledir}/../..") diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake deleted file mode 100644 index 56c7e18..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake +++ /dev/null @@ -1,88 +0,0 @@ -vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) - -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.freedesktop.org/ - OUT_SOURCE_PATH SOURCE_PATH - REPO dbus/dbus - REF "dbus-${VERSION}" - SHA512 8e476b408514e6540c36beb84e8025827c22cda8958b6eb74d22b99c64765eb3cd5a6502aea546e3e5f0534039857b37edee89c659acef40e7cab0939947d4af - HEAD_REF master - PATCHES - cmake.dep.patch - pkgconfig.patch - getpeereid.patch # missing check from configure.ac - libsystemd.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS options - FEATURES - systemd ENABLE_SYSTEMD - x11 DBUS_BUILD_X11 - x11 CMAKE_REQUIRE_FIND_PACKAGE_X11 -) - -unset(ENV{DBUSDIR}) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - -DDBUS_BUILD_TESTS=OFF - -DDBUS_ENABLE_DOXYGEN_DOCS=OFF - -DDBUS_ENABLE_XML_DOCS=OFF - -DDBUS_INSTALL_SYSTEM_LIBS=OFF - #-DDBUS_SERVICE=ON - -DDBUS_WITH_GLIB=OFF - -DTHREADS_PREFER_PTHREAD_FLAG=ON - -DXSLTPROC_EXECUTABLE=FALSE - "-DCMAKE_INSTALL_SYSCONFDIR=${CURRENT_PACKAGES_DIR}/etc/${PORT}" - "-DWITH_SYSTEMD_SYSTEMUNITDIR=lib/systemd/system" - "-DWITH_SYSTEMD_USERUNITDIR=lib/systemd/user" - ${options} - OPTIONS_RELEASE - -DDBUS_DISABLE_ASSERT=OFF - -DDBUS_ENABLE_STATS=OFF - -DDBUS_ENABLE_VERBOSE_MODE=OFF - MAYBE_UNUSED_VARIABLES - DBUS_BUILD_X11 - DBUS_WITH_GLIB - ENABLE_SYSTEMD - THREADS_PREFER_PTHREAD_FLAG - WITH_SYSTEMD_SYSTEMUNITDIR - WITH_SYSTEMD_USERUNITDIR -) -vcpkg_cmake_install() -vcpkg_copy_pdbs() -vcpkg_cmake_config_fixup(PACKAGE_NAME "DBus1" CONFIG_PATH "lib/cmake/DBus1") -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/debug/var/" - "${CURRENT_PACKAGES_DIR}/etc" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/services" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/session.d" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system-services" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.d" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" - "${CURRENT_PACKAGES_DIR}/share/doc" - "${CURRENT_PACKAGES_DIR}/var" -) - -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.conf</include>" "") -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<includedir>${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.d</includedir>" "") -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session-local.conf</include>" "") - -set(TOOLS daemon launch monitor run-session send test-tool update-activation-environment) -if(VCPKG_TARGET_IS_WINDOWS) - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") - file(RENAME "${CURRENT_PACKAGES_DIR}/bin/dbus-env.bat" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat" "${CURRENT_PACKAGES_DIR}" "%~dp0/../..") -else() - list(APPEND TOOLS cleanup-sockets uuidgen) -endif() -list(TRANSFORM TOOLS PREPEND "dbus-" ) -vcpkg_copy_tools(TOOL_NAMES ${TOOLS} AUTO_CLEAN) - -file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json deleted file mode 100644 index 853dff0..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "dbus", - "version": "1.15.8", - "port-version": 2, - "description": "D-Bus specification and reference implementation, including libdbus and dbus-daemon", - "homepage": "https://gitlab.freedesktop.org/dbus/dbus", - "license": "AFL-2.1 OR GPL-2.0-or-later", - "supports": "!uwp & !staticcrt", - "dependencies": [ - "expat", - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - ], - "features": { - "x11": { - "description": "Build with X11 autolaunch support", - "dependencies": [ - "libx11" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch deleted file mode 100644 index a8637d8..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake deleted file mode 100644 index 22685e6..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/usage b/dependencies/vcpkg_overlay_ports_linux/sdl2/usage deleted file mode 100644 index 1cddcd4..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main> - $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json deleted file mode 100644 index 1f46037..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch deleted file mode 100644 index 70654cf..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake deleted file mode 100644 index 426d8af..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/usage b/dependencies/vcpkg_overlay_ports_linux/tiff/usage deleted file mode 100644 index d47265b..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a..0000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch deleted file mode 100644 index a8637d8..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake deleted file mode 100644 index 22685e6..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage deleted file mode 100644 index 1cddcd4..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main> - $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json deleted file mode 100644 index 1f46037..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch deleted file mode 100644 index 70654cf..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake deleted file mode 100644 index 426d8af..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/usage b/dependencies/vcpkg_overlay_ports_mac/tiff/usage deleted file mode 100644 index d47265b..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a..0000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/vcpkg.json b/vcpkg.json index b27a709..0a46e32 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "cemu", "version-string": "1.0", - "builtin-baseline": "cbf4a6641528cee6f172328984576f51698de726", + "builtin-baseline": "a4275b7eee79fb24ec2e135481ef5fce8b41c339", "dependencies": [ "pugixml", "zlib", @@ -44,6 +44,22 @@ "default-features": false, "features": [ "openssl" ] }, + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "tiff", + "default-features": false, + "features": ["jpeg", "zip"] + }, "libusb" + ], + "overrides": [ + { + "name": "sdl2", + "version": "2.30.3" + } ] } From 1672f969bbc4a683e4a852aa2e145c1e6f9f68e6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:38:59 +0200 Subject: [PATCH 123/130] Latte: Add support for vertex format used by Rabbids Land --- .../LatteDecompilerEmitGLSLAttrDecoder.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp index ea2b949..76d7632 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp @@ -241,6 +241,16 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } + else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned == 1 ) + { + // seen in Rabbids Land + _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.xyzw = floatBitsToUint(vec4(ivec4(attrDecoder)));" _CRLF); + } else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams @@ -496,3 +506,5 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex cemu_assert_debug(false); } } + + From d4c2c3d2098616b3596b743f33fcc37629282ec0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:50:06 +0200 Subject: [PATCH 124/130] nsyskbd: Stub KBDGetKey Fixes MSX VC games freezing on boot --- src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp index 72cb9bd..f1571cc 100644 --- a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp +++ b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp @@ -3,6 +3,11 @@ namespace nsyskbd { + bool IsValidChannel(uint32 channel) + { + return channel >= 0 && channel < 4; + } + uint32 KBDGetChannelStatus(uint32 channel, uint32be* status) { static bool loggedError = false; @@ -16,8 +21,38 @@ namespace nsyskbd return 0; } +#pragma pack(push, 1) + struct KeyState + { + uint8be channel; + uint8be ukn1; + uint8be _padding[2]; + uint32be ukn4; + uint32be ukn8; + uint16be uknC; + }; +#pragma pack(pop) + static_assert(sizeof(KeyState) == 0xE); // actual size might be padded to 0x10? + + uint32 KBDGetKey(uint32 channel, KeyState* keyState) + { + // used by MSX VC + if(!IsValidChannel(channel) || !keyState) + { + cemuLog_log(LogType::APIErrors, "KBDGetKey(): Invalid parameter"); + return 0; + } + keyState->channel = channel; + keyState->ukn1 = 0; + keyState->ukn4 = 0; + keyState->ukn8 = 0; + keyState->uknC = 0; + return 0; + } + void nsyskbd_load() { cafeExportRegister("nsyskbd", KBDGetChannelStatus, LogType::Placeholder); + cafeExportRegister("nsyskbd", KBDGetKey, LogType::Placeholder); } } From f3d20832c191f90fe4ad6f9b64a3ff21eb477b02 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:28:21 +0200 Subject: [PATCH 125/130] Avoid an unhandled exception when mlc path is invalid --- src/gui/CemuApp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 505a09c..86d81e4 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -266,10 +266,10 @@ std::vector<const wxLanguageInfo*> CemuApp::GetAvailableTranslationLanguages(wxT void CemuApp::CreateDefaultFiles(bool first_start) { + std::error_code ec; fs::path mlc = ActiveSettings::GetMlcPath(); - // check for mlc01 folder missing if custom path has been set - if (!fs::exists(mlc) && !first_start) + if (!fs::exists(mlc, ec) && !first_start) { const wxString message = formatWxString(_("Your mlc01 folder seems to be missing.\n\nThis is where Cemu stores save files, game updates and other Wii U files.\n\nThe expected path is:\n{}\n\nDo you want to create the folder at the expected path?"), _pathToUtf8(mlc)); From 93b58ae6f7315bf17126d49314e0132eeb356ef9 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Thu, 27 Jun 2024 23:55:20 +0100 Subject: [PATCH 126/130] nsyshid: Add infrastructure and support for emulating Skylander Portal (#971) --- src/Cafe/CMakeLists.txt | 4 + .../OS/libs/nsyshid/AttachDefaultBackends.cpp | 9 + src/Cafe/OS/libs/nsyshid/Backend.h | 57 +- src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp | 29 + src/Cafe/OS/libs/nsyshid/BackendEmulated.h | 16 + src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 36 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 6 +- .../OS/libs/nsyshid/BackendWindowsHID.cpp | 34 +- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 6 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 939 ++++++++++++++++++ src/Cafe/OS/libs/nsyshid/Skylander.h | 98 ++ src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 41 +- src/config/CemuConfig.cpp | 8 + src/config/CemuConfig.h | 6 + src/gui/CMakeLists.txt | 2 + .../EmulatedUSBDeviceFrame.cpp | 354 +++++++ .../EmulatedUSBDeviceFrame.h | 42 + src/gui/MainWindow.cpp | 27 + src/gui/MainWindow.h | 2 + 19 files changed, 1658 insertions(+), 58 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/BackendEmulated.h create mode 100644 src/Cafe/OS/libs/nsyshid/Skylander.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Skylander.h create mode 100644 src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp create mode 100644 src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index b5090dc..1583bdd 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -457,10 +457,14 @@ add_library(CemuCafe OS/libs/nsyshid/AttachDefaultBackends.cpp OS/libs/nsyshid/Whitelist.cpp OS/libs/nsyshid/Whitelist.h + OS/libs/nsyshid/BackendEmulated.cpp + OS/libs/nsyshid/BackendEmulated.h OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.h + OS/libs/nsyshid/Skylander.cpp + OS/libs/nsyshid/Skylander.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp index 6e6cb12..fc8e496 100644 --- a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -1,5 +1,6 @@ #include "nsyshid.h" #include "Backend.h" +#include "BackendEmulated.h" #if NSYSHID_ENABLE_BACKEND_LIBUSB @@ -37,5 +38,13 @@ namespace nsyshid::backend } } #endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID + // add emulated backend + { + auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>(); + if (backendEmulated->IsInitialisedOk()) + { + AttachBackend(backendEmulated); + } + } } } // namespace nsyshid::backend diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 641104f..0323273 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -23,6 +23,55 @@ namespace nsyshid /* +0x12 */ uint16be maxPacketSizeTX; } HID_t; + struct TransferCommand + { + uint8* data; + sint32 length; + + TransferCommand(uint8* data, sint32 length) + : data(data), length(length) + { + } + virtual ~TransferCommand() = default; + }; + + struct ReadMessage final : TransferCommand + { + sint32 bytesRead; + + ReadMessage(uint8* data, sint32 length, sint32 bytesRead) + : bytesRead(bytesRead), TransferCommand(data, length) + { + } + using TransferCommand::TransferCommand; + }; + + struct WriteMessage final : TransferCommand + { + sint32 bytesWritten; + + WriteMessage(uint8* data, sint32 length, sint32 bytesWritten) + : bytesWritten(bytesWritten), TransferCommand(data, length) + { + } + using TransferCommand::TransferCommand; + }; + + struct ReportMessage final : TransferCommand + { + uint8* reportData; + sint32 length; + uint8* originalData; + sint32 originalLength; + + ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + : reportData(reportData), length(length), originalData(originalData), + originalLength(originalLength), TransferCommand(reportData, length) + { + } + using TransferCommand::TransferCommand; + }; + static_assert(offsetof(HID_t, vendorId) == 0x8, ""); static_assert(offsetof(HID_t, productId) == 0xA, ""); static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); @@ -69,7 +118,7 @@ namespace nsyshid ErrorTimeout, }; - virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0; + virtual ReadResult Read(ReadMessage* message) = 0; enum class WriteResult { @@ -78,7 +127,7 @@ namespace nsyshid ErrorTimeout, }; - virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0; + virtual WriteResult Write(WriteMessage* message) = 0; virtual bool GetDescriptor(uint8 descType, uint8 descIndex, @@ -88,7 +137,7 @@ namespace nsyshid virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; - virtual bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) = 0; + virtual bool SetReport(ReportMessage* message) = 0; }; class Backend { @@ -121,6 +170,8 @@ namespace nsyshid std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice); + bool FindDeviceById(uint16 vendorId, uint16 productId); + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); // called from OnAttach() - attach devices that your backend can see here diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp new file mode 100644 index 0000000..11a299e --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -0,0 +1,29 @@ +#include "BackendEmulated.h" +#include "Skylander.h" +#include "config/CemuConfig.h" + +namespace nsyshid::backend::emulated +{ + BackendEmulated::BackendEmulated() + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendEmulated: emulated backend initialised"); + } + + BackendEmulated::~BackendEmulated() = default; + + bool BackendEmulated::IsInitialisedOk() + { + return true; + } + + void BackendEmulated::AttachVisibleDevices() + { + if (GetConfig().emulated_usb_devices.emulate_skylander_portal && !FindDeviceById(0x1430, 0x0150)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Portal"); + // Add Skylander Portal + auto device = std::make_shared<SkylanderPortalDevice>(); + AttachDevice(device); + } + } +} // namespace nsyshid::backend::emulated \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.h b/src/Cafe/OS/libs/nsyshid/BackendEmulated.h new file mode 100644 index 0000000..cf38a8b --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.h @@ -0,0 +1,16 @@ +#include "nsyshid.h" +#include "Backend.h" + +namespace nsyshid::backend::emulated +{ + class BackendEmulated : public nsyshid::Backend { + public: + BackendEmulated(); + ~BackendEmulated(); + + bool IsInitialisedOk() override; + + protected: + void AttachVisibleDevices() override; + }; +} // namespace nsyshid::backend::emulated diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 4f88b7e..6701d78 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -241,11 +241,6 @@ namespace nsyshid::backend::libusb ret); return nullptr; } - if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) - { - cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); - } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, desc.idProduct, @@ -471,7 +466,7 @@ namespace nsyshid::backend::libusb return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; } - Device::ReadResult DeviceLibusb::Read(uint8* data, sint32 length, sint32& bytesRead) + Device::ReadResult DeviceLibusb::Read(ReadMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -488,8 +483,8 @@ namespace nsyshid::backend::libusb { ret = libusb_bulk_transfer(handleLock->GetHandle(), this->m_libusbEndpointIn, - data, - length, + message->data, + message->length, &actualLength, timeout); } @@ -500,8 +495,8 @@ namespace nsyshid::backend::libusb // success cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", actualLength, - length); - bytesRead = actualLength; + message->length); + message->bytesRead = actualLength; return ReadResult::Success; } cemuLog_logDebug(LogType::Force, @@ -510,7 +505,7 @@ namespace nsyshid::backend::libusb return ReadResult::Error; } - Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten) + Device::WriteResult DeviceLibusb::Write(WriteMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -520,23 +515,23 @@ namespace nsyshid::backend::libusb return WriteResult::Error; } - bytesWritten = 0; + message->bytesWritten = 0; int actualLength = 0; int ret = libusb_bulk_transfer(handleLock->GetHandle(), this->m_libusbEndpointOut, - data, - length, + message->data, + message->length, &actualLength, 0); if (ret == 0) { // success - bytesWritten = actualLength; + message->bytesWritten = actualLength; cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", - bytesWritten, - length); + message->bytesWritten, + message->length); return WriteResult::Success; } cemuLog_logDebug(LogType::Force, @@ -713,8 +708,7 @@ namespace nsyshid::backend::libusb return true; } - bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData, - sint32 originalLength) + bool DeviceLibusb::SetReport(ReportMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -731,8 +725,8 @@ namespace nsyshid::backend::libusb bRequest, wValue, wIndex, - reportData, - length, + message->reportData, + message->length, timeout); #endif diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index 216be6c..a8122af 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -63,9 +63,9 @@ namespace nsyshid::backend::libusb bool IsOpened() override; - ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + ReadResult Read(ReadMessage* message) override; - WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, @@ -75,7 +75,7 @@ namespace nsyshid::backend::libusb bool SetProtocol(uint32 ifIndex, uint32 protocol) override; - bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + bool SetReport(ReportMessage* message) override; uint8 m_libusbBusNumber; uint8 m_libusbDeviceAddress; diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 23da579..3cfba26 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -196,20 +196,20 @@ namespace nsyshid::backend::windows return m_hFile != INVALID_HANDLE_VALUE; } - Device::ReadResult DeviceWindowsHID::Read(uint8* data, sint32 length, sint32& bytesRead) + Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message) { - bytesRead = 0; + message->bytesRead = 0; DWORD bt; OVERLAPPED ovlp = {0}; ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - uint8* tempBuffer = (uint8*)malloc(length + 1); + uint8* tempBuffer = (uint8*)malloc(message->length + 1); sint32 transferLength = 0; // minus report byte - _debugPrintHex("HID_READ_BEFORE", data, length); + _debugPrintHex("HID_READ_BEFORE", message->data, message->length); - cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length); - BOOL readResult = ReadFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length); + BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); if (readResult != FALSE) { // sometimes we get the result immediately @@ -247,7 +247,7 @@ namespace nsyshid::backend::windows ReadResult result = ReadResult::Success; if (bt != 0) { - memcpy(data, tempBuffer + 1, transferLength); + memcpy(message->data, tempBuffer + 1, transferLength); sint32 hidReadLength = transferLength; char debugOutput[1024] = {0}; @@ -257,7 +257,7 @@ namespace nsyshid::backend::windows } cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); - bytesRead = transferLength; + message->bytesRead = transferLength; result = ReadResult::Success; } else @@ -270,19 +270,19 @@ namespace nsyshid::backend::windows return result; } - Device::WriteResult DeviceWindowsHID::Write(uint8* data, sint32 length, sint32& bytesWritten) + Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message) { - bytesWritten = 0; + message->bytesWritten = 0; DWORD bt; OVERLAPPED ovlp = {0}; ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - uint8* tempBuffer = (uint8*)malloc(length + 1); - memcpy(tempBuffer + 1, data, length); + uint8* tempBuffer = (uint8*)malloc(message->length + 1); + memcpy(tempBuffer + 1, message->data, message->length); tempBuffer[0] = 0; // report byte? - cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", length); - BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length); + BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); if (writeResult != FALSE) { // sometimes we get the result immediately @@ -314,7 +314,7 @@ namespace nsyshid::backend::windows if (bt != 0) { - bytesWritten = length; + message->bytesWritten = message->length; return WriteResult::Success; } return WriteResult::Error; @@ -407,12 +407,12 @@ namespace nsyshid::backend::windows return true; } - bool DeviceWindowsHID::SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + bool DeviceWindowsHID::SetReport(ReportMessage* message) { sint32 retryCount = 0; while (true) { - BOOL r = HidD_SetOutputReport(this->m_hFile, reportData, length); + BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length); if (r != FALSE) break; Sleep(20); // retry diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h index 049b33e..84fe7bd 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -41,15 +41,15 @@ namespace nsyshid::backend::windows bool IsOpened() override; - ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + ReadResult Read(ReadMessage* message) override; - WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; bool SetProtocol(uint32 ifIndef, uint32 protocol) override; - bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + bool SetReport(ReportMessage* message) override; private: wchar_t* m_devicePath; diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp new file mode 100644 index 0000000..3123d14 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -0,0 +1,939 @@ +#include "Skylander.h" + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + SkylanderUSB g_skyportal; + + const std::map<const std::pair<const uint16, const uint16>, const std::string> + listSkylanders = { + {{0, 0x0000}, "Whirlwind"}, + {{0, 0x1801}, "Series 2 Whirlwind"}, + {{0, 0x1C02}, "Polar Whirlwind"}, + {{0, 0x2805}, "Horn Blast Whirlwind"}, + {{0, 0x3810}, "Eon's Elite Whirlwind"}, + {{1, 0x0000}, "Sonic Boom"}, + {{1, 0x1801}, "Series 2 Sonic Boom"}, + {{2, 0x0000}, "Warnado"}, + {{2, 0x2206}, "LightCore Warnado"}, + {{3, 0x0000}, "Lightning Rod"}, + {{3, 0x1801}, "Series 2 Lightning Rod"}, + {{4, 0x0000}, "Bash"}, + {{4, 0x1801}, "Series 2 Bash"}, + {{5, 0x0000}, "Terrafin"}, + {{5, 0x1801}, "Series 2 Terrafin"}, + {{5, 0x2805}, "Knockout Terrafin"}, + {{5, 0x3810}, "Eon's Elite Terrafin"}, + {{6, 0x0000}, "Dino Rang"}, + {{6, 0x4810}, "Eon's Elite Dino Rang"}, + {{7, 0x0000}, "Prism Break"}, + {{7, 0x1801}, "Series 2 Prism Break"}, + {{7, 0x2805}, "Hyper Beam Prism Break"}, + {{7, 0x1206}, "LightCore Prism Break"}, + {{8, 0x0000}, "Sunburn"}, + {{9, 0x0000}, "Eruptor"}, + {{9, 0x1801}, "Series 2 Eruptor"}, + {{9, 0x2C02}, "Volcanic Eruptor"}, + {{9, 0x2805}, "Lava Barf Eruptor"}, + {{9, 0x1206}, "LightCore Eruptor"}, + {{9, 0x3810}, "Eon's Elite Eruptor"}, + {{10, 0x0000}, "Ignitor"}, + {{10, 0x1801}, "Series 2 Ignitor"}, + {{10, 0x1C03}, "Legendary Ignitor"}, + {{11, 0x0000}, "Flameslinger"}, + {{11, 0x1801}, "Series 2 Flameslinger"}, + {{12, 0x0000}, "Zap"}, + {{12, 0x1801}, "Series 2 Zap"}, + {{13, 0x0000}, "Wham Shell"}, + {{13, 0x2206}, "LightCore Wham Shell"}, + {{14, 0x0000}, "Gill Grunt"}, + {{14, 0x1801}, "Series 2 Gill Grunt"}, + {{14, 0x2805}, "Anchors Away Gill Grunt"}, + {{14, 0x3805}, "Tidal Wave Gill Grunt"}, + {{14, 0x3810}, "Eon's Elite Gill Grunt"}, + {{15, 0x0000}, "Slam Bam"}, + {{15, 0x1801}, "Series 2 Slam Bam"}, + {{15, 0x1C03}, "Legendary Slam Bam"}, + {{15, 0x4810}, "Eon's Elite Slam Bam"}, + {{16, 0x0000}, "Spyro"}, + {{16, 0x1801}, "Series 2 Spyro"}, + {{16, 0x2C02}, "Dark Mega Ram Spyro"}, + {{16, 0x2805}, "Mega Ram Spyro"}, + {{16, 0x3810}, "Eon's Elite Spyro"}, + {{17, 0x0000}, "Voodood"}, + {{17, 0x4810}, "Eon's Elite Voodood"}, + {{18, 0x0000}, "Double Trouble"}, + {{18, 0x1801}, "Series 2 Double Trouble"}, + {{18, 0x1C02}, "Royal Double Trouble"}, + {{19, 0x0000}, "Trigger Happy"}, + {{19, 0x1801}, "Series 2 Trigger Happy"}, + {{19, 0x2C02}, "Springtime Trigger Happy"}, + {{19, 0x2805}, "Big Bang Trigger Happy"}, + {{19, 0x3810}, "Eon's Elite Trigger Happy"}, + {{20, 0x0000}, "Drobot"}, + {{20, 0x1801}, "Series 2 Drobot"}, + {{20, 0x1206}, "LightCore Drobot"}, + {{21, 0x0000}, "Drill Seargeant"}, + {{21, 0x1801}, "Series 2 Drill Seargeant"}, + {{22, 0x0000}, "Boomer"}, + {{22, 0x4810}, "Eon's Elite Boomer"}, + {{23, 0x0000}, "Wrecking Ball"}, + {{23, 0x1801}, "Series 2 Wrecking Ball"}, + {{24, 0x0000}, "Camo"}, + {{24, 0x2805}, "Thorn Horn Camo"}, + {{25, 0x0000}, "Zook"}, + {{25, 0x1801}, "Series 2 Zook"}, + {{25, 0x4810}, "Eon's Elite Zook"}, + {{26, 0x0000}, "Stealth Elf"}, + {{26, 0x1801}, "Series 2 Stealth Elf"}, + {{26, 0x2C02}, "Dark Stealth Elf"}, + {{26, 0x1C03}, "Legendary Stealth Elf"}, + {{26, 0x2805}, "Ninja Stealth Elf"}, + {{26, 0x3810}, "Eon's Elite Stealth Elf"}, + {{27, 0x0000}, "Stump Smash"}, + {{27, 0x1801}, "Series 2 Stump Smash"}, + {{28, 0x0000}, "Dark Spyro"}, + {{29, 0x0000}, "Hex"}, + {{29, 0x1801}, "Series 2 Hex"}, + {{29, 0x1206}, "LightCore Hex"}, + {{30, 0x0000}, "Chop Chop"}, + {{30, 0x1801}, "Series 2 Chop Chop"}, + {{30, 0x2805}, "Twin Blade Chop Chop"}, + {{30, 0x3810}, "Eon's Elite Chop Chop"}, + {{31, 0x0000}, "Ghost Roaster"}, + {{31, 0x4810}, "Eon's Elite Ghost Roaster"}, + {{32, 0x0000}, "Cynder"}, + {{32, 0x1801}, "Series 2 Cynder"}, + {{32, 0x2805}, "Phantom Cynder"}, + {{100, 0x0000}, "Jet Vac"}, + {{100, 0x1403}, "Legendary Jet Vac"}, + {{100, 0x2805}, "Turbo Jet Vac"}, + {{100, 0x3805}, "Full Blast Jet Vac"}, + {{100, 0x1206}, "LightCore Jet Vac"}, + {{101, 0x0000}, "Swarm"}, + {{102, 0x0000}, "Crusher"}, + {{102, 0x1602}, "Granite Crusher"}, + {{103, 0x0000}, "Flashwing"}, + {{103, 0x1402}, "Jade Flash Wing"}, + {{103, 0x2206}, "LightCore Flashwing"}, + {{104, 0x0000}, "Hot Head"}, + {{105, 0x0000}, "Hot Dog"}, + {{105, 0x1402}, "Molten Hot Dog"}, + {{105, 0x2805}, "Fire Bone Hot Dog"}, + {{106, 0x0000}, "Chill"}, + {{106, 0x1603}, "Legendary Chill"}, + {{106, 0x2805}, "Blizzard Chill"}, + {{106, 0x1206}, "LightCore Chill"}, + {{107, 0x0000}, "Thumpback"}, + {{108, 0x0000}, "Pop Fizz"}, + {{108, 0x1402}, "Punch Pop Fizz"}, + {{108, 0x3C02}, "Love Potion Pop Fizz"}, + {{108, 0x2805}, "Super Gulp Pop Fizz"}, + {{108, 0x3805}, "Fizzy Frenzy Pop Fizz"}, + {{108, 0x1206}, "LightCore Pop Fizz"}, + {{109, 0x0000}, "Ninjini"}, + {{109, 0x1602}, "Scarlet Ninjini"}, + {{110, 0x0000}, "Bouncer"}, + {{110, 0x1603}, "Legendary Bouncer"}, + {{111, 0x0000}, "Sprocket"}, + {{111, 0x2805}, "Heavy Duty Sprocket"}, + {{112, 0x0000}, "Tree Rex"}, + {{112, 0x1602}, "Gnarly Tree Rex"}, + {{113, 0x0000}, "Shroomboom"}, + {{113, 0x3805}, "Sure Shot Shroomboom"}, + {{113, 0x1206}, "LightCore Shroomboom"}, + {{114, 0x0000}, "Eye Brawl"}, + {{115, 0x0000}, "Fright Rider"}, + {{200, 0x0000}, "Anvil Rain"}, + {{201, 0x0000}, "Hidden Treasure"}, + {{201, 0x2000}, "Platinum Hidden Treasure"}, + {{202, 0x0000}, "Healing Elixir"}, + {{203, 0x0000}, "Ghost Pirate Swords"}, + {{204, 0x0000}, "Time Twist Hourglass"}, + {{205, 0x0000}, "Sky Iron Shield"}, + {{206, 0x0000}, "Winged Boots"}, + {{207, 0x0000}, "Sparx the Dragonfly"}, + {{208, 0x0000}, "Dragonfire Cannon"}, + {{208, 0x1602}, "Golden Dragonfire Cannon"}, + {{209, 0x0000}, "Scorpion Striker"}, + {{210, 0x3002}, "Biter's Bane"}, + {{210, 0x3008}, "Sorcerous Skull"}, + {{210, 0x300B}, "Axe of Illusion"}, + {{210, 0x300E}, "Arcane Hourglass"}, + {{210, 0x3012}, "Spell Slapper"}, + {{210, 0x3014}, "Rune Rocket"}, + {{211, 0x3001}, "Tidal Tiki"}, + {{211, 0x3002}, "Wet Walter"}, + {{211, 0x3006}, "Flood Flask"}, + {{211, 0x3406}, "Legendary Flood Flask"}, + {{211, 0x3007}, "Soaking Staff"}, + {{211, 0x300B}, "Aqua Axe"}, + {{211, 0x3016}, "Frost Helm"}, + {{212, 0x3003}, "Breezy Bird"}, + {{212, 0x3006}, "Drafty Decanter"}, + {{212, 0x300D}, "Tempest Timer"}, + {{212, 0x3010}, "Cloudy Cobra"}, + {{212, 0x3011}, "Storm Warning"}, + {{212, 0x3018}, "Cyclone Saber"}, + {{213, 0x3004}, "Spirit Sphere"}, + {{213, 0x3404}, "Legendary Spirit Sphere"}, + {{213, 0x3008}, "Spectral Skull"}, + {{213, 0x3408}, "Legendary Spectral Skull"}, + {{213, 0x300B}, "Haunted Hatchet"}, + {{213, 0x300C}, "Grim Gripper"}, + {{213, 0x3010}, "Spooky Snake"}, + {{213, 0x3017}, "Dream Piercer"}, + {{214, 0x3000}, "Tech Totem"}, + {{214, 0x3007}, "Automatic Angel"}, + {{214, 0x3009}, "Factory Flower"}, + {{214, 0x300C}, "Grabbing Gadget"}, + {{214, 0x3016}, "Makers Mana"}, + {{214, 0x301A}, "Topsy Techy"}, + {{215, 0x3005}, "Eternal Flame"}, + {{215, 0x3009}, "Fire Flower"}, + {{215, 0x3011}, "Scorching Stopper"}, + {{215, 0x3012}, "Searing Spinner"}, + {{215, 0x3017}, "Spark Spear"}, + {{215, 0x301B}, "Blazing Belch"}, + {{216, 0x3000}, "Banded Boulder"}, + {{216, 0x3003}, "Rock Hawk"}, + {{216, 0x300A}, "Slag Hammer"}, + {{216, 0x300E}, "Dust Of Time"}, + {{216, 0x3013}, "Spinning Sandstorm"}, + {{216, 0x301A}, "Rubble Trouble"}, + {{217, 0x3003}, "Oak Eagle"}, + {{217, 0x3005}, "Emerald Energy"}, + {{217, 0x300A}, "Weed Whacker"}, + {{217, 0x3010}, "Seed Serpent"}, + {{217, 0x3018}, "Jade Blade"}, + {{217, 0x301B}, "Shrub Shrieker"}, + {{218, 0x3000}, "Dark Dagger"}, + {{218, 0x3014}, "Shadow Spider"}, + {{218, 0x301A}, "Ghastly Grimace"}, + {{219, 0x3000}, "Shining Ship"}, + {{219, 0x300F}, "Heavenly Hawk"}, + {{219, 0x301B}, "Beam Scream"}, + {{220, 0x301E}, "Kaos Trap"}, + {{220, 0x351F}, "Ultimate Kaos Trap"}, + {{230, 0x0000}, "Hand of Fate"}, + {{230, 0x3403}, "Legendary Hand of Fate"}, + {{231, 0x0000}, "Piggy Bank"}, + {{232, 0x0000}, "Rocket Ram"}, + {{233, 0x0000}, "Tiki Speaky"}, + {{300, 0x0000}, "Dragon’s Peak"}, + {{301, 0x0000}, "Empire of Ice"}, + {{302, 0x0000}, "Pirate Seas"}, + {{303, 0x0000}, "Darklight Crypt"}, + {{304, 0x0000}, "Volcanic Vault"}, + {{305, 0x0000}, "Mirror of Mystery"}, + {{306, 0x0000}, "Nightmare Express"}, + {{307, 0x0000}, "Sunscraper Spire"}, + {{308, 0x0000}, "Midnight Museum"}, + {{404, 0x0000}, "Legendary Bash"}, + {{416, 0x0000}, "Legendary Spyro"}, + {{419, 0x0000}, "Legendary Trigger Happy"}, + {{430, 0x0000}, "Legendary Chop Chop"}, + {{450, 0x0000}, "Gusto"}, + {{451, 0x0000}, "Thunderbolt"}, + {{452, 0x0000}, "Fling Kong"}, + {{453, 0x0000}, "Blades"}, + {{453, 0x3403}, "Legendary Blades"}, + {{454, 0x0000}, "Wallop"}, + {{455, 0x0000}, "Head Rush"}, + {{455, 0x3402}, "Nitro Head Rush"}, + {{456, 0x0000}, "Fist Bump"}, + {{457, 0x0000}, "Rocky Roll"}, + {{458, 0x0000}, "Wildfire"}, + {{458, 0x3402}, "Dark Wildfire"}, + {{459, 0x0000}, "Ka Boom"}, + {{460, 0x0000}, "Trail Blazer"}, + {{461, 0x0000}, "Torch"}, + {{462, 0x3000}, "Snap Shot"}, + {{462, 0x3402}, "Dark Snap Shot"}, + {{463, 0x0000}, "Lob Star"}, + {{463, 0x3402}, "Winterfest Lob-Star"}, + {{464, 0x0000}, "Flip Wreck"}, + {{465, 0x0000}, "Echo"}, + {{466, 0x0000}, "Blastermind"}, + {{467, 0x0000}, "Enigma"}, + {{468, 0x0000}, "Deja Vu"}, + {{468, 0x3403}, "Legendary Deja Vu"}, + {{469, 0x0000}, "Cobra Candabra"}, + {{469, 0x3402}, "King Cobra Cadabra"}, + {{470, 0x0000}, "Jawbreaker"}, + {{470, 0x3403}, "Legendary Jawbreaker"}, + {{471, 0x0000}, "Gearshift"}, + {{472, 0x0000}, "Chopper"}, + {{473, 0x0000}, "Tread Head"}, + {{474, 0x0000}, "Bushwack"}, + {{474, 0x3403}, "Legendary Bushwack"}, + {{475, 0x0000}, "Tuff Luck"}, + {{476, 0x0000}, "Food Fight"}, + {{476, 0x3402}, "Dark Food Fight"}, + {{477, 0x0000}, "High Five"}, + {{478, 0x0000}, "Krypt King"}, + {{478, 0x3402}, "Nitro Krypt King"}, + {{479, 0x0000}, "Short Cut"}, + {{480, 0x0000}, "Bat Spin"}, + {{481, 0x0000}, "Funny Bone"}, + {{482, 0x0000}, "Knight Light"}, + {{483, 0x0000}, "Spotlight"}, + {{484, 0x0000}, "Knight Mare"}, + {{485, 0x0000}, "Blackout"}, + {{502, 0x0000}, "Bop"}, + {{505, 0x0000}, "Terrabite"}, + {{506, 0x0000}, "Breeze"}, + {{508, 0x0000}, "Pet Vac"}, + {{508, 0x3402}, "Power Punch Pet Vac"}, + {{507, 0x0000}, "Weeruptor"}, + {{507, 0x3402}, "Eggcellent Weeruptor"}, + {{509, 0x0000}, "Small Fry"}, + {{510, 0x0000}, "Drobit"}, + {{519, 0x0000}, "Trigger Snappy"}, + {{526, 0x0000}, "Whisper Elf"}, + {{540, 0x0000}, "Barkley"}, + {{540, 0x3402}, "Gnarly Barkley"}, + {{541, 0x0000}, "Thumpling"}, + {{514, 0x0000}, "Gill Runt"}, + {{542, 0x0000}, "Mini-Jini"}, + {{503, 0x0000}, "Spry"}, + {{504, 0x0000}, "Hijinx"}, + {{543, 0x0000}, "Eye Small"}, + {{601, 0x0000}, "King Pen"}, + {{602, 0x0000}, "Tri-Tip"}, + {{603, 0x0000}, "Chopscotch"}, + {{604, 0x0000}, "Boom Bloom"}, + {{605, 0x0000}, "Pit Boss"}, + {{606, 0x0000}, "Barbella"}, + {{607, 0x0000}, "Air Strike"}, + {{608, 0x0000}, "Ember"}, + {{609, 0x0000}, "Ambush"}, + {{610, 0x0000}, "Dr. Krankcase"}, + {{611, 0x0000}, "Hood Sickle"}, + {{612, 0x0000}, "Tae Kwon Crow"}, + {{613, 0x0000}, "Golden Queen"}, + {{614, 0x0000}, "Wolfgang"}, + {{615, 0x0000}, "Pain-Yatta"}, + {{616, 0x0000}, "Mysticat"}, + {{617, 0x0000}, "Starcast"}, + {{618, 0x0000}, "Buckshot"}, + {{619, 0x0000}, "Aurora"}, + {{620, 0x0000}, "Flare Wolf"}, + {{621, 0x0000}, "Chompy Mage"}, + {{622, 0x0000}, "Bad Juju"}, + {{623, 0x0000}, "Grave Clobber"}, + {{624, 0x0000}, "Blaster-Tron"}, + {{625, 0x0000}, "Ro-Bow"}, + {{626, 0x0000}, "Chain Reaction"}, + {{627, 0x0000}, "Kaos"}, + {{628, 0x0000}, "Wild Storm"}, + {{629, 0x0000}, "Tidepool"}, + {{630, 0x0000}, "Crash Bandicoot"}, + {{631, 0x0000}, "Dr. Neo Cortex"}, + {{1000, 0x0000}, "Boom Jet (Bottom)"}, + {{1001, 0x0000}, "Free Ranger (Bottom)"}, + {{1001, 0x2403}, "Legendary Free Ranger (Bottom)"}, + {{1002, 0x0000}, "Rubble Rouser (Bottom)"}, + {{1003, 0x0000}, "Doom Stone (Bottom)"}, + {{1004, 0x0000}, "Blast Zone (Bottom)"}, + {{1004, 0x2402}, "Dark Blast Zone (Bottom)"}, + {{1005, 0x0000}, "Fire Kraken (Bottom)"}, + {{1005, 0x2402}, "Jade Fire Kraken (Bottom)"}, + {{1006, 0x0000}, "Stink Bomb (Bottom)"}, + {{1007, 0x0000}, "Grilla Drilla (Bottom)"}, + {{1008, 0x0000}, "Hoot Loop (Bottom)"}, + {{1008, 0x2402}, "Enchanted Hoot Loop (Bottom)"}, + {{1009, 0x0000}, "Trap Shadow (Bottom)"}, + {{1010, 0x0000}, "Magna Charge (Bottom)"}, + {{1010, 0x2402}, "Nitro Magna Charge (Bottom)"}, + {{1011, 0x0000}, "Spy Rise (Bottom)"}, + {{1012, 0x0000}, "Night Shift (Bottom)"}, + {{1012, 0x2403}, "Legendary Night Shift (Bottom)"}, + {{1013, 0x0000}, "Rattle Shake (Bottom)"}, + {{1013, 0x2402}, "Quick Draw Rattle Shake (Bottom)"}, + {{1014, 0x0000}, "Freeze Blade (Bottom)"}, + {{1014, 0x2402}, "Nitro Freeze Blade (Bottom)"}, + {{1015, 0x0000}, "Wash Buckler (Bottom)"}, + {{1015, 0x2402}, "Dark Wash Buckler (Bottom)"}, + {{2000, 0x0000}, "Boom Jet (Top)"}, + {{2001, 0x0000}, "Free Ranger (Top)"}, + {{2001, 0x2403}, "Legendary Free Ranger (Top)"}, + {{2002, 0x0000}, "Rubble Rouser (Top)"}, + {{2003, 0x0000}, "Doom Stone (Top)"}, + {{2004, 0x0000}, "Blast Zone (Top)"}, + {{2004, 0x2402}, "Dark Blast Zone (Top)"}, + {{2005, 0x0000}, "Fire Kraken (Top)"}, + {{2005, 0x2402}, "Jade Fire Kraken (Top)"}, + {{2006, 0x0000}, "Stink Bomb (Top)"}, + {{2007, 0x0000}, "Grilla Drilla (Top)"}, + {{2008, 0x0000}, "Hoot Loop (Top)"}, + {{2008, 0x2402}, "Enchanted Hoot Loop (Top)"}, + {{2009, 0x0000}, "Trap Shadow (Top)"}, + {{2010, 0x0000}, "Magna Charge (Top)"}, + {{2010, 0x2402}, "Nitro Magna Charge (Top)"}, + {{2011, 0x0000}, "Spy Rise (Top)"}, + {{2012, 0x0000}, "Night Shift (Top)"}, + {{2012, 0x2403}, "Legendary Night Shift (Top)"}, + {{2013, 0x0000}, "Rattle Shake (Top)"}, + {{2013, 0x2402}, "Quick Draw Rattle Shake (Top)"}, + {{2014, 0x0000}, "Freeze Blade (Top)"}, + {{2014, 0x2402}, "Nitro Freeze Blade (Top)"}, + {{2015, 0x0000}, "Wash Buckler (Top)"}, + {{2015, 0x2402}, "Dark Wash Buckler (Top)"}, + {{3000, 0x0000}, "Scratch"}, + {{3001, 0x0000}, "Pop Thorn"}, + {{3002, 0x0000}, "Slobber Tooth"}, + {{3002, 0x2402}, "Dark Slobber Tooth"}, + {{3003, 0x0000}, "Scorp"}, + {{3004, 0x0000}, "Fryno"}, + {{3004, 0x3805}, "Hog Wild Fryno"}, + {{3005, 0x0000}, "Smolderdash"}, + {{3005, 0x2206}, "LightCore Smolderdash"}, + {{3006, 0x0000}, "Bumble Blast"}, + {{3006, 0x2402}, "Jolly Bumble Blast"}, + {{3006, 0x2206}, "LightCore Bumble Blast"}, + {{3007, 0x0000}, "Zoo Lou"}, + {{3007, 0x2403}, "Legendary Zoo Lou"}, + {{3008, 0x0000}, "Dune Bug"}, + {{3009, 0x0000}, "Star Strike"}, + {{3009, 0x2602}, "Enchanted Star Strike"}, + {{3009, 0x2206}, "LightCore Star Strike"}, + {{3010, 0x0000}, "Countdown"}, + {{3010, 0x2402}, "Kickoff Countdown"}, + {{3010, 0x2206}, "LightCore Countdown"}, + {{3011, 0x0000}, "Wind Up"}, + {{3011, 0x2404}, "Gear Head VVind Up"}, + {{3012, 0x0000}, "Roller Brawl"}, + {{3013, 0x0000}, "Grim Creeper"}, + {{3013, 0x2603}, "Legendary Grim Creeper"}, + {{3013, 0x2206}, "LightCore Grim Creeper"}, + {{3014, 0x0000}, "Rip Tide"}, + {{3015, 0x0000}, "Punk Shock"}, + {{3200, 0x0000}, "Battle Hammer"}, + {{3201, 0x0000}, "Sky Diamond"}, + {{3202, 0x0000}, "Platinum Sheep"}, + {{3203, 0x0000}, "Groove Machine"}, + {{3204, 0x0000}, "UFO Hat"}, + {{3300, 0x0000}, "Sheep Wreck Island"}, + {{3301, 0x0000}, "Tower of Time"}, + {{3302, 0x0000}, "Fiery Forge"}, + {{3303, 0x0000}, "Arkeyan Crossbow"}, + {{3220, 0x0000}, "Jet Stream"}, + {{3221, 0x0000}, "Tomb Buggy"}, + {{3222, 0x0000}, "Reef Ripper"}, + {{3223, 0x0000}, "Burn Cycle"}, + {{3224, 0x0000}, "Hot Streak"}, + {{3224, 0x4402}, "Dark Hot Streak"}, + {{3224, 0x4004}, "E3 Hot Streak"}, + {{3224, 0x441E}, "Golden Hot Streak"}, + {{3225, 0x0000}, "Shark Tank"}, + {{3226, 0x0000}, "Thump Truck"}, + {{3227, 0x0000}, "Crypt Crusher"}, + {{3228, 0x0000}, "Stealth Stinger"}, + {{3228, 0x4402}, "Nitro Stealth Stinger"}, + {{3231, 0x0000}, "Dive Bomber"}, + {{3231, 0x4402}, "Spring Ahead Dive Bomber"}, + {{3232, 0x0000}, "Sky Slicer"}, + {{3233, 0x0000}, "Clown Cruiser (Nintendo Only)"}, + {{3233, 0x4402}, "Dark Clown Cruiser (Nintendo Only)"}, + {{3234, 0x0000}, "Gold Rusher"}, + {{3234, 0x4402}, "Power Blue Gold Rusher"}, + {{3235, 0x0000}, "Shield Striker"}, + {{3236, 0x0000}, "Sun Runner"}, + {{3236, 0x4403}, "Legendary Sun Runner"}, + {{3237, 0x0000}, "Sea Shadow"}, + {{3237, 0x4402}, "Dark Sea Shadow"}, + {{3238, 0x0000}, "Splatter Splasher"}, + {{3238, 0x4402}, "Power Blue Splatter Splasher"}, + {{3239, 0x0000}, "Soda Skimmer"}, + {{3239, 0x4402}, "Nitro Soda Skimmer"}, + {{3240, 0x0000}, "Barrel Blaster (Nintendo Only)"}, + {{3240, 0x4402}, "Dark Barrel Blaster (Nintendo Only)"}, + {{3241, 0x0000}, "Buzz Wing"}, + {{3400, 0x0000}, "Fiesta"}, + {{3400, 0x4515}, "Frightful Fiesta"}, + {{3401, 0x0000}, "High Volt"}, + {{3402, 0x0000}, "Splat"}, + {{3402, 0x4502}, "Power Blue Splat"}, + {{3406, 0x0000}, "Stormblade"}, + {{3411, 0x0000}, "Smash Hit"}, + {{3411, 0x4502}, "Steel Plated Smash Hit"}, + {{3412, 0x0000}, "Spitfire"}, + {{3412, 0x4502}, "Dark Spitfire"}, + {{3413, 0x0000}, "Hurricane Jet Vac"}, + {{3413, 0x4503}, "Legendary Hurricane Jet Vac"}, + {{3414, 0x0000}, "Double Dare Trigger Happy"}, + {{3414, 0x4502}, "Power Blue Double Dare Trigger Happy"}, + {{3415, 0x0000}, "Super Shot Stealth Elf"}, + {{3415, 0x4502}, "Dark Super Shot Stealth Elf"}, + {{3416, 0x0000}, "Shark Shooter Terrafin"}, + {{3417, 0x0000}, "Bone Bash Roller Brawl"}, + {{3417, 0x4503}, "Legendary Bone Bash Roller Brawl"}, + {{3420, 0x0000}, "Big Bubble Pop Fizz"}, + {{3420, 0x450E}, "Birthday Bash Big Bubble Pop Fizz"}, + {{3421, 0x0000}, "Lava Lance Eruptor"}, + {{3422, 0x0000}, "Deep Dive Gill Grunt"}, + {{3423, 0x0000}, "Turbo Charge Donkey Kong (Nintendo Only)"}, + {{3423, 0x4502}, "Dark Turbo Charge Donkey Kong (Nintendo Only)"}, + {{3424, 0x0000}, "Hammer Slam Bowser (Nintendo Only)"}, + {{3424, 0x4502}, "Dark Hammer Slam Bowser (Nintendo Only)"}, + {{3425, 0x0000}, "Dive-Clops"}, + {{3425, 0x450E}, "Missile-Tow Dive-Clops"}, + {{3426, 0x0000}, "Astroblast"}, + {{3426, 0x4503}, "Legendary Astroblast"}, + {{3427, 0x0000}, "Nightfall"}, + {{3428, 0x0000}, "Thrillipede"}, + {{3428, 0x450D}, "Eggcited Thrillipede"}, + {{3500, 0x0000}, "Sky Trophy"}, + {{3501, 0x0000}, "Land Trophy"}, + {{3502, 0x0000}, "Sea Trophy"}, + {{3503, 0x0000}, "Kaos Trophy"}, + }; + + uint16 SkylanderUSB::SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size) + { + const unsigned short CRC_CCITT_TABLE[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, + 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, + 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, + 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, + 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, + 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, + 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, + 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, + 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, + 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, + 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; + + uint16 crc = initValue; + + for (uint32 i = 0; i < size; i++) + { + const uint16 tmp = (crc >> 8) ^ buffer[i]; + crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp]; + } + + return crc; + } + SkylanderPortalDevice::SkylanderPortalDevice() + : Device(0x1430, 0x0150, 1, 2, 0) + { + m_IsOpened = false; + } + + bool SkylanderPortalDevice::Open() + { + if (!IsOpened()) + { + m_IsOpened = true; + } + return true; + } + + void SkylanderPortalDevice::Close() + { + if (IsOpened()) + { + m_IsOpened = false; + } + } + + bool SkylanderPortalDevice::IsOpened() + { + return m_IsOpened; + } + + Device::ReadResult SkylanderPortalDevice::Read(ReadMessage* message) + { + memcpy(message->data, g_skyportal.GetStatus().data(), message->length); + message->bytesRead = message->length; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return Device::ReadResult::Success; + } + + Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message) + { + message->bytesWritten = message->length; + return Device::WriteResult::Success; + } + + bool SkylanderPortalDevice::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + + bool SkylanderPortalDevice::SetProtocol(uint32 ifIndex, uint32 protocol) + { + return true; + } + + bool SkylanderPortalDevice::SetReport(ReportMessage* message) + { + g_skyportal.ControlTransfer(message->originalData, message->originalLength); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return true; + } + + void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength) + { + std::array<uint8, 64> interruptResponse = {}; + switch (buf[0]) + { + case 'A': + { + interruptResponse = {buf[0], buf[1], 0xFF, 0x77}; + g_skyportal.Activate(); + break; + } + case 'C': + { + g_skyportal.SetLeds(0x01, buf[1], buf[2], buf[3]); + break; + } + case 'J': + { + g_skyportal.SetLeds(buf[1], buf[2], buf[3], buf[4]); + interruptResponse = {buf[0]}; + break; + } + case 'L': + { + uint8 side = buf[1]; + if (side == 0x02) + { + side = 0x04; + } + g_skyportal.SetLeds(side, buf[2], buf[3], buf[4]); + break; + } + case 'M': + { + interruptResponse = {buf[0], buf[1], 0x00, 0x19}; + break; + } + case 'Q': + { + const uint8 skyNum = buf[1] & 0xF; + const uint8 block = buf[2]; + g_skyportal.QueryBlock(skyNum, block, interruptResponse.data()); + break; + } + case 'R': + { + interruptResponse = {buf[0], 0x02, 0x1b}; + break; + } + case 'S': + case 'V': + { + // No response needed + break; + } + case 'W': + { + const uint8 skyNum = buf[1] & 0xF; + const uint8 block = buf[2]; + g_skyportal.WriteBlock(skyNum, block, &buf[3], interruptResponse.data()); + break; + } + default: + cemu_assert_error(); + break; + } + if (interruptResponse[0] != 0) + { + std::lock_guard lock(m_queryMutex); + m_queries.push(interruptResponse); + } + } + + void SkylanderUSB::Activate() + { + std::lock_guard lock(m_skyMutex); + if (m_activated) + { + // If the portal was already active no change is needed + return; + } + + // If not we need to advertise change to all the figures present on the portal + for (auto& s : m_skylanders) + { + if (s.status & 1) + { + s.queuedStatus.push(3); + s.queuedStatus.push(1); + } + } + + m_activated = true; + } + + void SkylanderUSB::Deactivate() + { + std::lock_guard lock(m_skyMutex); + + for (auto& s : m_skylanders) + { + // check if at the end of the updates there would be a figure on the portal + if (!s.queuedStatus.empty()) + { + s.status = s.queuedStatus.back(); + s.queuedStatus = std::queue<uint8>(); + } + + s.status &= 1; + } + + m_activated = false; + } + + void SkylanderUSB::SetLeds(uint8 side, uint8 r, uint8 g, uint8 b) + { + std::lock_guard lock(m_skyMutex); + if (side == 0x00) + { + m_colorRight.red = r; + m_colorRight.green = g; + m_colorRight.blue = b; + } + else if (side == 0x01) + { + m_colorRight.red = r; + m_colorRight.green = g; + m_colorRight.blue = b; + + m_colorLeft.red = r; + m_colorLeft.green = g; + m_colorLeft.blue = b; + } + else if (side == 0x02) + { + m_colorLeft.red = r; + m_colorLeft.green = g; + m_colorLeft.blue = b; + } + else if (side == 0x03) + { + m_colorTrap.red = r; + m_colorTrap.green = g; + m_colorTrap.blue = b; + } + } + + uint8 SkylanderUSB::LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file) + { + std::lock_guard lock(m_skyMutex); + + uint32 skySerial = 0; + for (int i = 3; i > -1; i--) + { + skySerial <<= 8; + skySerial |= buf[i]; + } + uint8 foundSlot = 0xFF; + + // mimics spot retaining on the portal + for (auto i = 0; i < 16; i++) + { + if ((m_skylanders[i].status & 1) == 0) + { + if (m_skylanders[i].lastId == skySerial) + { + foundSlot = i; + break; + } + + if (i < foundSlot) + { + foundSlot = i; + } + } + } + + if (foundSlot != 0xFF) + { + auto& skylander = m_skylanders[foundSlot]; + memcpy(skylander.data.data(), buf, skylander.data.size()); + skylander.skyFile = std::move(file); + skylander.status = Skylander::ADDED; + skylander.queuedStatus.push(Skylander::ADDED); + skylander.queuedStatus.push(Skylander::READY); + skylander.lastId = skySerial; + } + return foundSlot; + } + + bool SkylanderUSB::RemoveSkylander(uint8 skyNum) + { + std::lock_guard lock(m_skyMutex); + auto& thesky = m_skylanders[skyNum]; + + if (thesky.status & 1) + { + thesky.status = 2; + thesky.queuedStatus.push(2); + thesky.queuedStatus.push(0); + thesky.Save(); + thesky.skyFile.reset(); + return true; + } + + return false; + } + + void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf) + { + std::lock_guard lock(m_skyMutex); + + const auto& skylander = m_skylanders[skyNum]; + + replyBuf[0] = 'Q'; + replyBuf[2] = block; + if (skylander.status & 1) + { + replyBuf[1] = (0x10 | skyNum); + memcpy(replyBuf + 3, skylander.data.data() + (16 * block), 16); + } + else + { + replyBuf[1] = skyNum; + } + } + + void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block, + const uint8* toWriteBuf, uint8* replyBuf) + { + std::lock_guard lock(m_skyMutex); + + auto& skylander = m_skylanders[skyNum]; + + replyBuf[0] = 'W'; + replyBuf[2] = block; + + if (skylander.status & 1) + { + replyBuf[1] = (0x10 | skyNum); + memcpy(skylander.data.data() + (block * 16), toWriteBuf, 16); + skylander.Save(); + } + else + { + replyBuf[1] = skyNum; + } + } + + std::array<uint8, 64> SkylanderUSB::GetStatus() + { + std::lock_guard lock(m_queryMutex); + std::array<uint8, 64> interruptResponse = {}; + + if (!m_queries.empty()) + { + interruptResponse = m_queries.front(); + m_queries.pop(); + // This needs to happen after ~22 milliseconds + } + else + { + uint32 status = 0; + uint8 active = 0x00; + if (m_activated) + { + active = 0x01; + } + + for (int i = 16 - 1; i >= 0; i--) + { + auto& s = m_skylanders[i]; + + if (!s.queuedStatus.empty()) + { + s.status = s.queuedStatus.front(); + s.queuedStatus.pop(); + } + status <<= 2; + status |= s.status; + } + interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++, + active, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; + memcpy(&interruptResponse[1], &status, sizeof(status)); + } + return interruptResponse; + } + + void SkylanderUSB::Skylander::Save() + { + if (!skyFile) + return; + + skyFile->writeData(data.data(), data.size()); + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h new file mode 100644 index 0000000..dfa370d --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -0,0 +1,98 @@ +#include <mutex> + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class SkylanderPortalDevice final : public Device { + public: + SkylanderPortalDevice(); + ~SkylanderPortalDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + extern const std::map<const std::pair<const uint16, const uint16>, const std::string> listSkylanders; + + class SkylanderUSB { + public: + struct Skylander final + { + std::unique_ptr<FileStream> skyFile; + uint8 status = 0; + std::queue<uint8> queuedStatus; + std::array<uint8, 0x40 * 0x10> data{}; + uint32 lastId = 0; + void Save(); + + enum : uint8 + { + REMOVED = 0, + READY = 1, + REMOVING = 2, + ADDED = 3 + }; + }; + + struct SkylanderLEDColor final + { + uint8 red = 0; + uint8 green = 0; + uint8 blue = 0; + }; + + void ControlTransfer(uint8* buf, sint32 originalLength); + + void Activate(); + void Deactivate(); + void SetLeds(uint8 side, uint8 r, uint8 g, uint8 b); + + std::array<uint8, 64> GetStatus(); + void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf); + void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, + uint8* replyBuf); + + uint8 LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file); + bool RemoveSkylander(uint8 skyNum); + uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size); + + protected: + std::mutex m_skyMutex; + std::mutex m_queryMutex; + std::array<Skylander, 16> m_skylanders; + + private: + std::queue<std::array<uint8, 64>> m_queries; + bool m_activated = true; + uint8 m_interruptCounter = 0; + SkylanderLEDColor m_colorRight = {}; + SkylanderLEDColor m_colorLeft = {}; + SkylanderLEDColor m_colorTrap = {}; + + }; + extern SkylanderUSB g_skyportal; +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index ff5c4f4..c674b84 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -256,6 +256,19 @@ namespace nsyshid device->m_productId); } + bool FindDeviceById(uint16 vendorId, uint16 productId) + { + std::lock_guard<std::recursive_mutex> lock(hidMutex); + for (const auto& device : deviceList) + { + if (device->m_vendorId == vendorId && device->m_productId == productId) + { + return true; + } + } + return false; + } + void export_HIDAddClient(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); @@ -406,7 +419,8 @@ namespace nsyshid sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); - if (device->SetReport(reportData, length, originalData, originalLength)) + ReportMessage message(reportData, length, originalData, originalLength); + if (device->SetReport(&message)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, @@ -433,7 +447,8 @@ namespace nsyshid { _debugPrintHex("_hidSetReportSync Begin", reportData, length); sint32 returnCode = 0; - if (device->SetReport(reportData, length, originalData, originalLength)) + ReportMessage message(reportData, length, originalData, originalLength); + if (device->SetReport(&message)) { returnCode = originalLength; } @@ -511,17 +526,16 @@ namespace nsyshid return -1; } memset(data, 0, maxLength); - - sint32 bytesRead = 0; - Device::ReadResult readResult = device->Read(data, maxLength, bytesRead); + ReadMessage message(data, maxLength, 0); + Device::ReadResult readResult = device->Read(&message); switch (readResult) { case Device::ReadResult::Success: { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", - bytesRead, + message.bytesRead, maxLength); - return bytesRead; + return message.bytesRead; } break; case Device::ReadResult::Error: @@ -609,15 +623,15 @@ namespace nsyshid cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); return -1; } - sint32 bytesWritten = 0; - Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten); + WriteMessage message(data, maxLength, 0); + Device::WriteResult writeResult = device->Write(&message); switch (writeResult) { case Device::WriteResult::Success: { - cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", bytesWritten, + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", message.bytesWritten, maxLength); - return bytesWritten; + return message.bytesWritten; } break; case Device::WriteResult::Error: @@ -758,6 +772,11 @@ namespace nsyshid return nullptr; } + bool Backend::FindDeviceById(uint16 vendorId, uint16 productId) + { + return nsyshid::FindDeviceById(vendorId, productId); + } + bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) { return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 4f1736e..8e7cf39 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -358,6 +358,10 @@ void CemuConfig::Load(XMLConfigParser& parser) auto dsuc = input.get("DSUC"); dsu_client.host = dsuc.get_attribute("host", dsu_client.host); dsu_client.port = dsuc.get_attribute("port", dsu_client.port); + + // emulatedusbdevices + auto usbdevices = parser.get("EmulatedUsbDevices"); + emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); } void CemuConfig::Save(XMLConfigParser& parser) @@ -551,6 +555,10 @@ void CemuConfig::Save(XMLConfigParser& parser) auto dsuc = input.set("DSUC"); dsuc.set_attribute("host", dsu_client.host); dsuc.set_attribute("port", dsu_client.port); + + // emulated usb devices + auto usbdevices = config.set("EmulatedUsbDevices"); + usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index cab7a1a..d0776d2 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -514,6 +514,12 @@ struct CemuConfig NetworkService GetAccountNetworkService(uint32 persistentId); void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); + + // emulated usb devices + struct + { + ConfigValue<bool> emulate_skylander_portal{false}; + }emulated_usb_devices{}; private: GameEntry* GetGameEntryByTitleId(uint64 titleId); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 19ce95d..02f96a9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -101,6 +101,8 @@ add_library(CemuGui PairingDialog.h TitleManager.cpp TitleManager.h + EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp + EmulatedUSBDevices/EmulatedUSBDeviceFrame.h windows/PPCThreadsViewer windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp windows/PPCThreadsViewer/DebugPPCThreadsWindow.h diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp new file mode 100644 index 0000000..58c1823 --- /dev/null +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -0,0 +1,354 @@ +#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" + +#include <algorithm> +#include <random> + +#include "config/CemuConfig.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/wxHelper.h" +#include "util/helpers/helpers.h" + +#include "Cafe/OS/libs/nsyshid/nsyshid.h" +#include "Cafe/OS/libs/nsyshid/Skylander.h" + +#include "Common/FileStream.h" + +#include <wx/arrstr.h> +#include <wx/button.h> +#include <wx/checkbox.h> +#include <wx/combobox.h> +#include <wx/filedlg.h> +#include <wx/msgdlg.h> +#include <wx/notebook.h> +#include <wx/panel.h> +#include <wx/sizer.h> +#include <wx/statbox.h> +#include <wx/stattext.h> +#include <wx/stream.h> +#include <wx/textctrl.h> +#include <wx/textentry.h> +#include <wx/valnum.h> +#include <wx/wfstream.h> + +#include "resource/embedded/resources.h" +#include "EmulatedUSBDeviceFrame.h" + +EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) + : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) +{ + SetIcon(wxICON(X_BOX)); + + auto& config = GetConfig(); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + auto* notebook = new wxNotebook(this, wxID_ANY); + + notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); + + sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); + + SetSizerAndFit(sizer); + Layout(); + Centre(wxBOTH); +} + +EmulatedUSBDeviceFrame::~EmulatedUSBDeviceFrame() {} + +wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panelSizer = new wxBoxSizer(wxVERTICAL); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Skylanders Manager")); + auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulatePortal = + new wxCheckBox(box, wxID_ANY, _("Emulate Skylander Portal")); + m_emulatePortal->SetValue( + GetConfig().emulated_usb_devices.emulate_skylander_portal); + m_emulatePortal->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_skylander_portal = + m_emulatePortal->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); + for (int i = 0; i < 16; i++) + { + boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2); + } + panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panelSizer); + + return panel; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 row_number, + wxStaticBox* box) +{ + auto* row = new wxBoxSizer(wxHORIZONTAL); + + row->Add(new wxStaticText(box, wxID_ANY, + fmt::format("{} {}", _("Skylander").ToStdString(), + (row_number + 1))), + 1, wxEXPAND | wxALL, 2); + m_skylanderSlots[row_number] = + new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + m_skylanderSlots[row_number]->SetMinSize(wxSize(150, -1)); + m_skylanderSlots[row_number]->Disable(); + row->Add(m_skylanderSlots[row_number], 1, wxEXPAND | wxALL, 2); + auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); + loadButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { + LoadSkylander(row_number); + }); + auto* createButton = new wxButton(box, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { + CreateSkylander(row_number); + }); + auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); + clearButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { + ClearSkylander(row_number); + }); + row->Add(loadButton, 1, wxEXPAND | wxALL, 2); + row->Add(createButton, 1, wxEXPAND | wxALL, 2); + row->Add(clearButton, 1, wxEXPAND | wxALL, 2); + + return row; +} + +void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", + "Skylander files (*.sky;*.bin;*.dump;*.dmp)|*.sky;*.bin;*.dump;*.dmp", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + return; + + LoadSkylanderPath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) +{ + std::unique_ptr<FileStream> skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!skyFile) + { + wxMessageDialog open_error(this, "Error Opening File: " + path.c_str()); + open_error.ShowModal(); + return; + } + + std::array<uint8, 0x40 * 0x10> fileData; + if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearSkylander(slot); + + uint16 skyId = uint16(fileData[0x11]) << 8 | uint16(fileData[0x10]); + uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]); + + uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(), + std::move(skyFile)); + m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar); + UpdateSkylanderEdits(); +} + +void EmulatedUSBDeviceFrame::CreateSkylander(uint8 slot) +{ + CreateSkylanderDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadSkylanderPath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearSkylander(uint8 slot) +{ + if (auto slotInfos = m_skySlots[slot]) + { + auto [curSlot, id, var] = slotInfos.value(); + nsyshid::g_skyportal.RemoveSkylander(curSlot); + m_skySlots[slot] = {}; + UpdateSkylanderEdits(); + } +} + +CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) + : wxDialog(parent, wxID_ANY, _("Skylander Figure Creator"), wxDefaultPosition, wxSize(500, 150)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF)); + wxArrayString filterlist; + for (auto it = nsyshid::listSkylanders.begin(); it != nsyshid::listSkylanders.end(); it++) + { + const uint32 variant = uint32(uint32(it->first.first) << 16) | uint32(it->first.second); + comboBox->Append(it->second, reinterpret_cast<void*>(variant)); + filterlist.Add(it->second); + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* idVarRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator<uint32> validator; + + auto* labelId = new wxStaticText(this, wxID_ANY, "ID:"); + auto* labelVar = new wxStaticText(this, wxID_ANY, "Variant:"); + auto* editId = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + auto* editVar = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + idVarRow->Add(labelId, 1, wxALL, 5); + idVarRow->Add(editId, 1, wxALL, 5); + idVarRow->Add(labelVar, 1, wxALL, 5); + idVarRow->Add(editVar, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editId, editVar, this](wxCommandEvent&) { + long longSkyId; + if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF) + { + wxMessageDialog id_error(this, "Error Converting ID!", "ID Entered is Invalid"); + id_error.ShowModal(); + return; + } + long longSkyVar; + if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF) + { + wxMessageDialog id_error(this, "Error Converting Variant!", "Variant Entered is Invalid"); + id_error.ShowModal(); + return; + } + uint16 skyId = longSkyId & 0xFFFF; + uint16 skyVar = longSkyVar & 0xFFFF; + const auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); + wxString predefName; + if (foundSky != nsyshid::listSkylanders.end()) + { + predefName = foundSky->second + ".sky"; + } + else + { + predefName = wxString::Format(_("Unknown(%i %i).sky"), skyId, skyVar); + } + wxFileDialog + saveFileDialog(this, _("Create Skylander file"), "", predefName, + "SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + return; + + m_filePath = saveFileDialog.GetPath(); + + wxFileOutputStream output_stream(saveFileDialog.GetPath()); + if (!output_stream.IsOk()) + { + wxMessageDialog saveError(this, "Error Creating Skylander File"); + return; + } + + std::array<uint8, 0x40 * 0x10> data{}; + + uint32 first_block = 0x690F0F0F; + uint32 other_blocks = 0x69080F7F; + memcpy(&data[0x36], &first_block, sizeof(first_block)); + for (size_t index = 1; index < 0x10; index++) + { + memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); + } + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<int> dist(0, 255); + data[0] = dist(mt); + data[1] = dist(mt); + data[2] = dist(mt); + data[3] = dist(mt); + data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; + data[5] = 0x81; + data[6] = 0x01; + data[7] = 0x0F; + + memcpy(&data[0x10], &skyId, sizeof(skyId)); + memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); + + uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); + + memcpy(&data[0x1E], &crc, sizeof(crc)); + + output_stream.SeekO(0); + output_stream.WriteAll(data.data(), data.size()); + output_stream.Close(); + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editId, editVar, this](wxCommandEvent&) { + const uint64 sky_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection())); + if (sky_info != 0xFFFFFFFF) + { + const uint16 skyId = sky_info >> 16; + const uint16 skyVar = sky_info & 0xFFFF; + + editId->SetValue(wxString::Format(wxT("%i"), skyId)); + editVar->SetValue(wxString::Format(wxT("%i"), skyVar)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(idVarRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateSkylanderDialog::GetFilePath() const +{ + return m_filePath; +} + +void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() +{ + for (auto i = 0; i < 16; i++) + { + std::string displayString; + if (auto sd = m_skySlots[i]) + { + auto [portalSlot, skyId, skyVar] = sd.value(); + auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); + if (foundSky != nsyshid::listSkylanders.end()) + { + displayString = foundSky->second; + } + else + { + displayString = fmt::format("Unknown (Id:{} Var:{})", skyId, skyVar); + } + } + else + { + displayString = "None"; + } + + m_skylanderSlots[i]->ChangeValue(displayString); + } +} \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h new file mode 100644 index 0000000..6acb7da --- /dev/null +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -0,0 +1,42 @@ +#pragma once + +#include <array> + +#include <wx/dialog.h> +#include <wx/frame.h> + +class wxBoxSizer; +class wxCheckBox; +class wxFlexGridSizer; +class wxNotebook; +class wxPanel; +class wxStaticBox; +class wxString; +class wxTextCtrl; + +class EmulatedUSBDeviceFrame : public wxFrame { + public: + EmulatedUSBDeviceFrame(wxWindow* parent); + ~EmulatedUSBDeviceFrame(); + + private: + wxCheckBox* m_emulatePortal; + std::array<wxTextCtrl*, 16> m_skylanderSlots; + std::array<std::optional<std::tuple<uint8, uint16, uint16>>, 16> m_skySlots; + + wxPanel* AddSkylanderPage(wxNotebook* notebook); + wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); + void LoadSkylander(uint8 slot); + void LoadSkylanderPath(uint8 slot, wxString path); + void CreateSkylander(uint8 slot); + void ClearSkylander(uint8 slot); + void UpdateSkylanderEdits(); +}; +class CreateSkylanderDialog : public wxDialog { + public: + explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); + wxString GetFilePath() const; + + protected: + wxString m_filePath; +}; \ No newline at end of file diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 03c69a7..7a4f317 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -30,6 +30,7 @@ #include "Cafe/Filesystem/FST/FST.h" #include "gui/TitleManager.h" +#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include "Cafe/CafeSystem.h" @@ -110,6 +111,7 @@ enum MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600, MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, + MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, // cpu // cpu->timer speed MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700, @@ -188,6 +190,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput) +EVT_MENU(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, MainWindow::OnToolsInput) // cpu menu EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting) @@ -1515,6 +1518,29 @@ void MainWindow::OnToolsInput(wxCommandEvent& event) }); m_title_manager->Show(); } + break; + } + case MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES: + { + if (m_usb_devices) + { + m_usb_devices->Show(true); + m_usb_devices->Raise(); + m_usb_devices->SetFocus(); + } + else + { + m_usb_devices = new EmulatedUSBDeviceFrame(this); + m_usb_devices->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) + { + if (event.CanVeto()) { + m_usb_devices->Show(false); + event.Veto(); + } + }); + m_usb_devices->Show(true); + } + break; } break; } @@ -2166,6 +2192,7 @@ void MainWindow::RecreateMenu() m_memorySearcherMenuItem->Enable(false); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); + toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, _("&Emulated USB Devices")); m_menuBar->Append(toolsMenu, _("&Tools")); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 7191df1..dd4d0d0 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -22,6 +22,7 @@ struct GameEntry; class DiscordPresence; class TitleManager; class GraphicPacksWindow2; +class EmulatedUSBDeviceFrame; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); @@ -164,6 +165,7 @@ private: MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; + EmulatedUSBDeviceFrame* m_usb_devices = nullptr; PadViewFrame* m_padView = nullptr; GraphicPacksWindow2* m_graphic_pack_window = nullptr; From aefbb918beb8718af8f190a73018ff63bf801d95 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Fri, 28 Jun 2024 14:44:49 +0100 Subject: [PATCH 127/130] nsyshid: Skylander emulation fixes and code cleanup (#1244) --- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 79 +++++++++++++++++-- src/Cafe/OS/libs/nsyshid/Skylander.h | 17 ++-- .../EmulatedUSBDeviceFrame.cpp | 78 ++++-------------- .../EmulatedUSBDeviceFrame.h | 6 +- 4 files changed, 101 insertions(+), 79 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 3123d14..241e196 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -1,5 +1,7 @@ #include "Skylander.h" +#include <random> + #include "nsyshid.h" #include "Backend.h" @@ -9,8 +11,8 @@ namespace nsyshid { SkylanderUSB g_skyportal; - const std::map<const std::pair<const uint16, const uint16>, const std::string> - listSkylanders = { + const std::map<const std::pair<const uint16, const uint16>, const char*> + s_listSkylanders = { {{0, 0x0000}, "Whirlwind"}, {{0, 0x1801}, "Series 2 Whirlwind"}, {{0, 0x1C02}, "Polar Whirlwind"}, @@ -845,6 +847,49 @@ namespace nsyshid return false; } + bool SkylanderUSB::CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar) + { + FileStream* skyFile(FileStream::createFile2(pathName)); + if (!skyFile) + { + return false; + } + + std::array<uint8, BLOCK_COUNT * BLOCK_SIZE> data{}; + + uint32 first_block = 0x690F0F0F; + uint32 other_blocks = 0x69080F7F; + memcpy(&data[0x36], &first_block, sizeof(first_block)); + for (size_t index = 1; index < 0x10; index++) + { + memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); + } + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<int> dist(0, 255); + data[0] = dist(mt); + data[1] = dist(mt); + data[2] = dist(mt); + data[3] = dist(mt); + data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; + data[5] = 0x81; + data[6] = 0x01; + data[7] = 0x0F; + + memcpy(&data[0x10], &skyId, sizeof(skyId)); + memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); + + uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); + + memcpy(&data[0x1E], &crc, sizeof(crc)); + + skyFile->writeData(data.data(), data.size()); + + delete skyFile; + + return true; + } + void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf) { std::lock_guard lock(m_skyMutex); @@ -865,7 +910,7 @@ namespace nsyshid } void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block, - const uint8* toWriteBuf, uint8* replyBuf) + const uint8* toWriteBuf, uint8* replyBuf) { std::lock_guard lock(m_skyMutex); @@ -919,21 +964,39 @@ namespace nsyshid status |= s.status; } interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++, - active, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00}; + active, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; memcpy(&interruptResponse[1], &status, sizeof(status)); } return interruptResponse; } + std::string SkylanderUSB::FindSkylander(uint16 skyId, uint16 skyVar) + { + for (const auto& it : GetListSkylanders()) + { + if(it.first.first == skyId && it.first.second == skyVar) + { + return it.second; + } + } + return fmt::format("Unknown ({} {})", skyId, skyVar); + } + + std::map<const std::pair<const uint16, const uint16>, const char*> SkylanderUSB::GetListSkylanders() + { + return s_listSkylanders; + } + void SkylanderUSB::Skylander::Save() { if (!skyFile) return; + skyFile->SetPosition(0); skyFile->writeData(data.data(), data.size()); } } // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index dfa370d..a1ca7f8 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -1,3 +1,5 @@ +#pragma once + #include <mutex> #include "nsyshid.h" @@ -36,7 +38,10 @@ namespace nsyshid bool m_IsOpened; }; - extern const std::map<const std::pair<const uint16, const uint16>, const std::string> listSkylanders; + constexpr uint16 BLOCK_COUNT = 0x40; + constexpr uint16 BLOCK_SIZE = 0x10; + constexpr uint16 FIGURE_SIZE = BLOCK_COUNT * BLOCK_SIZE; + constexpr uint8 MAX_SKYLANDERS = 16; class SkylanderUSB { public: @@ -45,7 +50,7 @@ namespace nsyshid std::unique_ptr<FileStream> skyFile; uint8 status = 0; std::queue<uint8> queuedStatus; - std::array<uint8, 0x40 * 0x10> data{}; + std::array<uint8, BLOCK_COUNT * BLOCK_SIZE> data{}; uint32 lastId = 0; void Save(); @@ -74,16 +79,19 @@ namespace nsyshid std::array<uint8, 64> GetStatus(); void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf); void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, - uint8* replyBuf); + uint8* replyBuf); uint8 LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file); bool RemoveSkylander(uint8 skyNum); + bool CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar); uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size); + static std::map<const std::pair<const uint16, const uint16>, const char*> GetListSkylanders(); + std::string FindSkylander(uint16 skyId, uint16 skyVar); protected: std::mutex m_skyMutex; std::mutex m_queryMutex; - std::array<Skylander, 16> m_skylanders; + std::array<Skylander, MAX_SKYLANDERS> m_skylanders; private: std::queue<std::array<uint8, 64>> m_queries; @@ -92,7 +100,6 @@ namespace nsyshid SkylanderLEDColor m_colorRight = {}; SkylanderLEDColor m_colorLeft = {}; SkylanderLEDColor m_colorTrap = {}; - }; extern SkylanderUSB g_skyportal; } // namespace nsyshid \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index 58c1823..f43c369 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -1,7 +1,6 @@ #include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include <algorithm> -#include <random> #include "config/CemuConfig.h" #include "gui/helpers/wxHelpers.h" @@ -9,7 +8,6 @@ #include "util/helpers/helpers.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h" -#include "Cafe/OS/libs/nsyshid/Skylander.h" #include "Common/FileStream.h" @@ -75,7 +73,7 @@ wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) }); row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2); boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); - for (int i = 0; i < 16; i++) + for (int i = 0; i < nsyshid::MAX_SKYLANDERS; i++) { boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2); } @@ -153,7 +151,7 @@ void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]); uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(), - std::move(skyFile)); + std::move(skyFile)); m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar); UpdateSkylanderEdits(); } @@ -189,11 +187,11 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) auto* comboBox = new wxComboBox(this, wxID_ANY); comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF)); wxArrayString filterlist; - for (auto it = nsyshid::listSkylanders.begin(); it != nsyshid::listSkylanders.end(); it++) + for (const auto& it : nsyshid::g_skyportal.GetListSkylanders()) { - const uint32 variant = uint32(uint32(it->first.first) << 16) | uint32(it->first.second); - comboBox->Append(it->second, reinterpret_cast<void*>(variant)); - filterlist.Add(it->second); + const uint32 variant = uint32(uint32(it.first.first) << 16) | uint32(it.first.second); + comboBox->Append(it.second, reinterpret_cast<void*>(variant)); + filterlist.Add(it.second); } comboBox->SetSelection(0); bool enabled = comboBox->AutoComplete(filterlist); @@ -233,16 +231,7 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) } uint16 skyId = longSkyId & 0xFFFF; uint16 skyVar = longSkyVar & 0xFFFF; - const auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); - wxString predefName; - if (foundSky != nsyshid::listSkylanders.end()) - { - predefName = foundSky->second + ".sky"; - } - else - { - predefName = wxString::Format(_("Unknown(%i %i).sky"), skyId, skyVar); - } + wxString predefName = nsyshid::g_skyportal.FindSkylander(skyId, skyVar) + ".sky"; wxFileDialog saveFileDialog(this, _("Create Skylander file"), "", predefName, "SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); @@ -251,46 +240,15 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) return; m_filePath = saveFileDialog.GetPath(); - - wxFileOutputStream output_stream(saveFileDialog.GetPath()); - if (!output_stream.IsOk()) + + if(!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) { - wxMessageDialog saveError(this, "Error Creating Skylander File"); + wxMessageDialog errorMessage(this, "Failed to create file"); + errorMessage.ShowModal(); + this->EndModal(0); return; } - std::array<uint8, 0x40 * 0x10> data{}; - - uint32 first_block = 0x690F0F0F; - uint32 other_blocks = 0x69080F7F; - memcpy(&data[0x36], &first_block, sizeof(first_block)); - for (size_t index = 1; index < 0x10; index++) - { - memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); - } - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_int_distribution<int> dist(0, 255); - data[0] = dist(mt); - data[1] = dist(mt); - data[2] = dist(mt); - data[3] = dist(mt); - data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; - data[5] = 0x81; - data[6] = 0x01; - data[7] = 0x0F; - - memcpy(&data[0x10], &skyId, sizeof(skyId)); - memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); - - uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); - - memcpy(&data[0x1E], &crc, sizeof(crc)); - - output_stream.SeekO(0); - output_stream.WriteAll(data.data(), data.size()); - output_stream.Close(); - this->EndModal(1); }); auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); @@ -328,21 +286,13 @@ wxString CreateSkylanderDialog::GetFilePath() const void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() { - for (auto i = 0; i < 16; i++) + for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) { std::string displayString; if (auto sd = m_skySlots[i]) { auto [portalSlot, skyId, skyVar] = sd.value(); - auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); - if (foundSky != nsyshid::listSkylanders.end()) - { - displayString = foundSky->second; - } - else - { - displayString = fmt::format("Unknown (Id:{} Var:{})", skyId, skyVar); - } + displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); } else { diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h index 6acb7da..8988cb8 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -5,6 +5,8 @@ #include <wx/dialog.h> #include <wx/frame.h> +#include "Cafe/OS/libs/nsyshid/Skylander.h" + class wxBoxSizer; class wxCheckBox; class wxFlexGridSizer; @@ -21,8 +23,8 @@ class EmulatedUSBDeviceFrame : public wxFrame { private: wxCheckBox* m_emulatePortal; - std::array<wxTextCtrl*, 16> m_skylanderSlots; - std::array<std::optional<std::tuple<uint8, uint16, uint16>>, 16> m_skySlots; + std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots; + std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots; wxPanel* AddSkylanderPage(wxNotebook* notebook); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); From 64b0b85ed5fe15d17cfa6b5fce0044000b013a07 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Sat, 29 Jun 2024 21:31:47 +0100 Subject: [PATCH 128/130] Create GamePad window at correct size (#1247) Don't change the size on canvas initialization --- src/gui/PadViewFrame.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index f2da2ca..e7cc5c1 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -20,18 +20,24 @@ extern WindowInfo g_window_info; +#define PAD_MIN_WIDTH 320 +#define PAD_MIN_HEIGHT 180 + PadViewFrame::PadViewFrame(wxFrame* parent) - : wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxSize(854, 480), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS) + : wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxDefaultSize, wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS) { gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_pad, this); - + SetIcon(wxICON(M_WND_ICON128)); wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); - SetMinClientSize({ 320, 180 }); + SetMinClientSize({ PAD_MIN_WIDTH, PAD_MIN_HEIGHT }); SetPosition({ g_window_info.restored_pad_x, g_window_info.restored_pad_y }); - SetSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); + if (g_window_info.restored_pad_width >= PAD_MIN_WIDTH && g_window_info.restored_pad_height >= PAD_MIN_HEIGHT) + SetClientSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); + else + SetClientSize(wxSize(854, 480)); if (g_window_info.pad_maximized) Maximize(); @@ -72,7 +78,7 @@ void PadViewFrame::InitializeRenderCanvas() m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); } - SetSizerAndFit(sizer); + SetSizer(sizer); Layout(); m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this); From 5209677f2fd661949778c071287f5005b57bc8fe Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Tue, 2 Jul 2024 02:32:37 +0100 Subject: [PATCH 129/130] nsyshid: Add SetProtocol and SetReport support for libusb backend (#1243) --- src/Cafe/OS/libs/nsyshid/Backend.h | 2 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 161 +++++++++++++----- src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 15 +- .../OS/libs/nsyshid/BackendWindowsHID.cpp | 2 +- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.h | 2 +- src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 4 +- 8 files changed, 137 insertions(+), 53 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 0323273..1236277 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -135,7 +135,7 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) = 0; - virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; + virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0; virtual bool SetReport(ReportMessage* message) = 0; }; diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 6701d78..7548c99 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -230,6 +230,17 @@ namespace nsyshid::backend::libusb return nullptr; } + std::pair<int, ConfigDescriptor> MakeConfigDescriptor(libusb_device* device, uint8 config_num) + { + libusb_config_descriptor* descriptor = nullptr; + const int ret = libusb_get_config_descriptor(device, config_num, &descriptor); + if (ret == LIBUSB_SUCCESS) + return {ret, ConfigDescriptor{descriptor, libusb_free_config_descriptor}}; + + return {ret, ConfigDescriptor{nullptr, [](auto) { + }}}; + } + std::shared_ptr<Device> BackendLibusb::CheckAndCreateDevice(libusb_device* dev) { struct libusb_device_descriptor desc; @@ -241,6 +252,25 @@ namespace nsyshid::backend::libusb ret); return nullptr; } + std::vector<ConfigDescriptor> config_descriptors{}; + for (uint8 i = 0; i < desc.bNumConfigurations; ++i) + { + auto [ret, config_descriptor] = MakeConfigDescriptor(dev, i); + if (ret != LIBUSB_SUCCESS || !config_descriptor) + { + cemuLog_log(LogType::Force, "Failed to make config descriptor {} for {:04x}:{:04x}: {}", + i, desc.idVendor, desc.idProduct, libusb_error_name(ret)); + } + else + { + config_descriptors.emplace_back(std::move(config_descriptor)); + } + } + if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); + } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, desc.idProduct, @@ -248,7 +278,8 @@ namespace nsyshid::backend::libusb 2, 0, libusb_get_bus_number(dev), - libusb_get_device_address(dev)); + libusb_get_device_address(dev), + std::move(config_descriptors)); // figure out device endpoints if (!FindDefaultDeviceEndpoints(dev, device->m_libusbHasEndpointIn, @@ -330,7 +361,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress) + uint8 libusbDeviceAddress, + std::vector<ConfigDescriptor> configs) : Device(vendorId, productId, interfaceIndex, @@ -346,6 +378,7 @@ namespace nsyshid::backend::libusb m_libusbHasEndpointOut(false), m_libusbEndpointOut(0) { + m_config_descriptors = std::move(configs); } DeviceLibusb::~DeviceLibusb() @@ -413,20 +446,8 @@ namespace nsyshid::backend::libusb } this->m_handleInUseCounter = 0; } - if (libusb_kernel_driver_active(this->m_libusbHandle, 0) == 1) { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver active"); - if (libusb_detach_kernel_driver(this->m_libusbHandle, 0) == 0) - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver detached"); - } - else - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to detach kernel driver"); - } - } - { - int ret = libusb_claim_interface(this->m_libusbHandle, 0); + int ret = ClaimAllInterfaces(0); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); @@ -680,7 +701,65 @@ namespace nsyshid::backend::libusb return false; } - bool DeviceLibusb::SetProtocol(uint32 ifIndex, uint32 protocol) + template<typename Configs, typename Function> + static int DoForEachInterface(const Configs& configs, uint8 config_num, Function action) + { + int ret = LIBUSB_ERROR_NOT_FOUND; + if (configs.size() <= config_num || !configs[config_num]) + return ret; + for (uint8 i = 0; i < configs[config_num]->bNumInterfaces; ++i) + { + ret = action(i); + if (ret < LIBUSB_SUCCESS) + break; + } + return ret; + } + + int DeviceLibusb::ClaimAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + if (libusb_kernel_driver_active(this->m_libusbHandle, i)) + { + const int ret2 = libusb_detach_kernel_driver(this->m_libusbHandle, i); + if (ret2 < LIBUSB_SUCCESS && ret2 != LIBUSB_ERROR_NOT_FOUND && + ret2 != LIBUSB_ERROR_NOT_SUPPORTED) + { + cemuLog_log(LogType::Force, "Failed to detach kernel driver {}", libusb_error_name(ret2)); + return ret2; + } + } + return libusb_claim_interface(this->m_libusbHandle, i); + }); + if (ret < LIBUSB_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + return libusb_release_interface(AquireHandleLock()->GetHandle(), i); + }); + if (ret < LIBUSB_SUCCESS && ret != LIBUSB_ERROR_NO_DEVICE && ret != LIBUSB_ERROR_NOT_FOUND) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfacesForCurrentConfig() + { + int config_num; + const int get_config_ret = libusb_get_configuration(AquireHandleLock()->GetHandle(), &config_num); + if (get_config_ret < LIBUSB_SUCCESS) + return get_config_ret; + return ReleaseAllInterfaces(config_num); + } + + bool DeviceLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -688,24 +767,18 @@ namespace nsyshid::backend::libusb cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); return false; } + if (m_interfaceIndex != ifIndex) + m_interfaceIndex = ifIndex; - // ToDo: implement this -#if 0 - // is this correct? Discarding "ifIndex" seems like a bad idea - int ret = libusb_set_configuration(handleLock->getHandle(), protocol); - if (ret == 0) { - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): success"); + ReleaseAllInterfacesForCurrentConfig(); + int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol); + if (ret == LIBUSB_SUCCESS) + ret = ClaimAllInterfaces(protocol); + + if (ret == LIBUSB_SUCCESS) return true; - } - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): failed with error code: {}", - ret); - return false; -#endif - // pretend that everything is fine - return true; + return false; } bool DeviceLibusb::SetReport(ReportMessage* message) @@ -717,20 +790,20 @@ namespace nsyshid::backend::libusb return false; } - // ToDo: implement this -#if 0 - // not sure if libusb_control_transfer() is the right candidate for this - int ret = libusb_control_transfer(handleLock->getHandle(), - bmRequestType, - bRequest, - wValue, - wIndex, - message->reportData, - message->length, - timeout); -#endif + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + LIBUSB_REQUEST_SET_CONFIGURATION, + 512, + 0, + message->originalData, + message->originalLength, + 0); - // pretend that everything is fine + if (ret != message->originalLength) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } return true; } diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index a8122af..a7b2376 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -44,6 +44,11 @@ namespace nsyshid::backend::libusb bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); }; + template<typename T> + using UniquePtr = std::unique_ptr<T, void (*)(T*)>; + + using ConfigDescriptor = UniquePtr<libusb_config_descriptor>; + class DeviceLibusb : public nsyshid::Device { public: DeviceLibusb(libusb_context* ctx, @@ -53,7 +58,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress); + uint8 libusbDeviceAddress, + std::vector<ConfigDescriptor> configs); ~DeviceLibusb() override; @@ -73,7 +79,11 @@ namespace nsyshid::backend::libusb uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + int ClaimAllInterfaces(uint8 config_num); + int ReleaseAllInterfaces(uint8 config_num); + int ReleaseAllInterfacesForCurrentConfig(); bool SetReport(ReportMessage* message) override; @@ -92,6 +102,7 @@ namespace nsyshid::backend::libusb std::atomic<sint32> m_handleInUseCounter; std::condition_variable m_handleInUseCounterDecremented; libusb_device_handle* m_libusbHandle; + std::vector<ConfigDescriptor> m_config_descriptors; class HandleLock { public: diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 3cfba26..44e0139 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -400,7 +400,7 @@ namespace nsyshid::backend::windows return false; } - bool DeviceWindowsHID::SetProtocol(uint32 ifIndef, uint32 protocol) + bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol) { // ToDo: implement this // pretend that everything is fine diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h index 84fe7bd..9a8a78e 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -47,7 +47,7 @@ namespace nsyshid::backend::windows bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndef, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 241e196..7f17f8a 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -627,7 +627,7 @@ namespace nsyshid return true; } - bool SkylanderPortalDevice::SetProtocol(uint32 ifIndex, uint32 protocol) + bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; } diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index a1ca7f8..ae8b5d9 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -30,7 +30,7 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index c674b84..99a736d 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -379,8 +379,8 @@ namespace nsyshid void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamU8(ifIndex, 1); // r4 + ppcDefineParamU8(protocol, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)"); From 9d366937cd1c5908e073ecd726a0b12763838ef7 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 7 Jul 2024 08:55:26 +0200 Subject: [PATCH 130/130] Workaround for compiler issue with Visual Studio 17.10 --- src/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5843c3..7d64d91 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,12 @@ add_executable(CemuBin mainLLE.cpp ) +if(MSVC AND MSVC_VERSION EQUAL 1940) + # workaround for an msvc issue on VS 17.10 where generated ILK files are too large + # see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511 + set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO") +endif() + if(WIN32) target_sources(CemuBin PRIVATE resource/cemu.rc