diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..b22a1048 --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Inline +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: true + AfterControlStatement: Always + AfterEnum: true + AfterExternBlock: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeElse: true + BeforeWhile: true + SplitEmptyFunction: false +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +ColumnLimit: 0 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCSpaceAfterProperty: false +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceBeforeCtorInitializerColon: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +TabWidth: 4 +UseTab: Always diff --git a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md deleted file mode 100644 index 61bd5d06..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug Report / Feature Request -about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Cemu or you are requesting a feature you believe would make Cemu better. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d33d87ed..d71e22d0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ blank_issues_enabled: false contact_links: - name: Cemu Discord url: https://discord.com/invite/5psYsup - about: If you are experiencing an issue with Cemu, and you need tech support, or if you have a general question, try asking in the official Cemu Discord linked here. Piracy is not allowed. + about: If you need technical support with Cemu or have other questions the best place to ask is on the official Cemu Discord linked here diff --git a/.github/ISSUE_TEMPLATE/emulation_bug_report.yaml b/.github/ISSUE_TEMPLATE/emulation_bug_report.yaml new file mode 100644 index 00000000..75928607 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/emulation_bug_report.yaml @@ -0,0 +1,69 @@ +# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema +name: Bug Report +description: Report an issue with Cemu emulator +title: "Enter a title for the bug report here" +labels: bug +body: + - type: markdown + id: md_readme + attributes: + value: | + ## Important: Read First + + If you discovered a bug you can report it here. Please make sure of the following first: + - That you are using the latest version of Cemu + - Only report something if you are sure it's a bug and not any technical issue on your end. For troubleshooting help see the [links page](https://github.com/cemu-project/Cemu#links) + - Problems specific to a single game should be reported on the [compatibility wiki](https://wiki.cemu.info/wiki/Main_Page) instead + - Verify that your problem isn't already mentioned on the [issue tracker](https://github.com/cemu-project/Cemu/issues) + + Additionally, be aware that graphic packs can also causes issues. There is a separate issue tracker for graphic pack bugs over at the [graphic pack repository](https://github.com/cemu-project/cemu_graphic_packs) + - type: textarea + id: current_behavior + attributes: + label: Current Behavior + description: "What the bug is, in a brief description" + validations: + required: true + - type: textarea + id: expected_behavior + attributes: + label: Expected Behavior + description: "What did you expect to happen?" + validations: + required: true + + - type: textarea + id: steps_to_reproduce + attributes: + label: Steps to Reproduce + description: "How to reproduce the issue" + validations: + required: true + - type: textarea + id: sys_info + attributes: + label: System Info (Optional) + description: "Your PC specifications. Usually only the operating system and graphics card is important. But feel free to add more info." + placeholder: | + Info + OS: Windows 10 + GPU: NVIDIA GeForce RTX 4090 + value: | + OS: + GPU: + - type: textarea + id: emulation_settings + attributes: + label: Emulation Settings (Optional) + description: | + Any non-default settings. You can leave this empty if you didn't change anything other than input settings. + validations: + required: false + - type: textarea + id: logs_files + attributes: + label: "Logs (Optional)" + description: | + "Attach `log.txt` from your Cemu folder (*File > Open Cemu folder*)". + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_report.yaml b/.github/ISSUE_TEMPLATE/feature_report.yaml new file mode 100644 index 00000000..a5d8705c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_report.yaml @@ -0,0 +1,28 @@ +# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema +name: Feature suggestion +description: Suggest a new feature +title: "Enter a title for the suggestion here" +labels: feature request +body: + - type: markdown + id: md_readme + attributes: + value: | + ## Important: Read First + + While we appreciate suggestions, it is important to note that we are a very small team and there are already many more ideas than we could ever implement in the near future. Therefore, please only suggest something if you believe it is a great addition and the idea is reasonably unique. + + *Avoid* to create suggestions for: + - Overly obvious features ("Game xyz does not work and should be fixed", "Wiimote support should be improved", "You should add an Android port", "Copy feature xyz from another emulator", "A button to pause/stop emulation") + - Niche features which are only interesting to a tiny percentage of users + - Large scale features ("Add a Metal backend for MacOS", "Add ARM support", "Add savestates") + + Note that this doesn't mean we aren't interested in these ideas, but rather we likely have them planned anyway and it's mostly up to finding the time to implement them. + If you believe your idea is worthwhile even if it doesn't meet all the criteria above, you can still try suggesting it but we might close it. + - type: textarea + id: idea_suggestion + attributes: + label: Your suggestion + description: "Describe what your suggestion is in as much detail as possible" + validations: + required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a91d562b..3c01ba7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ env: jobs: build-ubuntu: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout repo" uses: actions/checkout@v3 @@ -53,7 +53,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-12 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build + 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: "Bootstrap vcpkg" run: | @@ -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-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja + 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 - name: "Build Cemu" run: | @@ -93,7 +93,7 @@ jobs: path: ./bin/Cemu build-appimage: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: build-ubuntu steps: - name: Checkout Upstream Repo @@ -107,7 +107,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-12 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream + sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream - name: "Build AppImage" run: | @@ -183,7 +183,7 @@ jobs: - name: "Build Cemu" run: | cd build - cmake --build . --config ${{ env.BUILD_MODE }} -j 2 + cmake --build . --config ${{ env.BUILD_MODE }} - name: Prepare artifact if: ${{ inputs.deploymode == 'release' }} @@ -232,7 +232,7 @@ jobs: - name: "Install system dependencies" run: | brew update - brew install llvm@15 ninja nasm molten-vk + brew install llvm@15 ninja nasm molten-vk automake libtool - name: "Bootstrap vcpkg" run: | diff --git a/.github/workflows/deploy_experimental_release.yml b/.github/workflows/deploy_experimental_release.yml index 9296c1cc..3bf86db4 100644 --- a/.github/workflows/deploy_experimental_release.yml +++ b/.github/workflows/deploy_experimental_release.yml @@ -10,7 +10,7 @@ jobs: experimentalversion: ${{ github.run_number }} deploy: name: Deploy experimental release - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: call-release-build steps: - uses: actions/checkout@v3 @@ -72,7 +72,7 @@ jobs: ls ./bin/ cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu - zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }} + zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-22.04-x64.zip ${{ env.CEMU_FOLDER_NAME }} rm -r ./${{ env.CEMU_FOLDER_NAME }} - name: Create release from macos-bin diff --git a/.github/workflows/generate_pot.yml b/.github/workflows/generate_pot.yml index f2675574..7dfa86f8 100644 --- a/.github/workflows/generate_pot.yml +++ b/.github/workflows/generate_pot.yml @@ -29,8 +29,8 @@ jobs: - name: "Generate POT file using xgettext" run: > find src -name *.cpp -o -name *.hpp -o -name *.h | - xargs xgettext --from-code=utf-8 - -k_ -kwxTRANSLATE -w 100 + xargs xgettext --from-code=utf-8 -w 100 + --keyword="_" --keyword="wxTRANSLATE" --keyword="wxPLURAL:1,2" --check=space-ellipsis --omit-header -o cemu.pot diff --git a/.gitignore b/.gitignore index e7f104d2..18f14cf3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ .idea/ build/ -cmake-build-*-*/ +cmake-build-*/ out/ .cache/ bin/Cemu_* @@ -49,3 +49,6 @@ bin/controllerProfiles/* bin/gameProfiles/* bin/graphicPacks/* + +# Ignore Finder view option files created by OS X +.DS_Store \ No newline at end of file diff --git a/BUILD.md b/BUILD.md index 5ff9bfd5..e4993ca1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -3,40 +3,39 @@ ## Windows Prerequisites: -- A recent version of Visual Studio 2022 (recommended but not required) with the following additional components: - - C++ CMake tools for Windows - - Windows 10/11 SDK - git +- A recent version of Visual Studio 2022 with the following additional components: + - C++ CMake tools for Windows + - Windows 10/11 SDK -Instructions: +Instructions for Visual Studio 2022: 1. Run `git clone --recursive https://github.com/cemu-project/Cemu` -2. Launch `Cemu/generate_vs_solution.bat`. - - If you installed VS to a custom location or use VS 2019, you may need to manually change the path inside the .bat file. -3. Wait until it's done, then open `Cemu/build/Cemu.sln` in Visual Studio. -4. Then build the solution and once finished you can run and debug it, or build it and check the /bin folder for the final Cemu_release.exe. +2. Open the newly created Cemu directory in Visual Studio using the "Open a local folder" option +3. In the menu select Project -> Configure CMake. Wait until it is done, this may take a long time +4. You can now build, run and debug Cemu -You can also skip steps 3-5 and open the root folder of the cloned repo directly in Visual Studio (as a folder) and use the built-in CMake support but be warned that cmake support in VS can be a bit finicky. +Any other IDE should also work as long as it has CMake and MSVC support. CLion and Visual Studio Code have been confirmed to work. ## Linux -To compile Cemu, a recent enough compiler and STL with C++20 support is required! clang-12 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 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build` +`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` -*Additionally, for Ubuntu 22.04 only:* - - `sudo apt install -y clang-12` - - At step 3 while building, use - `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +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` #### 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 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 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` @@ -46,7 +45,8 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required 5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`. #### Using GCC -While we use 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). +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 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++` @@ -86,7 +86,7 @@ You can skip this section if you have an Intel Mac. Every time you compile, you ### Installing dependencies -`brew install boost git cmake llvm ninja nasm molten-vk` +`brew install boost git cmake llvm ninja nasm molten-vk automake libtool` ### Build Cemu using cmake and clang 1. `git clone --recursive https://github.com/cemu-project/Cemu` diff --git a/CMakeLists.txt b/CMakeLists.txt index e1937036..c02b1d93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ if (ENABLE_VCPKG) if(UNIX AND NOT APPLE AND NOT VCPKG_TARGET_ANDROID) set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux") + elseif(APPLE) + set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_mac") else() set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") endif() @@ -99,6 +101,8 @@ if (WIN32) option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON) add_compile_definitions(HAS_DIRECTINPUT) endif() + +option(ENABLE_HIDAPI "Build with HIDAPI" ON) option(ENABLE_SDL "Enables the SDLController backend" ON) # audio backends @@ -108,6 +112,23 @@ if (WIN32) endif() option(ENABLE_CUBEB "Enabled cubeb backend" ON) +# usb hid backends +if (WIN32) + option(ENABLE_NSYSHID_WINDOWS_HID "Enables the native Windows HID backend for nsyshid" ON) +endif () +# libusb and windows hid backends shouldn't be active at the same time; otherwise we'd see all devices twice! +if (NOT ENABLE_NSYSHID_WINDOWS_HID) + option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON) +else () + set(ENABLE_NSYSHID_LIBUSB OFF CACHE BOOL "" FORCE) +endif () +if (ENABLE_NSYSHID_WINDOWS_HID) + add_compile_definitions(NSYSHID_ENABLE_BACKEND_WINDOWS_HID) +endif () +if (ENABLE_NSYSHID_LIBUSB) + add_compile_definitions(NSYSHID_ENABLE_BACKEND_LIBUSB) +endif () + option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON) set(THREADS_PREFER_PTHREAD_FLAG true) @@ -129,7 +150,7 @@ find_package(ZLIB REQUIRED) find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED) find_package(glm REQUIRED) -find_package(fmt 9.1.0...<10 REQUIRED) +find_package(fmt 9 REQUIRED) find_package(PNG REQUIRED) # glslang versions older than 11.11.0 define targets without a namespace @@ -170,6 +191,12 @@ if (ENABLE_DISCORD_RPC) target_include_directories(discord-rpc INTERFACE ./dependencies/discord-rpc/include) endif() +if (ENABLE_HIDAPI) + find_package(hidapi REQUIRED) + set(ENABLE_WIIMOTE ON) + add_compile_definitions(HAS_HIDAPI) +endif () + if(UNIX AND NOT APPLE AND NOT ANDROID) if(ENABLE_FERAL_GAMEMODE) add_compile_definitions(ENABLE_FERAL_GAMEMODE) diff --git a/CMakeSettings.json b/CMakeSettings.json index 3097caab..0927e98b 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -14,7 +14,7 @@ "generator": "Ninja", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}" }, { "name": "Debug", diff --git a/CODING_STYLE.md b/CODING_STYLE.md new file mode 100644 index 00000000..39e1d342 --- /dev/null +++ b/CODING_STYLE.md @@ -0,0 +1,99 @@ + +# Coding style guidelines for Cemu + +This document describes the latest version of our coding-style guidelines. Since we did not use this style from the beginning, older code may not adhere to these guidelines. Nevertheless, use these rules even if the surrounding code does not match. + +Cemu comes with a `.clang-format` file which is supported by most IDEs for formatting. Avoid auto-reformatting whole files, PRs with a lot of formatting changes are difficult to review. + +## Names for variables, functions and classes + +- Always prefix class member variables with `m_` +- Always prefix static class variables with `s_` +- For variable names: Camel case, starting with a lower case letter after the prefix. Examples: `m_option`, `s_audioVolume` +- For functions/class names: Use camel case starting with a capital letter. Examples: `MyClass`, `SetActive` +- Avoid underscores in variable names after the prefix. Use `m_myVariable` instead of `m_my_variable` + +## About types + +Cemu provides its own set of basic fixed-width types. They are: +`uint8`, `sint8`, `uint16`, `sint16`, `uint32`, `sint32`, `uint64`, `sint64`. Always use these types over something like `uint32_t`. Using `size_t` is also acceptable where suitable. Avoid C types like `int` or `long`. The only exception is when interacting with external libraries which expect these types as parameters. + +## When and where to put brackets + +Always put curly-brackets (`{ }`) on their own line. Example: + +``` +void FooBar() +{ + if (m_hasFoo) + { + ... + } +} +``` +As an exception, you can put short lambdas onto the same line: +``` +SomeFunc([]() { .... }); +``` +You can skip brackets for single-statement `if`. Example: +``` +if (cond) + action(); +``` + +## Printing + +Avoid sprintf and similar C-style formatting API. Use `fmt::format()`. +In UI related code you can use `formatWxString`, but be aware that number formatting with this function will be locale dependent! + +## Strings and encoding + +We use UTF-8 encoded `std::string` where possible. Some conversions need special handling and we have helper functions for those: +```cpp +// std::filesystem::path <-> std::string (in precompiled.h) +std::string _pathToUtf8(const fs::path& path); +fs::path _utf8ToPath(std::string_view input); + +// wxString <-> std::string +wxString wxString::FromUTF8(const std::string& s) +wxString to_wxString(std::string_view str); // in gui/helpers.h +std::string wxString::utf8_string(); + +``` + +## Logging + +If you want to write to log.txt use `cemuLog_log()`. The log type parameter should be mostly self-explanatory. Use `LogType::Force` if you always want to log something. For example: +`cemuLog_log(LogType::Force, "The value is {}", 123);` + +## HLE and endianness + +A pretty large part of Cemu's code base are re-implementations of various Cafe OS modules (e.g. `coreinit.rpl`, `gx2.rpl`...). These generally run in the context of the emulated process, thus special care has to be taken to use types with the correct size and endianness when interacting with memory. + +Keep in mind that the emulated Espresso CPU is 32bit big-endian, while the host architectures targeted by Cemu are 64bit little-endian! + +To keep code simple and remove the need for manual endian-swapping, Cemu has templates and aliases of the basic types with explicit endian-ness. +For big-endian types add the suffix `be`. Example: `uint32be` + +When you need to store a pointer in the guest's memory. Use `MEMPTR`. It will automatically store any pointer as 32bit big-endian. The pointer you store must point to memory that is within the guest address space. + +## HLE interfaces + +The implementation for each HLE module is inside a namespace with a matching name. E.g. `coreinit.rpl` functions go into `coreinit` namespace. + +To expose a new function as callable from within the emulated machine, use `cafeExportRegister` or `cafeExportRegisterFunc`. Here is a short example: +```cpp +namespace coreinit +{ + uint32 OSGetCoreCount() + { + return Espresso::CORE_COUNT; + } + + void Init() + { + cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread); + } +} +``` +You may also see some code which uses `osLib_addFunction` directly. This is a deprecated way of registering functions. \ No newline at end of file diff --git a/README.md b/README.md index 01eb46fe..e57cb483 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Matrix Server](https://img.shields.io/matrix/cemu:cemu.info?server_fqdn=matrix.cemu.info&label=cemu:cemu.info&logo=matrix&logoColor=FFFFFF)](https://matrix.to/#/#cemu:cemu.info) This is the code repository of Cemu, a Wii U emulator that is able to run most Wii U games and homebrew in a playable state. -It's written in C/C++ and is being actively developed with new features and fixes to increase compatibility, convenience and usability. +It's written in C/C++ and is being actively developed with new features and fixes. Cemu is currently only available for 64-bit Windows, Linux & macOS devices. @@ -24,11 +24,9 @@ Cemu is currently only available for 64-bit Windows, Linux & macOS devices. ## Download -You can download the latest Cemu releases from the [GitHub Releases](https://github.com/cemu-project/Cemu/releases/) or from [Cemu's website](https://cemu.info). +You can download the latest Cemu releases for Windows, Linux and Mac from the [GitHub Releases](https://github.com/cemu-project/Cemu/releases/). For Linux you can also find Cemu on [flathub](https://flathub.org/apps/info.cemu.Cemu). -Cemu is currently only available in a portable format so no installation is required besides extracting it in a safe place. - -The native Linux build is currently a work-in-progress. See [Current State Of Linux builds](https://github.com/cemu-project/Cemu/issues/107) for more information about the things to be aware of. +On Windows Cemu is currently only available in a portable format so no installation is required besides extracting it in a safe place. The native macOS build is currently purely experimental and should not be considered stable or ready for issue-free gameplay. There are also known issues with degraded performance due to the use of MoltenVK and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS. @@ -36,7 +34,7 @@ Pre-2.0 releases can be found on Cemu's [changelog page](https://cemu.info/chang ## Build Instructions -To compile Cemu yourself on Windows, Linux or macOS, view the [BUILD.md file](/BUILD.md). +To compile Cemu yourself on Windows, Linux or macOS, view [BUILD.md](/BUILD.md). ## Issues @@ -46,6 +44,7 @@ The old bug tracker can be found at [bugs.cemu.info](https://bugs.cemu.info) and ## Contributing Pull requests are very welcome. For easier coordination you can visit the developer discussion channel on [Discord](https://discord.gg/5psYsup) or alternatively the [Matrix Server](https://matrix.to/#/#cemu:cemu.info). +Before submitting a pull request, please read and follow our code style guidelines listed in [CODING_STYLE.md](/CODING_STYLE.md). If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated! diff --git a/bin/resources/de/cemu.mo b/bin/resources/de/cemu.mo index ee2bb295..918fcd35 100644 Binary files a/bin/resources/de/cemu.mo and b/bin/resources/de/cemu.mo differ diff --git a/bin/resources/he/cemu.mo b/bin/resources/he/cemu.mo new file mode 100644 index 00000000..072e48c6 Binary files /dev/null and b/bin/resources/he/cemu.mo differ diff --git a/bin/resources/it/cemu.mo b/bin/resources/it/cemu.mo index 18ca9c93..20d5bb93 100644 Binary files a/bin/resources/it/cemu.mo and b/bin/resources/it/cemu.mo differ diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo index edaec863..9f20110f 100644 Binary files a/bin/resources/ko/cemu.mo and b/bin/resources/ko/cemu.mo differ diff --git a/cmake/Findlibusb.cmake b/cmake/Findlibusb.cmake new file mode 100644 index 00000000..85da6736 --- /dev/null +++ b/cmake/Findlibusb.cmake @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2022 Andrea Pappacoda +# SPDX-License-Identifier: ISC + +find_package(libusb CONFIG) +if (NOT libusb_FOUND) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_search_module(libusb IMPORTED_TARGET GLOBAL libusb-1.0 libusb) + if (libusb_FOUND) + add_library(libusb::libusb ALIAS PkgConfig::libusb) + endif () + endif () +endif () + +find_package_handle_standard_args(libusb + REQUIRED_VARS + libusb_LINK_LIBRARIES + libusb_FOUND + VERSION_VAR libusb_VERSION +) diff --git a/dependencies/ih264d/CMakeLists.txt b/dependencies/ih264d/CMakeLists.txt index 212cf346..d97d6dda 100644 --- a/dependencies/ih264d/CMakeLists.txt +++ b/dependencies/ih264d/CMakeLists.txt @@ -183,4 +183,10 @@ endif() if(MSVC) set_property(TARGET ih264d PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +# tune settings for slightly better performance +target_compile_options(ih264d PRIVATE $<$:/Oi>) # enable intrinsic functions +target_compile_options(ih264d PRIVATE $<$:/Ot>) # favor speed +target_compile_options(ih264d PRIVATE "/GS-") # disable runtime checks + endif() diff --git a/dependencies/ih264d/common/ithread.c b/dependencies/ih264d/common/ithread.c index d710e323..2c25bdb0 100644 --- a/dependencies/ih264d/common/ithread.c +++ b/dependencies/ih264d/common/ithread.c @@ -85,28 +85,59 @@ UWORD32 ithread_get_mutex_lock_size(void) return sizeof(CRITICAL_SECTION); } +struct _ithread_launch_param +{ + void (*startFunc)(void* argument); + void* argument; +}; + +DWORD WINAPI _ithread_WinThreadStartRoutine(LPVOID lpThreadParameter) +{ + struct _ithread_launch_param* param = (struct _ithread_launch_param*)lpThreadParameter; + typedef void *(*ThreadStartRoutineType)(void *); + ThreadStartRoutineType pfnThreadRoutine = (ThreadStartRoutineType)param->startFunc; + void* arg = param->argument; + free(param); + pfnThreadRoutine(arg); + return 0; +} + WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument) { - //UNUSED(attribute); - //return pthread_create((pthread_t*)thread_handle, NULL, (void* (*)(void*)) strt, argument); - __debugbreak(); + UNUSED(attribute); + struct _ithread_launch_param* param = malloc(sizeof(struct _ithread_launch_param)); + param->startFunc = (void (*)(void*))strt; + param->argument = argument; + HANDLE *handle = (HANDLE*)thread_handle; + *handle = CreateThread(NULL, 0, _ithread_WinThreadStartRoutine, param, 0, NULL); + if(*handle == NULL) + { + return -1; + } return 0; } WORD32 ithread_join(void* thread_handle, void** val_ptr) { //UNUSED(val_ptr); - //pthread_t* pthread_handle = (pthread_t*)thread_handle; - //return pthread_join(*pthread_handle, NULL); - - __debugbreak(); - return 0; + HANDLE *handle = (HANDLE*)thread_handle; + DWORD result = WaitForSingleObject(*handle, INFINITE); + if(result == WAIT_OBJECT_0) + { + CloseHandle(*handle); + return 0; + } + else + { + return -1; + } } WORD32 ithread_get_mutex_struct_size(void) { return sizeof(CRITICAL_SECTION); } + WORD32 ithread_mutex_init(void* mutex) { InitializeCriticalSection((LPCRITICAL_SECTION)mutex); @@ -153,7 +184,6 @@ UWORD32 ithread_get_sem_struct_size(void) //return(sizeof(sem_t)); } - WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value) { __debugbreak(); @@ -168,7 +198,6 @@ WORD32 ithread_sem_post(void* sem) //return sem_post((sem_t*)sem); } - WORD32 ithread_sem_wait(void* sem) { __debugbreak(); @@ -176,7 +205,6 @@ WORD32 ithread_sem_wait(void* sem) //return sem_wait((sem_t*)sem); } - WORD32 ithread_sem_destroy(void* sem) { __debugbreak(); diff --git a/dependencies/ih264d/common/x86/ih264_platform_macros.h b/dependencies/ih264d/common/x86/ih264_platform_macros.h index ebc1b106..22de33d6 100644 --- a/dependencies/ih264d/common/x86/ih264_platform_macros.h +++ b/dependencies/ih264d/common/x86/ih264_platform_macros.h @@ -79,10 +79,8 @@ static inline int __builtin_clz(unsigned x) { unsigned long n; - if (x == 0) - return 32; _BitScanReverse(&n, x); - return 31 - n; + return n ^ 31; } static inline int __builtin_ctz(unsigned x) { diff --git a/dependencies/vcpkg b/dependencies/vcpkg index b81bc3a8..53bef899 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit b81bc3a83fdbdffe80325eeabb2ec735a1f3c29d +Subproject commit 53bef8994c541b6561884a8395ea35715ece75db diff --git a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch deleted file mode 100644 index a8637d8c..00000000 --- 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 39a724c5..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake +++ /dev/null @@ -1,130 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 90858ae8c5fdddd5e13724e05ad0970e11bbab1df8a0201c3f4ce354dc6018e5d4ab7279402a263c716aacdaa52745f78531dc225d48d790ee9307e2f6198695 - HEAD_REF main - PATCHES - deps.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 - vulkan SDL_VULKAN - x11 SDL_X11 - wayland SDL_WAYLAND - samplerate SDL_LIBSAMPLERATE - ibus SDL_IBUS -) - -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 - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT -) - -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 deleted file mode 100644 index 1cddcd46..00000000 --- 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 - $ - $,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 de2eb9b9..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "sdl2", - "version": "2.26.5", - "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": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "base" - ], - "features": { - "base": { - "description": "Base functionality for SDL", - "dependencies": [ - { - "name": "sdl2", - "default-features": false, - "features": [ - "ibus", - "wayland", - "x11" - ], - "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/deps.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch deleted file mode 100644 index a8637d8c..00000000 --- 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 39a724c5..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake +++ /dev/null @@ -1,130 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 90858ae8c5fdddd5e13724e05ad0970e11bbab1df8a0201c3f4ce354dc6018e5d4ab7279402a263c716aacdaa52745f78531dc225d48d790ee9307e2f6198695 - HEAD_REF main - PATCHES - deps.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 - vulkan SDL_VULKAN - x11 SDL_X11 - wayland SDL_WAYLAND - samplerate SDL_LIBSAMPLERATE - ibus SDL_IBUS -) - -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 - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT -) - -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 deleted file mode 100644 index 1cddcd46..00000000 --- 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 - $ - $,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 de2eb9b9..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "sdl2", - "version": "2.26.5", - "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": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "base" - ], - "features": { - "base": { - "description": "Base functionality for SDL", - "dependencies": [ - { - "name": "sdl2", - "default-features": false, - "features": [ - "ibus", - "wayland", - "x11" - ], - "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/.gitkeep b/dependencies/vcpkg_overlay_ports_mac/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake new file mode 100644 index 00000000..7b76bba0 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake @@ -0,0 +1,71 @@ +set(VCPKG_LIBRARY_LINKAGE dynamic) + +if(VCPKG_TARGET_IS_LINUX) + message("${PORT} currently requires the following tools and libraries from the system package manager:\n autoreconf\n libudev\n\nThese can be installed on Ubuntu systems via apt-get install autoconf libudev-dev") +endif() + +set(VERSION 1.0.26) +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libusb/libusb + REF fcf0c710ef5911ae37fbbf1b39d48a89f6f14e8a # v1.0.26.11791 2023-03-12 + SHA512 0aa6439f7988487adf2a3bff473fec80b5c722a47f117a60696d2aa25c87cc3f20fb6aaca7c66e49be25db6a35eb0bb5f71ed7b211d1b8ee064c5d7f1b985c73 + HEAD_REF master +) + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + + if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + set(LIBUSB_PROJECT_TYPE dll) + else() + set(LIBUSB_PROJECT_TYPE static) + endif() + + # The README.md file in the archive is a symlink to README + # which causes issues with the windows MSBUILD process + file(REMOVE "${SOURCE_PATH}/README.md") + + vcpkg_msbuild_install( + SOURCE_PATH "${SOURCE_PATH}" + PROJECT_SUBPATH msvc/libusb_${LIBUSB_PROJECT_TYPE}.vcxproj + ) + + file(INSTALL "${SOURCE_PATH}/libusb/libusb.h" DESTINATION "${CURRENT_PACKAGES_DIR}/include/libusb-1.0") + set(prefix "") + set(exec_prefix [[${prefix}]]) + set(libdir [[${prefix}/lib]]) + set(includedir [[${prefix}/include]]) + configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" @ONLY) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0") + if(NOT VCPKG_BUILD_TYPE) + set(includedir [[${prefix}/../include]]) + configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" @ONLY) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0") + endif() +else() + vcpkg_list(SET MAKE_OPTIONS) + vcpkg_list(SET LIBUSB_LINK_LIBRARIES) + if(VCPKG_TARGET_IS_EMSCRIPTEN) + vcpkg_list(APPEND MAKE_OPTIONS BUILD_TRIPLET --host=wasm32) + endif() + if("udev" IN_LIST FEATURES) + vcpkg_list(APPEND MAKE_OPTIONS "--enable-udev") + vcpkg_list(APPEND LIBUSB_LINK_LIBRARIES udev) + else() + vcpkg_list(APPEND MAKE_OPTIONS "--disable-udev") + endif() + vcpkg_configure_make( + SOURCE_PATH "${SOURCE_PATH}" + AUTOCONFIG + OPTIONS + ${MAKE_OPTIONS} + "--enable-examples-build=no" + "--enable-tests-build=no" + ) + vcpkg_install_make() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING") diff --git a/dependencies/vcpkg_overlay_ports_mac/libusb/usage b/dependencies/vcpkg_overlay_ports_mac/libusb/usage new file mode 100644 index 00000000..87e6e860 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/libusb/usage @@ -0,0 +1,5 @@ +libusb can be imported via CMake FindPkgConfig module: + find_package(PkgConfig REQUIRED) + pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) + + target_link_libraries(main PRIVATE PkgConfig::libusb) diff --git a/dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json new file mode 100644 index 00000000..efc70f3d --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json @@ -0,0 +1,8 @@ +{ + "name": "libusb", + "version": "1.0.26.11791", + "port-version": 7, + "description": "a cross-platform library to access USB devices", + "homepage": "https://github.com/libusb/libusb", + "license": "LGPL-2.1-or-later" +} diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index 7043a759..60a50329 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -46,6 +46,7 @@ fi echo "Cemu Version Cemu-${GITVERSION}" rm AppDir/usr/lib/libwayland-client.so.0 +cp /lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/lib/ echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-hooks/linuxdeploy-plugin-gtk.sh VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir diff --git a/dist/linux/info.cemu.Cemu.metainfo.xml b/dist/linux/info.cemu.Cemu.metainfo.xml index f056555b..ef59427f 100644 --- a/dist/linux/info.cemu.Cemu.metainfo.xml +++ b/dist/linux/info.cemu.Cemu.metainfo.xml @@ -3,15 +3,15 @@ info.cemu.Cemu Cemu - Software to emulate Wii U games and applications on PC - Software zum emulieren von Wii U Spielen und Anwendungen auf dem PC - Application pour émuler des jeux et applications Wii U sur PC - Applicatie om Wii U spellen en applicaties te emuleren op PC - Πρόγραμμα προσομοίωσης παιχνιδιών και εφαρμογών Wii U στον υπολογιστή - Software para emular juegos y aplicaciones de Wii U en PC - Software para emular jogos e aplicativos de Wii U no PC - Software per emulare giochi e applicazioni per Wii U su PC - Ojelmisto Wii U -pelien ja -sovellusten emulointiin PC:llä + Nintendo Wii U Emulator + Nintendo Wii U Emulator + Émulateur Nintendo Wii U + Nintendo Wii U Emulator + Εξομοιωτής Nintendo Wii U + Emulador de Nintendo Wii U + Emulador Nintendo Wii U + Emulatore Nintendo Wii U + Nintendo Wii U Emulaattori Cemu Project info.cemu.Cemu.desktop CC0-1.0 diff --git a/dist/windows/Cemu.manifest b/dist/windows/Cemu.manifest new file mode 100644 index 00000000..5ff952b1 --- /dev/null +++ b/dist/windows/Cemu.manifest @@ -0,0 +1,16 @@ + + + + + + + + + + + + + True/PM + + + \ No newline at end of file diff --git a/generate_vs_solution.bat b/generate_vs_solution.bat deleted file mode 100644 index 21060027..00000000 --- a/generate_vs_solution.bat +++ /dev/null @@ -1,2 +0,0 @@ -"C:\PROGRAM FILES\MICROSOFT VISUAL STUDIO\2022\COMMUNITY\COMMON7\IDE\COMMONEXTENSIONS\MICROSOFT\CMAKE\CMake\bin\cmake.exe" -B build/ -pause \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3bb5281..6b88ef37 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,7 @@ else() if(WIN32) target_sources(CemuBin PRIVATE resource/cemu.rc + ../dist/windows/cemu.manifest ) endif() set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") @@ -89,9 +90,11 @@ if (MACOS_BUNDLE) set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu") set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION}) set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}) - set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2022 Cemu Project") + set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project") set(MACOSX_BUNDLE_CATEGORY "public.app-category.games") + set(MACOSX_MINIMUM_SYSTEM_VERSION "12.0") + set(MACOSX_BUNDLE_TYPE_EXTENSION "wua") set_target_properties(CemuBin PROPERTIES MACOSX_BUNDLE true @@ -105,10 +108,10 @@ if (MACOS_BUNDLE) endforeach(folder) 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") - - add_custom_command (TARGET CemuBin POST_BUILD - COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + 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 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() set_target_properties(CemuBin PROPERTIES diff --git a/src/Cafe/Account/Account.h b/src/Cafe/Account/Account.h index 63eb5082..da196e42 100644 --- a/src/Cafe/Account/Account.h +++ b/src/Cafe/Account/Account.h @@ -16,22 +16,6 @@ enum class OnlineAccountError kPasswordCacheEmpty, kNoPrincipalId, }; -template <> -struct fmt::formatter : formatter { - template - auto format(const OnlineAccountError v, FormatContext& ctx) { - switch (v) - { - case OnlineAccountError::kNoAccountId: return formatter::format("AccountId missing (The account is not connected to a NNID)", ctx); - case OnlineAccountError::kNoPasswordCached: return formatter::format("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)", ctx); - case OnlineAccountError::kPasswordCacheEmpty: return formatter::format("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)", ctx); - case OnlineAccountError::kNoPrincipalId: return formatter::format("PrincipalId missing", ctx); - default: break; - } - return formatter::format("no error", ctx); - } -}; - struct OnlineValidator { diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 534f76b5..77c560c5 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(CemuCafe HW/Espresso/Debugger/DebugSymbolStorage.h HW/Espresso/Debugger/GDBStub.h HW/Espresso/Debugger/GDBStub.cpp + HW/Espresso/Debugger/GDBBreakpoints.cpp HW/Espresso/Debugger/GDBBreakpoints.h HW/Espresso/EspressoISA.h HW/Espresso/Interpreter/PPCInterpreterALU.hpp @@ -420,6 +421,8 @@ add_library(CemuCafe OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h OS/libs/nn_olv/nn_olv_PostTypes.cpp OS/libs/nn_olv/nn_olv_PostTypes.h + OS/libs/nn_olv/nn_olv_OfflineDB.cpp + OS/libs/nn_olv/nn_olv_OfflineDB.h OS/libs/nn_pdm/nn_pdm.cpp OS/libs/nn_pdm/nn_pdm.h OS/libs/nn_save/nn_save.cpp @@ -430,6 +433,14 @@ add_library(CemuCafe OS/libs/nn_uds/nn_uds.h OS/libs/nsyshid/nsyshid.cpp OS/libs/nsyshid/nsyshid.h + OS/libs/nsyshid/Backend.h + OS/libs/nsyshid/AttachDefaultBackends.cpp + OS/libs/nsyshid/Whitelist.cpp + OS/libs/nsyshid/Whitelist.h + OS/libs/nsyshid/BackendLibusb.cpp + OS/libs/nsyshid/BackendLibusb.h + OS/libs/nsyshid/BackendWindowsHID.cpp + OS/libs/nsyshid/BackendWindowsHID.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp @@ -545,6 +556,17 @@ if (ENABLE_WAYLAND) target_link_libraries(CemuCafe PUBLIC Wayland::Client) endif() +if (ENABLE_NSYSHID_LIBUSB) + if (ENABLE_VCPKG) + find_package(PkgConfig REQUIRED) + pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) + target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb) + else () + find_package(libusb MODULE REQUIRED) + target_link_libraries(CemuCafe PRIVATE libusb::libusb) + endif () +endif () + if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") target_link_libraries(CemuCafe PRIVATE xbyak_aarch64) endif() diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 8b2c3189..40a4d047 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -3,17 +3,16 @@ #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "audio/IAudioAPI.h" #include "audio/IAudioInputAPI.h" -#include "Cafe/HW/Espresso/Debugger/Debugger.h" - #include "config/ActiveSettings.h" #include "Cafe/TitleList/GameInfo.h" -#include "util/helpers/SystemException.h" #include "Cafe/GraphicPack/GraphicPack2.h" - +#include "util/helpers/SystemException.h" +#include "Common/cpu_features.h" #include "input/InputManager.h" - #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/GameInfo.h" @@ -21,14 +20,9 @@ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/HW/Latte/Core/Latte.h" - #include "Cafe/Filesystem/FST/FST.h" - #include "Common/FileStream.h" - #include "GamePatch.h" - -#include #include "HW/Espresso/Debugger/GDBStub.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" @@ -66,6 +60,15 @@ // HW interfaces #include "Cafe/HW/SI/si.h" +#include + +#if BOOST_OS_LINUX +#include +#elif BOOST_OS_MACOS +#include +#include +#endif + std::string _pathToExecutable; std::string _pathToBaseExecutable; @@ -158,7 +161,7 @@ void LoadMainExecutable() { // RPX RPLLoader_AddDependency(_pathToExecutable.c_str()); - applicationRPX = rpl_loadFromMem(rpxData, rpxSize, (char*)_pathToExecutable.c_str()); + applicationRPX = RPLLoader_LoadFromMemory(rpxData, rpxSize, (char*)_pathToExecutable.c_str()); if (!applicationRPX) { cemuLog_log(LogType::Force, "Failed to run this title because the executable is damaged"); @@ -244,14 +247,7 @@ void InfoLog_PrintActiveSettings() if(!GetConfig().vk_accurate_barriers.GetValue()) cemuLog_log(LogType::Force, "Accurate barriers are disabled!"); } - cemuLog_log(LogType::Force, "Console language: {}", config.console_language); -} - -void PPCCore_setupSPR(PPCInterpreter_t* hCPU, uint32 coreIndex) -{ - hCPU->sprExtended.PVR = 0x70010001; - hCPU->spr.UPIR = coreIndex; - hCPU->sprExtended.msr |= MSR_FP; // enable floating point + cemuLog_log(LogType::Force, "Console language: {}", stdx::to_underlying(config.console_language.GetValue())); } struct SharedDataEntry @@ -453,34 +449,143 @@ namespace CafeSystem GameInfo2 sGameInfo_ForegroundTitle; - // initialize all subsystems which are persistent and don't depend on a game running + + static void _CheckForWine() + { + #if BOOST_OS_WINDOWS + const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); + if (!hmodule) + return; + + const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); + if (pwine_get_version) + { + cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version()); + } + #endif + } + + void logCPUAndMemoryInfo() + { + std::string cpuName = g_CPUFeatures.GetCPUName(); + if (!cpuName.empty()) + cemuLog_log(LogType::Force, "CPU: {}", cpuName); + #if BOOST_OS_WINDOWS + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + GlobalMemoryStatusEx(&statex); + uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); + cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB); + #elif BOOST_OS_LINUX + struct sysinfo info {}; + sysinfo(&info); + cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL)); + #elif BOOST_OS_MACOS + int64_t totalRam; + size_t size = sizeof(totalRam); + int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0); + if (result == 0) + cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); + #endif + } + + #if BOOST_OS_WINDOWS + std::string GetWindowsNamedVersion(uint32& buildNumber) + { + char productName[256]; + HKEY hKey; + DWORD dwType = REG_SZ; + DWORD dwSize = sizeof(productName); + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)productName, &dwSize) != ERROR_SUCCESS) + strcpy(productName, "Windows"); + RegCloseKey(hKey); + } + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + buildNumber = osvi.dwBuildNumber; + return std::string(productName); + } + #endif + + void logPlatformInfo() + { + std::string buffer; + const char* platform = NULL; + #if __ANDROID__ + platform = "Android"; + #elif BOOST_OS_WINDOWS + uint32 buildNumber; + std::string windowsVersionName = GetWindowsNamedVersion(buildNumber); + buffer = fmt::format("{} (Build {})", windowsVersionName, buildNumber); + platform = buffer.c_str(); + #elif BOOST_OS_LINUX + if (getenv ("APPIMAGE")) + platform = "Linux (AppImage)"; + else if (getenv ("SNAP")) + platform = "Linux (Snap)"; + else if (platform = getenv ("container")) + { + if (strcmp (platform, "flatpak") == 0) + platform = "Linux (Flatpak)"; + } + else + platform = "Linux"; + #elif BOOST_OS_MACOS + platform = "MacOS"; + #endif + cemuLog_log(LogType::Force, "Platform: {}", platform); + } + + static std::vector s_iosuModules = + { + // entries in this list are ordered by initialization order. Shutdown in reverse order + iosu::kernel::GetModule(), + iosu::fpd::GetModule(), + iosu::pdm::GetModule(), + }; + + // initialize all subsystems which are persistent and don't depend on a game running void Initialize() { if (s_initialized) return; s_initialized = true; // init core systems + cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); fsc_init(); memory_init(); + cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); PPCCore_init(); RPLLoader_InitState(); + cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); + _CheckForWine(); + // CPU and RAM info + logCPUAndMemoryInfo(); + logPlatformInfo(); + cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); + // misc systems + rplSymbolStorage_init(); // allocate memory for all SysAllocators // must happen before COS module init, but also before iosu::kernel::Initialize() SysAllocatorContainer::GetInstance().Initialize(); - // init IOSU + // init IOSU modules + for(auto& module : s_iosuModules) + module->SystemLaunch(); + // init IOSU (deprecated manual init) iosuCrypto_init(); - iosu::kernel::Initialize(); iosu::fsa::Initialize(); iosuIoctl_init(); iosuAct_init_depr(); iosu::act::Initialize(); - iosu::fpd::Initialize(); iosu::iosuMcp_init(); iosu::mcp::Init(); iosu::iosuAcp_init(); iosu::boss_init(); iosu::nim::Initialize(); - iosu::pdm::Initialize(); iosu::odm::Initialize(); // init Cafe OS avm::Initialize(); @@ -510,11 +615,14 @@ namespace CafeSystem // if a title is running, shut it down if (sSystemRunning) ShutdownTitle(); - // shutdown persistent subsystems + // shutdown persistent subsystems (deprecated manual shutdown) iosu::odm::Shutdown(); iosu::act::Stop(); iosu::mcp::Shutdown(); iosu::fsa::Shutdown(); + // shutdown IOSU modules + for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it) + (*it)->SystemExit(); s_initialized = false; } @@ -738,14 +846,14 @@ namespace CafeSystem void _LaunchTitleThread() { - // init + for(auto& module : s_iosuModules) + module->TitleStart(); cemu_initForGame(); // enter scheduler if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler) coreinit::OSSchedulerBegin(3); else coreinit::OSSchedulerBegin(1); - iosu::pdm::StartTrackingTime(GetForegroundTitleId()); } void LaunchForegroundTitle() @@ -875,8 +983,8 @@ namespace CafeSystem nn::save::ResetToDefaultState(); coreinit::__OSDeleteAllActivePPCThreads(); RPLLoader_ResetState(); - // stop time tracking - iosu::pdm::Stop(); + for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it) + (*it)->TitleStop(); // reset Cemu subsystems PPCRecompiler_Shutdown(); GraphicPack2::Reset(); diff --git a/src/Cafe/Filesystem/FST/FST.cpp b/src/Cafe/Filesystem/FST/FST.cpp index a4bbfeed..570671d4 100644 --- a/src/Cafe/Filesystem/FST/FST.cpp +++ b/src/Cafe/Filesystem/FST/FST.cpp @@ -12,6 +12,8 @@ #include "boost/range/adaptor/reversed.hpp" +#define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code + class FSTDataSource { public: @@ -215,23 +217,22 @@ bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey) // open WUD image using key cache // if no matching key is found then keyFound will return false -FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, bool* keyFound) +FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut) { + SET_FST_ERROR(UNKNOWN_ERROR); KeyCache_Prepare(); NCrypto::AesKey discTitleKey; if (!FindDiscKey(path, discTitleKey)) { - if(keyFound) - *keyFound = false; + SET_FST_ERROR(DISC_KEY_MISSING); return nullptr; } - if(keyFound) - *keyFound = true; - return OpenFromDiscImage(path, discTitleKey); + return OpenFromDiscImage(path, discTitleKey, errorCodeOut); } // open WUD image -FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey) +FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut) + { // WUD images support multiple partitions, each with their own key and FST // the process for loading game data FSTVolume from a WUD image is as follows: @@ -240,6 +241,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d // 3) find main GM partition // 4) use SI information to get titleKey for GM partition // 5) Load FST for GM + SET_FST_ERROR(UNKNOWN_ERROR); std::unique_ptr dataSource(FSTDataSourceWUD::Open(path)); if (!dataSource) return nullptr; @@ -365,11 +367,15 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d // load GM partition dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); - return OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast(partitionHeaderGM.fstHashType)); + FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast(partitionHeaderGM.fstHashType)); + if (r) + SET_FST_ERROR(OK); + return r; } -FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) +FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut) { + SET_FST_ERROR(UNKNOWN_ERROR); // load TMD FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd"); if (!tmdFile) @@ -379,17 +385,26 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) delete tmdFile; NCrypto::TMDParser tmdParser; if (!tmdParser.parse(tmdData.data(), tmdData.size())) + { + SET_FST_ERROR(BAD_TITLE_TMD); return nullptr; + } // load ticket FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik"); if (!ticketFile) + { + SET_FST_ERROR(TITLE_TIK_MISSING); return nullptr; + } std::vector ticketData; ticketFile->extract(ticketData); delete ticketFile; NCrypto::ETicketParser ticketParser; if (!ticketParser.parse(ticketData.data(), ticketData.size())) + { + SET_FST_ERROR(BAD_TITLE_TIK); return nullptr; + } NCrypto::AesKey titleKey; ticketParser.GetTitleKey(titleKey); // open data source @@ -412,6 +427,8 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) // load FST // fstSize = size of first cluster? FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode); + if (fstVolume) + SET_FST_ERROR(OK); return fstVolume; } @@ -669,25 +686,25 @@ bool FSTVolume::OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bo return true; } -bool FSTVolume::IsDirectory(FSTFileHandle& fileHandle) const +bool FSTVolume::IsDirectory(const FSTFileHandle& fileHandle) const { cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::DIRECTORY; }; -bool FSTVolume::IsFile(FSTFileHandle& fileHandle) const +bool FSTVolume::IsFile(const FSTFileHandle& fileHandle) const { cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::FILE; }; -bool FSTVolume::HasLinkFlag(FSTFileHandle& fileHandle) const +bool FSTVolume::HasLinkFlag(const FSTFileHandle& fileHandle) const { cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); return HAS_FLAG(m_entries[fileHandle.m_fstIndex].GetFlags(), FSTEntry::FLAGS::FLAG_LINK); }; -std::string_view FSTVolume::GetName(FSTFileHandle& fileHandle) const +std::string_view FSTVolume::GetName(const FSTFileHandle& fileHandle) const { if (fileHandle.m_fstIndex > m_entries.size()) return ""; @@ -695,7 +712,7 @@ std::string_view FSTVolume::GetName(FSTFileHandle& fileHandle) const return entryName; } -std::string FSTVolume::GetPath(FSTFileHandle& fileHandle) const +std::string FSTVolume::GetPath(const FSTFileHandle& fileHandle) const { std::string path; auto& entry = m_entries[fileHandle.m_fstIndex]; @@ -726,7 +743,7 @@ std::string FSTVolume::GetPath(FSTFileHandle& fileHandle) const return path; } -uint32 FSTVolume::GetFileSize(FSTFileHandle& fileHandle) const +uint32 FSTVolume::GetFileSize(const FSTFileHandle& fileHandle) const { if (m_entries[fileHandle.m_fstIndex].GetType() != FSTEntry::TYPE::FILE) return 0; @@ -977,6 +994,7 @@ bool FSTVolume::OpenDirectoryIterator(std::string_view path, FSTDirectoryIterato if (!IsDirectory(fileHandle)) return false; auto const& fstEntry = m_entries[fileHandle.m_fstIndex]; + directoryIteratorOut.dirHandle = fileHandle; directoryIteratorOut.startIndex = fileHandle.m_fstIndex + 1; directoryIteratorOut.endIndex = fstEntry.dirInfo.endIndex; directoryIteratorOut.currentIndex = directoryIteratorOut.startIndex; diff --git a/src/Cafe/Filesystem/FST/FST.h b/src/Cafe/Filesystem/FST/FST.h index 3f59152f..24fc39ea 100644 --- a/src/Cafe/Filesystem/FST/FST.h +++ b/src/Cafe/Filesystem/FST/FST.h @@ -11,7 +11,13 @@ private: struct FSTDirectoryIterator { friend class FSTVolume; + + const FSTFileHandle& GetDirHandle() const + { + return dirHandle; + } private: + FSTFileHandle dirHandle; uint32 startIndex; uint32 endIndex; uint32 currentIndex; @@ -20,11 +26,21 @@ private: class FSTVolume { public: + enum class ErrorCode + { + OK = 0, + UNKNOWN_ERROR = 1, + DISC_KEY_MISSING = 2, + TITLE_TIK_MISSING = 3, + BAD_TITLE_TMD = 4, + BAD_TITLE_TIK = 5, + }; + static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey); - static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey); - static FSTVolume* OpenFromDiscImage(const fs::path& path, bool* keyFound = nullptr); - static FSTVolume* OpenFromContentFolder(fs::path folderPath); + static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut = nullptr); + static FSTVolume* OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut = nullptr); + static FSTVolume* OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut = nullptr); ~FSTVolume(); @@ -33,15 +49,15 @@ public: bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false); // file and directory functions - bool IsDirectory(FSTFileHandle& fileHandle) const; - bool IsFile(FSTFileHandle& fileHandle) const; - bool HasLinkFlag(FSTFileHandle& fileHandle) const; + bool IsDirectory(const FSTFileHandle& fileHandle) const; + bool IsFile(const FSTFileHandle& fileHandle) const; + bool HasLinkFlag(const FSTFileHandle& fileHandle) const; - std::string_view GetName(FSTFileHandle& fileHandle) const; - std::string GetPath(FSTFileHandle& fileHandle) const; + std::string_view GetName(const FSTFileHandle& fileHandle) const; + std::string GetPath(const FSTFileHandle& fileHandle) const; // file functions - uint32 GetFileSize(FSTFileHandle& fileHandle) const; + uint32 GetFileSize(const FSTFileHandle& fileHandle) const; uint32 ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut); // directory iterator diff --git a/src/Cafe/Filesystem/fscDeviceWud.cpp b/src/Cafe/Filesystem/fscDeviceWud.cpp index bf43bf3e..517c8573 100644 --- a/src/Cafe/Filesystem/fscDeviceWud.cpp +++ b/src/Cafe/Filesystem/fscDeviceWud.cpp @@ -128,7 +128,7 @@ class fscDeviceWUDC : public fscDeviceC if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) { FSTFileHandle fstFileHandle; - if (mountedVolume->OpenFile(path, fstFileHandle, true)) + if (mountedVolume->OpenFile(path, fstFileHandle, true) && !mountedVolume->HasLinkFlag(fstFileHandle)) { *fscStatus = FSC_STATUS_OK; return new FSCDeviceWudFileCtx(mountedVolume, fstFileHandle); @@ -137,7 +137,7 @@ class fscDeviceWUDC : public fscDeviceC if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) { FSTDirectoryIterator dirIterator; - if (mountedVolume->OpenDirectoryIterator(path, dirIterator)) + if (mountedVolume->OpenDirectoryIterator(path, dirIterator) && !mountedVolume->HasLinkFlag(dirIterator.GetDirHandle())) { *fscStatus = FSC_STATUS_OK; return new FSCDeviceWudFileCtx(mountedVolume, dirIterator); diff --git a/src/Cafe/GamePatch.cpp b/src/Cafe/GamePatch.cpp index 84bfcb21..77eaff32 100644 --- a/src/Cafe/GamePatch.cpp +++ b/src/Cafe/GamePatch.cpp @@ -52,7 +52,7 @@ typedef struct void hleExport_xcx_enterCriticalSection(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(xcxCS, xcxCS_t, 0); - uint32 threadId = coreinitThread_getCurrentThreadMPTRDepr(hCPU); + uint32 threadId = MEMPTR(coreinit::OSGetCurrentThread()).GetMPTR(); cemu_assert_debug(xcxCS->ukn08 != 0); cemu_assert_debug(threadId); if (xcxCS->ownerThreadId == (uint32be)threadId) diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index d068237e..ee92107a 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -209,7 +209,7 @@ bool GameProfile::Load(uint64_t title_id) m_gameName = std::string(game_name.begin(), game_name.end()); trim(m_gameName.value()); } - IniParser iniParser(*profileContents, gameProfilePath.string()); + IniParser iniParser(*profileContents, _pathToUtf8(gameProfilePath)); // parse ini while (iniParser.NextSection()) { diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 4594aed0..365e6e3e 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -28,7 +28,7 @@ void GraphicPack2::LoadGraphicPack(fs::path graphicPackPath) return; std::vector rulesData; fs_rules->extract(rulesData); - IniParser iniParser(rulesData, rulesPath.string()); + IniParser iniParser(rulesData, _pathToUtf8(rulesPath)); if (!iniParser.NextSection()) { @@ -51,10 +51,9 @@ void GraphicPack2::LoadGraphicPack(fs::path graphicPackPath) cemuLog_log(LogType::Force, "{}: Unable to parse version", _pathToUtf8(rulesPath)); return; } - if (versionNum > GP_LEGACY_VERSION) { - GraphicPack2::LoadGraphicPack(rulesPath.generic_wstring(), iniParser); + GraphicPack2::LoadGraphicPack(rulesPath, iniParser); return; } } @@ -79,22 +78,22 @@ void GraphicPack2::LoadAll() } } -bool GraphicPack2::LoadGraphicPack(const std::wstring& filename, IniParser& rules) +bool GraphicPack2::LoadGraphicPack(const fs::path& rulesPath, IniParser& rules) { try { - auto gp = std::make_shared(filename, rules); + auto gp = std::make_shared(rulesPath, rules); // check if enabled and preset set const auto& config_entries = g_config.data().graphic_pack_entries; // legacy absolute path checking for not breaking compatibility - auto file = gp->GetFilename2(); + auto file = gp->GetRulesPath(); auto it = config_entries.find(file.lexically_normal()); if (it == config_entries.cend()) { // check for relative path - it = config_entries.find(MakeRelativePath(ActiveSettings::GetUserDataPath(), gp->GetFilename2()).lexically_normal()); + it = config_entries.find(_utf8ToPath(gp->GetNormalizedPathString())); } if (it != config_entries.cend()) @@ -145,7 +144,7 @@ bool GraphicPack2::DeactivateGraphicPack(const std::shared_ptr& gr const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(), [graphic_pack](const GraphicPackPtr& gp) { - return gp->GetFilename() == graphic_pack->GetFilename(); + return gp->GetNormalizedPathString() == graphic_pack->GetNormalizedPathString(); } ); @@ -173,12 +172,12 @@ void GraphicPack2::ActivateForCurrentTitle() { if (gp->GetPresets().empty()) { - cemuLog_log(LogType::Force, "Activate graphic pack: {}", gp->GetPath()); + cemuLog_log(LogType::Force, "Activate graphic pack: {}", gp->GetVirtualPath()); } else { std::string logLine; - logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetPath())); + logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetVirtualPath())); bool isFirst = true; for (auto& itr : gp->GetPresets()) { @@ -216,12 +215,6 @@ void GraphicPack2::WaitUntilReady() std::this_thread::sleep_for(std::chrono::milliseconds(5)); } -GraphicPack2::GraphicPack2(std::wstring filename) - : m_filename(std::move(filename)) -{ - // unused for now -} - std::unordered_map GraphicPack2::ParsePresetVars(IniParser& rules) const { ExpressionParser parser; @@ -255,8 +248,8 @@ std::unordered_map GraphicPack2::ParsePres return vars; } -GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules) - : m_filename(std::move(filename)) +GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) + : m_rulesPath(std::move(rulesPath)) { // we're already in [Definition] auto option_version = rules.FindOption("version"); @@ -265,7 +258,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules) m_version = StringHelpers::ToInt(*option_version, -1); if (m_version < 0) { - cemuLog_log(LogType::Force, L"{}: Invalid version", m_filename); + cemuLog_log(LogType::Force, "{}: Invalid version", _pathToUtf8(m_rulesPath)); throw std::exception(); } @@ -311,7 +304,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules) cemuLog_log(LogType::Force, "[Definition] section from '{}' graphic pack must contain option: path", gp_name_log.has_value() ? *gp_name_log : "Unknown"); throw std::exception(); } - m_path = *option_path; + m_virtualPath = *option_path; auto option_gp_name = rules.FindOption("name"); if (option_gp_name) @@ -514,6 +507,11 @@ bool GraphicPack2::Reload() return Activate(); } +std::string GraphicPack2::GetNormalizedPathString() const +{ + return _pathToUtf8(MakeRelativePath(ActiveSettings::GetUserDataPath(), GetRulesPath()).lexically_normal()); +} + bool GraphicPack2::ContainsTitleId(uint64_t title_id) const { const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; }); @@ -656,7 +654,7 @@ bool GraphicPack2::SetActivePreset(std::string_view category, std::string_view n void GraphicPack2::LoadShaders() { - fs::path path(m_filename); + fs::path path = GetRulesPath(); for (auto& it : fs::directory_iterator(path.remove_filename())) { if (!is_regular_file(it)) @@ -682,7 +680,7 @@ void GraphicPack2::LoadShaders() { std::ifstream file(p); if (!file.is_open()) - throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str()); + throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename()))); file.seekg(0, std::ios::end); m_output_shader_source.reserve(file.tellg()); @@ -695,7 +693,7 @@ void GraphicPack2::LoadShaders() { std::ifstream file(p); if (!file.is_open()) - throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str()); + throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename()))); file.seekg(0, std::ios::end); m_upscaling_shader_source.reserve(file.tellg()); @@ -708,7 +706,7 @@ void GraphicPack2::LoadShaders() { std::ifstream file(p); if (!file.is_open()) - throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str()); + throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename()))); file.seekg(0, std::ios::end); m_downscaling_shader_source.reserve(file.tellg()); @@ -811,7 +809,7 @@ void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep) } } -void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, std::wstring& internalPath, bool isAOC) +void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC) { uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull; @@ -839,7 +837,7 @@ void GraphicPack2::LoadReplacedFiles() return; m_patchedFilesLoaded = true; - fs::path gfxPackPath(m_filename.c_str()); + fs::path gfxPackPath = GetRulesPath(); gfxPackPath = gfxPackPath.remove_filename(); // /content/ @@ -849,10 +847,9 @@ void GraphicPack2::LoadReplacedFiles() std::error_code ec; if (fs::exists(contentPath, ec)) { - std::wstring internalPath(L"/vol/content/"); // setup redirections fscDeviceRedirect_map(); - _iterateReplacedFiles(contentPath, internalPath, false); + _iterateReplacedFiles(contentPath, false); } // /aoc/ fs::path aocPath(gfxPackPath); @@ -863,13 +860,9 @@ void GraphicPack2::LoadReplacedFiles() uint64 aocTitleId = CafeSystem::GetForegroundTitleId(); aocTitleId = aocTitleId & 0xFFFFFFFFULL; aocTitleId |= 0x0005000c00000000ULL; - wchar_t internalAocPath[128]; - swprintf(internalAocPath, sizeof(internalAocPath)/sizeof(wchar_t), L"/aoc/%016llx/", aocTitleId); - - std::wstring internalPath(internalAocPath); // setup redirections fscDeviceRedirect_map(); - _iterateReplacedFiles(aocPath, internalPath, true); + _iterateReplacedFiles(aocPath, true); } } @@ -892,14 +885,14 @@ bool GraphicPack2::Activate() return false; } - FileStream* fs_rules = FileStream::openFile2({ m_filename }); + FileStream* fs_rules = FileStream::openFile2(m_rulesPath); if (!fs_rules) return false; std::vector rulesData; fs_rules->extract(rulesData); delete fs_rules; - IniParser rules({ (char*)rulesData.data(), rulesData.size()}, boost::nowide::narrow(m_filename)); + IniParser rules({ (char*)rulesData.data(), rulesData.size()}, GetNormalizedPathString()); // load rules try @@ -953,7 +946,7 @@ bool GraphicPack2::Activate() else if (anisotropyValue == 16) rule.overwrite_settings.anistropic_value = 4; else - cemuLog_log(LogType::Force, fmt::format(L"Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, m_filename)); + cemuLog_log(LogType::Force, "Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, GetNormalizedPathString()); } m_texture_rules.emplace_back(rule); } @@ -998,11 +991,11 @@ bool GraphicPack2::Activate() if (LatteTiming_getCustomVsyncFrequency(globalCustomVsyncFreq)) { if (customVsyncFreq != globalCustomVsyncFreq) - cemuLog_log(LogType::Force, "rules.txt error: Mismatching vsync frequency {} in graphic pack \'{}\'", customVsyncFreq, GetPath()); + cemuLog_log(LogType::Force, "rules.txt error: Mismatching vsync frequency {} in graphic pack \'{}\'", customVsyncFreq, GetVirtualPath()); } else { - cemuLog_log(LogType::Force, "Set vsync frequency to {} (graphic pack {})", customVsyncFreq, GetPath()); + cemuLog_log(LogType::Force, "Set vsync frequency to {} (graphic pack {})", customVsyncFreq, GetVirtualPath()); LatteTiming_setCustomVsyncFrequency(customVsyncFreq); } } diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index a087f757..6b07cce9 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -97,21 +97,20 @@ public: }; using PresetPtr = std::shared_ptr; - GraphicPack2(std::wstring filename); - GraphicPack2(std::wstring filename, IniParser& rules); + GraphicPack2(fs::path rulesPath, IniParser& rules); bool IsEnabled() const { return m_enabled; } bool IsActivated() const { return m_activated; } sint32 GetVersion() const { return m_version; } - const std::wstring& GetFilename() const { return m_filename; } - const fs::path GetFilename2() const { return fs::path(m_filename); } + const fs::path GetRulesPath() const { return m_rulesPath; } + std::string GetNormalizedPathString() const; bool RequiresRestart(bool changeEnableState, bool changePreset); bool Reload(); bool HasName() const { return !m_name.empty(); } - const std::string& GetName() const { return m_name.empty() ? m_path : m_name; } - const std::string& GetPath() const { return m_path; } + const std::string& GetName() const { return m_name.empty() ? m_virtualPath : m_name; } + 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; } @@ -165,7 +164,7 @@ public: static const std::vector>& GetGraphicPacks() { return s_graphic_packs; } static const std::vector>& GetActiveGraphicPacks() { return s_active_graphic_packs; } static void LoadGraphicPack(fs::path graphicPackPath); - static bool LoadGraphicPack(const std::wstring& filename, class IniParser& rules); + static bool LoadGraphicPack(const fs::path& rulesPath, class IniParser& rules); static bool ActivateGraphicPack(const std::shared_ptr& graphic_pack); static bool DeactivateGraphicPack(const std::shared_ptr& graphic_pack); static void ClearGraphicPacks(); @@ -209,11 +208,11 @@ private: parser.TryAddConstant(var.first, (TType)var.second.second); } - std::wstring m_filename; + fs::path m_rulesPath; sint32 m_version; std::string m_name; - std::string m_path; + std::string m_virtualPath; std::string m_description; bool m_default_enabled = false; @@ -258,7 +257,7 @@ private: CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const; void ApplyShaderPresets(std::string& shader_source) const; void LoadReplacedFiles(); - void _iterateReplacedFiles(const fs::path& currentPath, std::wstring& internalPath, bool isAOC); + void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC); // ram mappings std::vector> m_ramMappings; diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp index 162a5536..6b6915d7 100644 --- a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp @@ -41,9 +41,9 @@ void PatchErrorHandler::showStageErrorMessageBox() if (m_gp) { if (m_stage == STAGE::PARSER) - errorMsg.assign(fmt::format("Failed to load patches for graphic pack \'{}\'", m_gp->GetName())); + errorMsg = fmt::format("Failed to load patches for graphic pack \'{}\'", m_gp->GetName()); else - errorMsg.assign(fmt::format("Failed to apply patches for graphic pack \'{}\'", m_gp->GetName())); + errorMsg = fmt::format("Failed to apply patches for graphic pack \'{}\'", m_gp->GetName()); } else { @@ -51,11 +51,11 @@ void PatchErrorHandler::showStageErrorMessageBox() } if (cemuLog_isLoggingEnabled(LogType::Patches)) { - errorMsg.append("\n \nDetails:\n"); + errorMsg += "\n\nDetails:\n"; for (auto& itr : errorMessages) { - errorMsg.append(itr); - errorMsg.append("\n"); + errorMsg += itr; + errorMsg += "\n"; } } cemuLog_log(LogType::Force, "Graphic pack error: {}", errorMsg); @@ -65,19 +65,8 @@ void PatchErrorHandler::showStageErrorMessageBox() // returns true if at least one file was found even if it could not be successfully parsed bool GraphicPack2::LoadCemuPatches() { - // todo - once we have updated to C++20 we can replace these with the new std::string functions - auto startsWith = [](const std::wstring& str, const std::wstring& prefix) - { - return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix); - }; - - auto endsWith = [](const std::wstring& str, const std::wstring& suffix) - { - return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); - }; - bool foundPatches = false; - fs::path path(m_filename); + fs::path path(m_rulesPath); path.remove_filename(); for (auto& p : fs::directory_iterator(path)) { @@ -85,10 +74,10 @@ bool GraphicPack2::LoadCemuPatches() if (fs::is_regular_file(p.status()) && path.has_filename()) { // check if filename matches - std::wstring filename = path.filename().generic_wstring(); - if (boost::istarts_with(filename, L"patch_") && boost::iends_with(filename, L".asm")) + std::string filename = _pathToUtf8(path.filename()); + if (boost::istarts_with(filename, "patch_") && boost::iends_with(filename, ".asm")) { - FileStream* patchFile = FileStream::openFile(path.generic_wstring().c_str()); + FileStream* patchFile = FileStream::openFile2(path); if (patchFile) { // read file @@ -120,27 +109,20 @@ void GraphicPack2::LoadPatchFiles() // order of loading patches: // 1) Load Cemu-style patches (patch_.asm), stop here if at least one patch file exists // 2) Load Cemuhook patches.txt - - // update: As of 1.20.2b Cemu always takes over patching since Cemuhook patching broke due to other internal changes (memory allocation changed and some reordering on when graphic packs get loaded) if (LoadCemuPatches()) return; // exit if at least one Cemu style patch file was found // fall back to Cemuhook patches.txt to guarantee backward compatibility - fs::path path(m_filename); + fs::path path(m_rulesPath); path.remove_filename(); path.append("patches.txt"); - - FileStream* patchFile = FileStream::openFile(path.generic_wstring().c_str()); - + FileStream* patchFile = FileStream::openFile2(path); if (patchFile == nullptr) return; - // read file std::vector fileData; patchFile->extract(fileData); delete patchFile; - cemu_assert_debug(list_patchGroups.empty()); - // parse MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size()); ParseCemuhookPatchesTxtInternal(patchesStream); diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp index ce04bf93..05f8c696 100644 --- a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp @@ -25,7 +25,7 @@ sint32 GraphicPack2::GetLengthWithoutComment(const char* str, size_t length) void GraphicPack2::LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg) { - cemuLog_log(LogType::Force, fmt::format(L"Syntax error while parsing patch for graphic pack '{}':", this->GetFilename())); + cemuLog_log(LogType::Force, "Syntax error while parsing patch for graphic pack '{}':", _pathToUtf8(this->GetRulesPath())); if(lineNumber >= 0) cemuLog_log(LogType::Force, fmt::format("Line {0}: {1}", lineNumber, errorMsg)); else diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index defb9629..9438c350 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -1,8 +1,9 @@ #include "Debugger.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/OS/RPL/rpl.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cemu/ExpressionParser/ExpressionParser.h" - #include "Cafe/OS/libs/coreinit/coreinit.h" #if BOOST_OS_WINDOWS @@ -86,7 +87,7 @@ uint32 debugger_getAddressOriginalOpcode(uint32 address) auto bpItr = debugger_getFirstBP(address); while (bpItr) { - if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_ONE_SHOT) + if (bpItr->isExecuteBP()) return bpItr->originalOpcodeValue; bpItr = bpItr->next; } @@ -133,32 +134,23 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore) } } -void debugger_createExecuteBreakpoint(uint32 address) +void debugger_createCodeBreakpoint(uint32 address, uint8 bpType) { // check if breakpoint already exists auto existingBP = debugger_getFirstBP(address); - if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_NORMAL)) + if (existingBP && debuggerBPChain_hasType(existingBP, bpType)) return; // breakpoint already exists // get original opcode at address uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); // init breakpoint object - DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_NORMAL, true); + DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, bpType, true); debuggerBPChain_add(address, bp); debugger_updateExecutionBreakpoint(address); } -void debugger_createSingleShotExecuteBreakpoint(uint32 address) +void debugger_createExecuteBreakpoint(uint32 address) { - // check if breakpoint already exists - auto existingBP = debugger_getFirstBP(address); - if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_ONE_SHOT)) - return; // breakpoint already exists - // get original opcode at address - uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); - // init breakpoint object - DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_ONE_SHOT, true); - debuggerBPChain_add(address, bp); - debugger_updateExecutionBreakpoint(address); + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } namespace coreinit @@ -230,7 +222,8 @@ void debugger_handleSingleStepException(uint64 dr6) } if (catchBP) { - debugger_createSingleShotExecuteBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + debugger_createCodeBreakpoint(hCPU->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT); } } @@ -262,7 +255,7 @@ void debugger_handleEntryBreakpoint(uint32 address) if (!debuggerState.breakOnEntry) return; - debugger_createExecuteBreakpoint(address); + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } void debugger_deleteBreakpoint(DebuggerBreakpoint* bp) @@ -310,10 +303,12 @@ void debugger_toggleExecuteBreakpoint(uint32 address) { // delete existing breakpoint debugger_deleteBreakpoint(existingBP); - return; } - // create new - debugger_createExecuteBreakpoint(address); + else + { + // create new breakpoint + debugger_createExecuteBreakpoint(address); + } } void debugger_forceBreak() @@ -339,7 +334,7 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b { if (bpItr == bp) { - if (bpItr->bpType == DEBUGGER_BP_T_NORMAL) + if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING) { bp->enabled = state; debugger_updateExecutionBreakpoint(address); @@ -501,7 +496,7 @@ bool debugger_stepOver(PPCInterpreter_t* hCPU) return false; } // create one-shot breakpoint at next instruction - debugger_createSingleShotExecuteBreakpoint(initialIP +4); + debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT); // step over current instruction (to avoid breakpoint) debugger_stepInto(hCPU); if (sDebuggerCallbacks) @@ -522,10 +517,41 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i]; } +void DebugLogStackTrace(OSThread_t* thread, MPTR sp); + void debugger_enterTW(PPCInterpreter_t* hCPU) { + // handle logging points + DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer); + bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT); + while (bp) + { + if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled) + { + std::string logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address); + std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : ""); + cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext); + if (cemuLog_advancedPPCLoggingEnabled()) + DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]); + break; + } + bp = bp->next; + } + + // return early if it's only a non-pausing logging breakpoint to prevent a modified debugger state and GUI updates + if (!shouldBreak) + { + uint32 backupIP = debuggerState.debugSession.instructionPointer; + debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; + debugger_stepInto(hCPU, false); + PPCInterpreterSlim_executeInstruction(hCPU); + debuggerState.debugSession.instructionPointer = backupIP; + return; + } + + // handle breakpoints debuggerState.debugSession.isTrapped = true; - debuggerState.debugSession.debuggedThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(hCPU); + debuggerState.debugSession.debuggedThreadMPTR = MEMPTR(coreinit::OSGetCurrentThread()).GetMPTR(); debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; debuggerState.debugSession.hCPU = hCPU; debugger_createPPCStateSnapshot(hCPU); @@ -603,6 +629,20 @@ void debugger_shouldBreak(PPCInterpreter_t* hCPU) void debugger_addParserSymbols(class ExpressionParser& ep) { + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + + std::vector module_tmp(module_count); + for (int i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + if (module) + { + module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); + ep.AddConstant(module->moduleName2, module_tmp[i]); + } + } + for (sint32 i = 0; i < 32; i++) ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]); } \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h index 22015a8f..6901544a 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.h +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -7,6 +7,7 @@ #define DEBUGGER_BP_T_ONE_SHOT 1 // normal breakpoint, deletes itself after trigger (used for stepping) #define DEBUGGER_BP_T_MEMORY_READ 2 // memory breakpoint #define DEBUGGER_BP_T_MEMORY_WRITE 3 // memory breakpoint +#define DEBUGGER_BP_T_LOGGING 4 // logging breakpoint, prints the breakpoint comment and stack trace whenever hit #define DEBUGGER_BP_T_GDBSTUB 1 // breakpoint created by GDBStub #define DEBUGGER_BP_T_DEBUGGER 2 // breakpoint created by Cemu's debugger @@ -42,7 +43,7 @@ struct DebuggerBreakpoint bool isExecuteBP() const { - return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_ONE_SHOT; + return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_LOGGING || bpType == DEBUGGER_BP_T_ONE_SHOT; } bool isMemBP() const @@ -113,8 +114,9 @@ void debugger_unregisterDebuggerCallbacks(); DebuggerCallbacks* debugger_getDebuggerCallbacks(); DebuggerBreakpoint* debugger_getFirstBP(uint32 address); -void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint +void debugger_createCodeBreakpoint(uint32 address, uint8 bpType); void debugger_createExecuteBreakpoint(uint32 address); +void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp); void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite); diff --git a/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp new file mode 100644 index 00000000..675050d3 --- /dev/null +++ b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp @@ -0,0 +1,304 @@ +#include "GDBBreakpoints.h" +#include "Debugger.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" + +#if defined(ARCH_X86_64) && BOOST_OS_LINUX +#include +#include +#include + +DRType _GetDR(pid_t tid, int drIndex) +{ + size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]); + + long v; + v = ptrace(PTRACE_PEEKUSER, tid, drOffset, nullptr); + if (v == -1) + perror("ptrace(PTRACE_PEEKUSER)"); + + return (DRType)v; +} + +void _SetDR(pid_t tid, int drIndex, DRType newValue) +{ + size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]); + + long rc = ptrace(PTRACE_POKEUSER, tid, drOffset, newValue); + if (rc == -1) + perror("ptrace(PTRACE_POKEUSER)"); +} + +DRType _ReadDR6() +{ + pid_t tid = gettid(); + + // linux doesn't let us attach to the current thread / threads in the current thread group + // we have to create a child process which then modifies the debug registers and quits + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return 0; + } + + if (child == 0) + { + if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr)) + { + perror("attach"); + _exit(0); + } + + waitpid(tid, NULL, 0); + + uint64_t dr6 = _GetDR(tid, 6); + + if (ptrace(PTRACE_DETACH, tid, nullptr, nullptr)) + perror("detach"); + + // since the status code only uses the lower 8 bits, we have to discard the rest of DR6 + // this should be fine though, since the lower 4 bits of DR6 contain all the bp conditions + _exit(dr6 & 0xff); + } + + // wait for child process + int wstatus; + waitpid(child, &wstatus, 0); + + return (DRType)WEXITSTATUS(wstatus); +} +#endif + +GDBServer::ExecutionBreakpoint::ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason) + : m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason)) +{ + if (type == BreakpointType::BP_SINGLE) + { + this->m_pauseThreads = true; + this->m_restoreAfterInterrupt = false; + this->m_deleteAfterAnyInterrupt = false; + this->m_pauseOnNextInterrupt = false; + this->m_visible = visible; + } + else if (type == BreakpointType::BP_PERSISTENT) + { + this->m_pauseThreads = true; + this->m_restoreAfterInterrupt = true; + this->m_deleteAfterAnyInterrupt = false; + this->m_pauseOnNextInterrupt = false; + this->m_visible = visible; + } + else if (type == BreakpointType::BP_RESTORE_POINT) + { + this->m_pauseThreads = false; + this->m_restoreAfterInterrupt = false; + this->m_deleteAfterAnyInterrupt = false; + this->m_pauseOnNextInterrupt = false; + this->m_visible = false; + } + else if (type == BreakpointType::BP_STEP_POINT) + { + this->m_pauseThreads = false; + this->m_restoreAfterInterrupt = false; + this->m_deleteAfterAnyInterrupt = true; + this->m_pauseOnNextInterrupt = true; + this->m_visible = false; + } + + this->m_origOpCode = memory_readU32(address); + memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW); + PPCRecompiler_invalidateRange(address, address + 4); +} + +GDBServer::ExecutionBreakpoint::~ExecutionBreakpoint() +{ + memory_writeU32(this->m_address, this->m_origOpCode); + PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); +} + +uint32 GDBServer::ExecutionBreakpoint::GetVisibleOpCode() const +{ + if (this->m_visible) + return memory_readU32(this->m_address); + else + return this->m_origOpCode; +} + +void GDBServer::ExecutionBreakpoint::RemoveTemporarily() +{ + memory_writeU32(this->m_address, this->m_origOpCode); + PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); + this->m_restoreAfterInterrupt = true; +} + +void GDBServer::ExecutionBreakpoint::Restore() +{ + memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW); + PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); + this->m_restoreAfterInterrupt = false; +} + +namespace coreinit +{ +#if BOOST_OS_LINUX + std::vector& OSGetSchedulerThreadIds(); +#endif + + std::vector& OSGetSchedulerThreads(); +} + +GDBServer::AccessBreakpoint::AccessBreakpoint(MPTR address, AccessPointType type) + : m_address(address), m_type(type) +{ +#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS + for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) + { + HANDLE hThread = (HANDLE)hThreadNH; + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); + + // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already + ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address); + ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address); + // breakpoint 2 + SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true + SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) + SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) + // breakpoint 3 + SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true + SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) + SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) + + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } +#elif defined(ARCH_X86_64) && BOOST_OS_LINUX + // linux doesn't let us attach to threads which are in the same thread group as our current thread + // we have to create a child process which then modifies the debug registers and quits + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return; + } + + if (child == 0) + { + for (pid_t tid : coreinit::OSGetSchedulerThreadIds()) + { + long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_ATTACH)"); + + waitpid(tid, nullptr, 0); + + DRType dr7 = _GetDR(tid, 7); + // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already + DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address); + DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address); + // breakpoint 2 + SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true + SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) + SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) + // breakpoint 3 + SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true + SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) + SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) + + _SetDR(tid, 2, dr2); + _SetDR(tid, 3, dr3); + _SetDR(tid, 7, dr7); + + rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_DETACH)"); + } + + // exit child process + _exit(0); + } + + // wait for child process + waitpid(child, nullptr, 0); +#else + cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet."); +#endif +} + +GDBServer::AccessBreakpoint::~AccessBreakpoint() +{ +#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS + for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) + { + HANDLE hThread = (HANDLE)hThreadNH; + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); + + // reset BP 2/3 to zero + ctx.Dr2 = (DWORD64)0; + ctx.Dr3 = (DWORD64)0; + // breakpoint 2 + SetBits(ctx.Dr7, 4, 1, 0); + SetBits(ctx.Dr7, 24, 2, 0); + SetBits(ctx.Dr7, 26, 2, 0); + // breakpoint 3 + SetBits(ctx.Dr7, 6, 1, 0); + SetBits(ctx.Dr7, 28, 2, 0); + SetBits(ctx.Dr7, 30, 2, 0); + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } +#elif defined(ARCH_X86_64) && BOOST_OS_LINUX + // linux doesn't let us attach to threads which are in the same thread group as our current thread + // we have to create a child process which then modifies the debug registers and quits + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return; + } + + if (child == 0) + { + for (pid_t tid : coreinit::OSGetSchedulerThreadIds()) + { + long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_ATTACH)"); + + waitpid(tid, nullptr, 0); + + DRType dr7 = _GetDR(tid, 7); + // reset BP 2/3 to zero + DRType dr2 = 0; + DRType dr3 = 0; + // breakpoint 2 + SetBits(dr7, 4, 1, 0); + SetBits(dr7, 24, 2, 0); + SetBits(dr7, 26, 2, 0); + // breakpoint 3 + SetBits(dr7, 6, 1, 0); + SetBits(dr7, 28, 2, 0); + SetBits(dr7, 30, 2, 0); + + _SetDR(tid, 2, dr2); + _SetDR(tid, 3, dr3); + _SetDR(tid, 7, dr7); + + rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_DETACH)"); + } + + // exit child process + _exit(0); + } + + // wait for child process + waitpid(child, nullptr, 0); +#endif +} diff --git a/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h index b86bd9a6..f94365c2 100644 --- a/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h +++ b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h @@ -1,33 +1,18 @@ +#pragma once +#include "GDBStub.h" #include -#if defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE -#include -#include -#include +#if defined(ARCH_X86_64) && BOOST_OS_LINUX +#include // helpers for accessing debug register typedef unsigned long DRType; -DRType _GetDR(pid_t tid, int drIndex) -{ - unsigned long v; - v = ptrace (PTRACE_PEEKUSER, tid, offsetof (struct user, u_debugreg[drIndex]), 0); - return (DRType)v; -} - -void _SetDR(pid_t tid, int drIndex, DRType newValue) -{ - unsigned long v = newValue; - ptrace (PTRACE_POKEUSER, tid, offsetof (struct user, u_debugreg[drIndex]), v); -} - +DRType _GetDR(pid_t tid, int drIndex); +void _SetDR(pid_t tid, int drIndex, DRType newValue); +DRType _ReadDR6(); #endif -namespace coreinit -{ - std::vector& OSGetSchedulerThreads(); -} - enum class BreakpointType { BP_SINGLE, @@ -38,59 +23,10 @@ enum class BreakpointType class GDBServer::ExecutionBreakpoint { public: - ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason) - : m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason)) - { - if (type == BreakpointType::BP_SINGLE) - { - this->m_pauseThreads = true; - this->m_restoreAfterInterrupt = false; - this->m_deleteAfterAnyInterrupt = false; - this->m_pauseOnNextInterrupt = false; - this->m_visible = visible; - } - else if (type == BreakpointType::BP_PERSISTENT) - { - this->m_pauseThreads = true; - this->m_restoreAfterInterrupt = true; - this->m_deleteAfterAnyInterrupt = false; - this->m_pauseOnNextInterrupt = false; - this->m_visible = visible; - } - else if (type == BreakpointType::BP_RESTORE_POINT) - { - this->m_pauseThreads = false; - this->m_restoreAfterInterrupt = false; - this->m_deleteAfterAnyInterrupt = false; - this->m_pauseOnNextInterrupt = false; - this->m_visible = false; - } - else if (type == BreakpointType::BP_STEP_POINT) - { - this->m_pauseThreads = false; - this->m_restoreAfterInterrupt = false; - this->m_deleteAfterAnyInterrupt = true; - this->m_pauseOnNextInterrupt = true; - this->m_visible = false; - } + ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason); + ~ExecutionBreakpoint(); - this->m_origOpCode = memory_readU32(address); - memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW); - PPCRecompiler_invalidateRange(address, address + 4); - }; - ~ExecutionBreakpoint() - { - memory_writeU32(this->m_address, this->m_origOpCode); - PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); - }; - - [[nodiscard]] uint32 GetVisibleOpCode() const - { - if (this->m_visible) - return memory_readU32(this->m_address); - else - return this->m_origOpCode; - }; + [[nodiscard]] uint32 GetVisibleOpCode() const; [[nodiscard]] bool ShouldBreakThreads() const { return this->m_pauseThreads; @@ -118,18 +54,8 @@ public: return m_reason; }; - void RemoveTemporarily() - { - memory_writeU32(this->m_address, this->m_origOpCode); - PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); - this->m_restoreAfterInterrupt = true; - }; - void Restore() - { - memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW); - PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); - this->m_restoreAfterInterrupt = false; - }; + void RemoveTemporarily(); + void Restore(); void PauseOnNextInterrupt() { this->m_pauseOnNextInterrupt = true; @@ -162,115 +88,8 @@ enum class AccessPointType class GDBServer::AccessBreakpoint { public: - AccessBreakpoint(MPTR address, AccessPointType type) - : m_address(address), m_type(type) - { -#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - HANDLE hThread = (HANDLE)hThreadNH; - CONTEXT ctx{}; - ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; - SuspendThread(hThread); - GetThreadContext(hThread, &ctx); - - // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already - ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address); - ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address); - // breakpoint 2 - SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true - SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) - SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) - // breakpoint 3 - SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true - SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) - SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) - - SetThreadContext(hThread, &ctx); - ResumeThread(hThread); - } - // todo: port the following code to all unix platforms, they seem to differ quite a bit -#elif defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - pid_t pid = (pid_t)(uintptr_t)hThreadNH; - ptrace(PTRACE_ATTACH, pid, nullptr, nullptr); - waitpid(pid, nullptr, 0); - - DRType dr7 = _GetDR(pid, 7); - // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already - DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address); - DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address); - // breakpoint 2 - SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true - SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) - SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) - // breakpoint 3 - SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true - SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) - SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) - - _SetDR(pid, 2, dr2); - _SetDR(pid, 3, dr3); - _SetDR(pid, 7, dr7); - ptrace(PTRACE_DETACH, pid, nullptr, nullptr); - } -#else - cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet."); -#endif - }; - ~AccessBreakpoint() - { -#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - HANDLE hThread = (HANDLE)hThreadNH; - CONTEXT ctx{}; - ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; - SuspendThread(hThread); - GetThreadContext(hThread, &ctx); - - // reset BP 2/3 to zero - ctx.Dr2 = (DWORD64)0; - ctx.Dr3 = (DWORD64)0; - // breakpoint 2 - SetBits(ctx.Dr7, 4, 1, 0); - SetBits(ctx.Dr7, 24, 2, 0); - SetBits(ctx.Dr7, 26, 2, 0); - // breakpoint 3 - SetBits(ctx.Dr7, 6, 1, 0); - SetBits(ctx.Dr7, 28, 2, 0); - SetBits(ctx.Dr7, 30, 2, 0); - SetThreadContext(hThread, &ctx); - ResumeThread(hThread); - } -#elif defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - pid_t pid = (pid_t)(uintptr_t)hThreadNH; - ptrace(PTRACE_ATTACH, pid, nullptr, nullptr); - waitpid(pid, nullptr, 0); - - DRType dr7 = _GetDR(pid, 7); - // reset BP 2/3 to zero - DRType dr2 = 0; - DRType dr3 = 0; - // breakpoint 2 - SetBits(dr7, 4, 1, 0); - SetBits(dr7, 24, 2, 0); - SetBits(dr7, 26, 2, 0); - // breakpoint 3 - SetBits(dr7, 6, 1, 0); - SetBits(dr7, 28, 2, 0); - SetBits(dr7, 30, 2, 0); - - _SetDR(pid, 2, dr2); - _SetDR(pid, 3, dr3); - _SetDR(pid, 7, dr7); - ptrace(PTRACE_DETACH, pid, nullptr, nullptr); - } -#endif - }; + AccessBreakpoint(MPTR address, AccessPointType type); + ~AccessBreakpoint(); MPTR GetAddress() const { @@ -284,4 +103,4 @@ public: private: const MPTR m_address; const AccessPointType m_type; -}; \ No newline at end of file +}; diff --git a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp index d83ea46b..6cddae01 100644 --- a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp +++ b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp @@ -263,6 +263,14 @@ bool GDBServer::Initialize() return false; } + int nodelayEnabled = TRUE; + if (setsockopt(m_server_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelayEnabled, sizeof(nodelayEnabled)) == SOCKET_ERROR) + { + closesocket(m_server_socket); + m_server_socket = INVALID_SOCKET; + return false; + } + memset(&m_server_addr, 0, sizeof(m_server_addr)); m_server_addr.sin_family = AF_INET; m_server_addr.sin_addr.s_addr = htonl(INADDR_ANY); @@ -356,7 +364,7 @@ void GDBServer::ThreadFunc() } char checkSumStr[2]; receiveMessage(checkSumStr, 2); - uint32_t checkSum = std::stoi(checkSumStr, nullptr, 16); + uint32_t checkSum = std::stoi(std::string(checkSumStr, sizeof(checkSumStr)), nullptr, 16); assert((checkedSum & 0xFF) == checkSum); HandleCommand(message); @@ -900,7 +908,7 @@ void GDBServer::HandleTrapInstruction(PPCInterpreter_t* hCPU) return cemu_assert_suspicious(); // Secondly, delete one-shot breakpoints but also temporarily delete patched instruction to run original instruction - OSThread_t* currThread = coreinitThread_getCurrentThreadDepr(hCPU); + OSThread_t* currThread = coreinit::OSGetCurrentThread(); std::string pauseReason = fmt::format("T05thread:{:08X};core:{:02X};{}", GET_THREAD_ID(currThread), PPCInterpreter_getCoreIndex(hCPU), patchedBP->second.GetReason()); bool pauseThreads = patchedBP->second.ShouldBreakThreads() || patchedBP->second.ShouldBreakThreadsOnNextInterrupt(); if (patchedBP->second.IsPersistent()) @@ -939,7 +947,7 @@ void GDBServer::HandleTrapInstruction(PPCInterpreter_t* hCPU) ThreadPool::FireAndForget(&waitForBrokenThreads, std::move(m_resumed_context), pauseReason); } - breakThreads(GET_THREAD_ID(coreinitThread_getCurrentThreadDepr(hCPU))); + breakThreads(GET_THREAD_ID(coreinit::OSGetCurrentThread())); cemuLog_logDebug(LogType::Force, "[GDBStub] Resumed from a breakpoint!"); } } @@ -959,8 +967,9 @@ void GDBServer::HandleAccessException(uint64 dr6) if (!response.empty()) { + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); cemuLog_logDebug(LogType::Force, "Received matching breakpoint exception: {}", response); - auto nextInstructions = findNextInstruction(ppcInterpreterCurrentInstance->instructionPointer, ppcInterpreterCurrentInstance->spr.LR, ppcInterpreterCurrentInstance->spr.CTR); + auto nextInstructions = findNextInstruction(hCPU->instructionPointer, hCPU->spr.LR, hCPU->spr.CTR); for (MPTR nextInstr : nextInstructions) { auto bpIt = m_patchedInstructions.find(nextInstr); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp index 6aa1fcfa..24219e66 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp @@ -19,7 +19,7 @@ void PPCInterpreter_handleUnsupportedHLECall(PPCInterpreter_t* hCPU) std::vector* sPPCHLETable{}; -HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall) +HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName) { if (!sPPCHLETable) sPPCHLETable = new std::vector(); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp index 295865f5..36166f1c 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -6,7 +6,6 @@ thread_local PPCInterpreter_t* ppcInterpreterCurrentInstance; // main thread instruction counter and timing -volatile uint64 ppcMainThreadCycleCounter = 0; uint64 ppcMainThreadDECCycleValue = 0; // value that was set to dec register uint64 ppcMainThreadDECCycleStart = 0; // at which cycle the dec register was set, if == 0 -> dec is 0 uint64 ppcCyclesSince2000 = 0; @@ -29,11 +28,16 @@ PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint) return pData; } -PPCInterpreter_t* PPCInterpreter_getCurrentInstance() +TLS_WORKAROUND_NOINLINE PPCInterpreter_t* PPCInterpreter_getCurrentInstance() { return ppcInterpreterCurrentInstance; } +TLS_WORKAROUND_NOINLINE void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU) +{ + ppcInterpreterCurrentInstance = hCPU; +} + uint64 PPCInterpreter_getMainCoreCycleCounter() { return PPCTimer_getFromRDTSC(); @@ -86,24 +90,25 @@ uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU) uint32 PPCInterpreter_getCurrentCoreIndex() { - return ppcInterpreterCurrentInstance->spr.UPIR; + return PPCInterpreter_getCurrentInstance()->spr.UPIR; }; uint8* PPCInterpreterGetStackPointer() { - return memory_getPointerFromVirtualOffset(ppcInterpreterCurrentInstance->gpr[1]); + return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]); } uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset) { - uint8* result = memory_getPointerFromVirtualOffset(ppcInterpreterCurrentInstance->gpr[1] - offset); - ppcInterpreterCurrentInstance->gpr[1] -= offset; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset); + hCPU->gpr[1] -= offset; return result; } void PPCInterpreterModifyStackPointer(sint32 offset) { - ppcInterpreterCurrentInstance->gpr[1] -= offset; + PPCInterpreter_getCurrentInstance()->gpr[1] -= offset; } uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU)); diff --git a/src/Cafe/HW/Espresso/PPCCallback.h b/src/Cafe/HW/Espresso/PPCCallback.h index fd790f0c..19fcd4d1 100644 --- a/src/Cafe/HW/Espresso/PPCCallback.h +++ b/src/Cafe/HW/Espresso/PPCCallback.h @@ -18,19 +18,20 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, { cemu_assert_debug(data.gprCount <= 8); cemu_assert_debug(data.floatCount <= 8); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if constexpr (std::is_pointer_v) { - ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); + hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); data.gprCount++; } else if constexpr (std::is_base_of_v>) { - ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = currentArg.GetMPTR(); + hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR(); data.gprCount++; } else if constexpr (std::is_reference_v) { - ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); + hCPU->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); data.gprCount++; } else if constexpr(std::is_enum_v) @@ -40,19 +41,19 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, } else if constexpr (std::is_floating_point_v) { - ppcInterpreterCurrentInstance->fpr[1 + data.floatCount].fpr = (double)currentArg; + hCPU->fpr[1 + data.floatCount].fpr = (double)currentArg; data.floatCount++; } else if constexpr (std::is_integral_v && sizeof(T) == sizeof(uint64)) { - ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = (uint32)(currentArg >> 32); // high - ppcInterpreterCurrentInstance->gpr[3 + data.gprCount + 1] = (uint32)currentArg; // low + hCPU->gpr[3 + data.gprCount] = (uint32)(currentArg >> 32); // high + hCPU->gpr[3 + data.gprCount + 1] = (uint32)currentArg; // low data.gprCount += 2; } else { - ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = (uint32)currentArg; + hCPU->gpr[3 + data.gprCount] = (uint32)currentArg; data.gprCount++; } diff --git a/src/Cafe/HW/Espresso/PPCScheduler.cpp b/src/Cafe/HW/Espresso/PPCScheduler.cpp index 2a3a4aaa..a4c04aaa 100644 --- a/src/Cafe/HW/Espresso/PPCScheduler.cpp +++ b/src/Cafe/HW/Espresso/PPCScheduler.cpp @@ -11,21 +11,24 @@ uint32 ppcThreadQuantum = 45000; // execute 45000 instructions before thread res void PPCInterpreter_relinquishTimeslice() { - if( ppcInterpreterCurrentInstance->remainingCycles >= 0 ) + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + if( hCPU->remainingCycles >= 0 ) { - ppcInterpreterCurrentInstance->skippedCycles = ppcInterpreterCurrentInstance->remainingCycles + 1; - ppcInterpreterCurrentInstance->remainingCycles = -1; + hCPU->skippedCycles = hCPU->remainingCycles + 1; + hCPU->remainingCycles = -1; } } void PPCCore_boostQuantum(sint32 numCycles) { - ppcInterpreterCurrentInstance->remainingCycles += numCycles; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + hCPU->remainingCycles += numCycles; } void PPCCore_deboostQuantum(sint32 numCycles) { - ppcInterpreterCurrentInstance->remainingCycles -= numCycles; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + hCPU->remainingCycles -= numCycles; } namespace coreinit @@ -36,7 +39,7 @@ namespace coreinit void PPCCore_switchToScheduler() { cemu_assert_debug(__OSHasSchedulerLock() == false); // scheduler lock must not be hold past thread time slice - cemu_assert_debug(ppcInterpreterCurrentInstance->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); + cemu_assert_debug(PPCInterpreter_getCurrentInstance()->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); __OSLockScheduler(); coreinit::__OSThreadSwitchToNext(); __OSUnlockScheduler(); @@ -45,7 +48,7 @@ void PPCCore_switchToScheduler() void PPCCore_switchToSchedulerWithLock() { cemu_assert_debug(__OSHasSchedulerLock() == true); // scheduler lock must be hold - cemu_assert_debug(ppcInterpreterCurrentInstance->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); + cemu_assert_debug(PPCInterpreter_getCurrentInstance()->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); coreinit::__OSThreadSwitchToNext(); } @@ -58,7 +61,7 @@ void _PPCCore_callbackExit(PPCInterpreter_t* hCPU) PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR) { cemu_assert_debug(functionMPTR != 0); - PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); // remember LR and instruction pointer uint32 lr = hCPU->spr.LR; uint32 ip = hCPU->instructionPointer; diff --git a/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp b/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp index 8ef54256..bb4bf9ff 100644 --- a/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp +++ b/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp @@ -220,7 +220,7 @@ void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint) for (uint32 coreIndex = 0; coreIndex < 3; coreIndex++) { PPCInterpreter_t* hCPU = cpuContext->cores+coreIndex; - ppcInterpreterCurrentInstance = hCPU; + PPCInterpreter_setCurrentInstance(hCPU); if (coreIndex == 1) { // check SCR core 1 enable bit diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index b14e33c6..558add98 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -152,6 +152,7 @@ static uint64 PPCInterpreter_getCallParamU64(PPCInterpreter_t* hCPU, uint32 inde PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint); PPCInterpreter_t* PPCInterpreter_getCurrentInstance(); +void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU); uint64 PPCInterpreter_getMainCoreCycleCounter(); @@ -195,7 +196,6 @@ uint32 PPCInterpreter_getCurrentCoreIndex(); void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue); // timing for main processor -extern volatile uint64 ppcMainThreadCycleCounter; extern uint64 ppcCyclesSince2000; // on init this is set to the cycles that passed since 1.1.2000 extern uint64 ppcCyclesSince2000TimerClock; // on init this is set to the cycles that passed since 1.1.2000 / 20 extern uint64 ppcCyclesSince2000_UTC; @@ -216,7 +216,6 @@ void PPCTimer_start(); // core info and control extern uint32 ppcThreadQuantum; -extern thread_local PPCInterpreter_t *ppcInterpreterCurrentInstance; uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); uint8* PPCInterpreterGetStackPointer(); void PPCInterpreterModifyStackPointer(sint32 offset); @@ -234,7 +233,7 @@ static inline float flushDenormalToZero(float f) typedef void(*HLECALL)(PPCInterpreter_t* hCPU); typedef sint32 HLEIDX; -HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall); +HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName); HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex); // HLE scheduler diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 264e8af9..b15ae77d 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -464,6 +464,8 @@ void PPCRecompiler_thread() PPCRecompilerState.recompilerSpinlock.unlock(); PPCRecompiler_recompileAtAddress(enterAddress); + if(s_recompilerThreadStopSignal) + return; } } } diff --git a/src/Cafe/HW/Latte/Core/FetchShader.cpp b/src/Cafe/HW/Latte/Core/FetchShader.cpp index b4beba4e..6c9893f9 100644 --- a/src/Cafe/HW/Latte/Core/FetchShader.cpp +++ b/src/Cafe/HW/Latte/Core/FetchShader.cpp @@ -228,13 +228,13 @@ void _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(LatteFetchShader* pars else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Y) { // use alu divisor 1 - attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[mmVGT_INSTANCE_STEP_RATE_0 + 0]; + attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0]; cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0); } else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Z) { // use alu divisor 2 - attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[mmVGT_INSTANCE_STEP_RATE_0 + 1]; + attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[Latte::REGADDR::VGT_INSTANCE_STEP_RATE_1]; cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0); } } diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index ed2116d0..dc3cbc91 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -84,6 +84,10 @@ extern uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring bu void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd); // render target + +#define RENDER_TARGET_TV (1 << 0) +#define RENDER_TARGET_DRC (1 << 2) + void LatteRenderTarget_updateScissorBox(); void LatteRenderTarget_trackUpdates(); @@ -111,7 +115,7 @@ void LatteTC_RegisterTexture(LatteTexture* tex); void LatteTC_UnregisterTexture(LatteTexture* tex); uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture); -void LatteTexture_ReloadData(LatteTexture* hostTexture, uint32 textureUnit); +void LatteTexture_ReloadData(LatteTexture* hostTexture); bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force = false); void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force = false); @@ -163,7 +167,7 @@ void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, void LatteRenderTarget_updateViewport(); -#define LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE (1024) // maximum size for uniform blocks (in vec4s). On Nvidia hardware 4096 is the maximum (64K / 16 = 4096) all other vendors have much higher limits +#define LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE (4096) // maximum size for uniform blocks (in vec4s). On Nvidia hardware 4096 is the maximum (64K / 16 = 4096) all other vendors have much higher limits //static uint32 glTempError; //#define catchOpenGLError() glFinish(); if( (glTempError = glGetError()) != 0 ) { printf("OpenGL error 0x%x: %s : %d timestamp %08x\n", glTempError, __FILE__, __LINE__, GetTickCount()); __debugbreak(); } diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index d31a8651..85d4cdf7 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -132,22 +132,18 @@ void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const { if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { - // use full uniform buffers - for (sint32 t = 0; t < shader->uniformBufferListCount; t++) + for(const auto& buf : shader->list_quickBufferList) { - sint32 i = shader->uniformBufferList[t]; + sint32 i = buf.index; MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; - - if (physicalAddr == MPTR_NULL) + if (physicalAddr == MPTR_NULL) [[unlikely]] { - // no data g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0); continue; } - + uniformSize = std::min(uniformSize, buf.size); uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize); - g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize); } } diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 37ce8ff9..60e5935c 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -16,9 +16,17 @@ #include "Cafe/CafeSystem.h" +#include + +void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size); + #define CP_TIMER_RECHECK 1024 -//#define FAST_DRAW_LOGGING +//#define LATTE_CP_LOGGING + +typedef uint32be* LatteCMDPtr; +#define LatteReadCMD() ((uint32)*(cmd++)) +#define LatteSkipCMD(_nWords) cmd += (_nWords) uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list) uint8* gx2CPParserDisplayListPtr; @@ -31,6 +39,14 @@ void LatteThread_Exit(); class DrawPassContext { + struct CmdQueuePos + { + CmdQueuePos(LatteCMDPtr current, LatteCMDPtr start, LatteCMDPtr end) : current(current), start(start), end(end) {}; + + LatteCMDPtr current; + LatteCMDPtr start; + LatteCMDPtr end; + }; public: bool isWithinDrawPass() const { @@ -54,6 +70,13 @@ public: if (numInstances == 0) return; + /* + if (GetAsyncKeyState('B')) + { + cemuLog_force("[executeDraw] {} Count {} BaseVertex {} BaseInstance {}", m_isFirstDraw?"Init":"Fast", count, baseVertex, baseInstance); + } + */ + if (!isAutoIndex) { cemu_assert_debug(physIndices != MPTR_NULL); @@ -66,6 +89,9 @@ public: { g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_isFirstDraw); } + performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; + if (!m_isFirstDraw) + performanceMonitor.cycle[performanceMonitor.cycleIndex].fastDrawCallCounter++; m_isFirstDraw = false; m_vertexBufferChanged = false; m_uniformBufferChanged = false; @@ -87,14 +113,33 @@ public: m_uniformBufferChanged = true; } + // command buffer processing position + void PushCurrentCommandQueuePos(LatteCMDPtr current, LatteCMDPtr start, LatteCMDPtr end) + { + m_queuePosStack.emplace_back(current, start, end); + } + + bool PopCurrentCommandQueuePos(LatteCMDPtr& current, LatteCMDPtr& start, LatteCMDPtr& end) + { + if (m_queuePosStack.empty()) + return false; + const auto& it = m_queuePosStack.back(); + current = it.current; + start = it.start; + end = it.end; + m_queuePosStack.pop_back(); + return true; + } + private: bool m_drawPassActive{ false }; bool m_isFirstDraw{false}; bool m_vertexBufferChanged{ false }; bool m_uniformBufferChanged{ false }; + boost::container::small_vector m_queuePosStack; }; -void LatteCP_processCommandBuffer(uint8* cmdBuffer, sint32 cmdSize, DrawPassContext& drawPassCtx); +void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx); /* * Read a U32 from the command buffer @@ -193,10 +238,6 @@ void LatteCP_skipWords(uint32 wordsToSkip) } } -typedef uint32be* LatteCMDPtr; -#define LatteReadCMD() ((uint32)*(cmd++)) -#define LatteSkipCMD(_nWords) cmd += (_nWords) - LatteCMDPtr LatteCP_itSurfaceSync(LatteCMDPtr cmd) { uint32 invalidationFlags = LatteReadCMD(); @@ -215,22 +256,31 @@ LatteCMDPtr LatteCP_itSurfaceSync(LatteCMDPtr cmd) return cmd; } -template -void LatteCP_itIndirectBufferDepr(uint32 nWords) +// called from TCL command queue. Executes a memory command buffer +void LatteCP_itIndirectBufferDepr(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 3); - - uint32 physicalAddress = readU32(); - uint32 physicalAddressHigh = readU32(); // unused - uint32 sizeInDWords = readU32(); + uint32 physicalAddress = LatteReadCMD(); + uint32 physicalAddressHigh = LatteReadCMD(); // unused + uint32 sizeInDWords = LatteReadCMD(); uint32 displayListSize = sizeInDWords * 4; DrawPassContext drawPassCtx; - LatteCP_processCommandBuffer(memory_getPointerFromPhysicalOffset(physicalAddress), displayListSize, drawPassCtx); + +#ifdef LATTE_CP_LOGGING + if (GetAsyncKeyState('A')) + LatteCP_DebugPrintCmdBuffer(MEMPTR(physicalAddress), displayListSize); +#endif + + uint32be* buf = MEMPTR(physicalAddress).GetPtr(); + drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords); + + LatteCP_processCommandBuffer(drawPassCtx); if (drawPassCtx.isWithinDrawPass()) drawPassCtx.endDrawPass(); } -LatteCMDPtr LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) +// pushes the command buffer to the stack +void LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) { cemu_assert_debug(nWords == 3); uint32 physicalAddress = LatteReadCMD(); @@ -239,8 +289,8 @@ LatteCMDPtr LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassCon uint32 displayListSize = sizeInDWords * 4; cemu_assert_debug(displayListSize >= 4); - LatteCP_processCommandBuffer(memory_getPointerFromPhysicalOffset(physicalAddress), displayListSize, drawPassCtx); - return cmd; + uint32be* buf = MEMPTR(physicalAddress).GetPtr(); + drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords); } LatteCMDPtr LatteCP_itStreamoutBufferUpdate(LatteCMDPtr cmd, uint32 nWords) @@ -615,8 +665,6 @@ LatteCMDPtr LatteCP_itDrawIndex2(LatteCMDPtr cmd, uint32 nWords, DrawPassContext uint32 count = LatteReadCMD(); uint32 ukn3 = LatteReadCMD(); - performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; - LatteGPUState.currentDrawCallTick = GetTickCount(); drawPassCtx.executeDraw(count, false, physIndices); return cmd; @@ -628,8 +676,6 @@ LatteCMDPtr LatteCP_itDrawIndexAuto(LatteCMDPtr cmd, uint32 nWords, DrawPassCont uint32 count = LatteReadCMD(); uint32 ukn = LatteReadCMD(); - performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; - if (LatteGPUState.drawContext.numInstances == 0) return cmd; LatteGPUState.currentDrawCallTick = GetTickCount(); @@ -692,7 +738,6 @@ LatteCMDPtr LatteCP_itDrawImmediate(LatteCMDPtr cmd, uint32 nWords, DrawPassCont // verify packet size if (nWords != (2 + numIndexU32s)) debugBreakpoint(); - performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; uint32 baseVertex = LatteGPUState.contextRegister[mmSQ_VTX_BASE_VTX_LOC]; uint32 baseInstance = LatteGPUState.contextRegister[mmSQ_VTX_START_INST_LOC]; @@ -930,431 +975,412 @@ void LatteCP_dumpCommandBufferError(LatteCMDPtr cmdStart, LatteCMDPtr cmdEnd, La } // any drawcalls issued without changing textures, framebuffers, shader or other complex states can be done quickly without having to reinitialize the entire pipeline state -// we implement this optimization by having an optimized version of LatteCP_processCommandBuffer, called right after drawcalls, which only implements commands that dont interfere with fast drawing. Other commands will cause this function to return to the complex parser -LatteCMDPtr LatteCP_processCommandBuffer_continuousDrawPass(LatteCMDPtr cmd, LatteCMDPtr cmdStart, LatteCMDPtr cmdEnd, DrawPassContext& drawPassCtx) +// we implement this optimization by having a specialized version of LatteCP_processCommandBuffer, called right after drawcalls, which only implements commands that dont interfere with fast drawing. Other commands will cause this function to return to the complex and generic parser +void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCtx) { cemu_assert_debug(drawPassCtx.isWithinDrawPass()); // quit early if there are parameters set which are generally incompatible with fast drawing if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0) { drawPassCtx.endDrawPass(); - return cmd; + return; } // check for other special states? - while (cmd < cmdEnd) + while (true) { - LatteCMDPtr cmdBeforeCommand = cmd; - uint32 itHeader = LatteReadCMD(); - uint32 itHeaderType = (itHeader >> 30) & 3; - if (itHeaderType == 3) + LatteCMDPtr cmd, cmdStart, cmdEnd; + if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) { - uint32 itCode = (itHeader >> 8) & 0xFF; - uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; - switch (itCode) - { - case IT_SET_RESOURCE: // attribute buffers, uniform buffers or texture units - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd) - { - if (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD_FIRST && registerStart <= Latte::REGADDR::SQ_TEX_RESOURCE_WORD_LAST) - drawPassCtx.endDrawPass(); // texture updates end the current draw sequence - else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END) - drawPassCtx.notifyModifiedVertexBuffer(); - else - drawPassCtx.notifyModifiedUniformBuffer(); - }); - if (!drawPassCtx.isWithinDrawPass()) - return cmd; - break; - } - case IT_SET_ALU_CONST: // uniform register - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - break; - } - case IT_SET_CTL_CONST: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - break; - } - case IT_SET_CONFIG_REG: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - break; - } - case IT_INDEX_TYPE: - { - cmd = LatteCP_itIndexType(cmd, nWords); - break; - } - case IT_NUM_INSTANCES: - { - cmd = LatteCP_itNumInstances(cmd, nWords); - break; - } - case IT_DRAW_INDEX_2: - { -#ifdef FAST_DRAW_LOGGING - if(GetAsyncKeyState('A')) - forceLogRemoveMe_printf("Minimal draw"); -#endif - cmd = LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); - break; - } - case IT_SET_CONTEXT_REG: - { -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] Quit due to command IT_SET_CONTEXT_REG Reg: %04x", (uint32)cmd[0] + 0xA000); -#endif - drawPassCtx.endDrawPass(); - return cmdBeforeCommand; - } - case IT_INDIRECT_BUFFER_PRIV: - { - cmd = LatteCP_itIndirectBuffer(cmd, nWords, drawPassCtx); - if (!drawPassCtx.isWithinDrawPass()) - return cmd; - break; - } - default: -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] Quit due to command itCode 0x%02x", itCode); -#endif - drawPassCtx.endDrawPass(); - return cmdBeforeCommand; - } - } - else if (itHeaderType == 2) - { - // filler packet - } - else - { -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] Quit due to unsupported headerType 0x%02x", itHeaderType); -#endif drawPassCtx.endDrawPass(); - return cmdBeforeCommand; - } - } - cemu_assert_debug(drawPassCtx.isWithinDrawPass()); - return cmd; -} - -void LatteCP_processCommandBuffer(uint8* cmdBuffer, sint32 cmdSize, DrawPassContext& drawPassCtx) -{ - LatteCMDPtr cmd = (LatteCMDPtr)cmdBuffer; - LatteCMDPtr cmdStart = (LatteCMDPtr)cmdBuffer; - LatteCMDPtr cmdEnd = (LatteCMDPtr)(cmdBuffer + cmdSize); - - if (drawPassCtx.isWithinDrawPass()) - { - cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); - cemu_assert_debug(cmd <= cmdEnd); - if (cmd == cmdEnd) return; - cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); - } + } - while (cmd < cmdEnd) - { - uint32 itHeader = LatteReadCMD(); - uint32 itHeaderType = (itHeader >> 30) & 3; - if (itHeaderType == 3) + while (cmd < cmdEnd) { - uint32 itCode = (itHeader >> 8) & 0xFF; - uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; -#ifdef CEMU_DEBUG_ASSERT - LatteCMDPtr expectedPostCmd = cmd + nWords; -#endif - switch (itCode) + LatteCMDPtr cmdBeforeCommand = cmd; + uint32 itHeader = LatteReadCMD(); + uint32 itHeaderType = (itHeader >> 30) & 3; + if (itHeaderType == 3) { - case IT_SET_CONTEXT_REG: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - } - break; - case IT_SET_RESOURCE: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - } - break; - case IT_SET_ALU_CONST: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - } - break; - case IT_SET_CTL_CONST: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - } - break; - case IT_SET_SAMPLER: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - } - break; - case IT_SET_CONFIG_REG: - { - cmd = LatteCP_itSetRegistersGeneric(cmd, nWords); - } - break; - case IT_SET_LOOP_CONST: - { - LatteSkipCMD(nWords); - // todo - } - break; - case IT_SURFACE_SYNC: - { - cmd = LatteCP_itSurfaceSync(cmd); - } - break; - case IT_INDIRECT_BUFFER_PRIV: - { - cmd = LatteCP_itIndirectBuffer(cmd, nWords, drawPassCtx); - if (drawPassCtx.isWithinDrawPass()) + uint32 itCode = (itHeader >> 8) & 0xFF; + uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; + LatteCMDPtr cmdData = cmd; + cmd += nWords; + switch (itCode) { - cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); - cemu_assert_debug(cmd <= cmdEnd); - if (cmd == cmdEnd) + case IT_SET_RESOURCE: // attribute buffers, uniform buffers or texture units + { + LatteCP_itSetRegistersGeneric(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd) + { + if ((registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) || + (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) || + (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7))) + drawPassCtx.endDrawPass(); // texture updates end the current draw sequence + else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END) + drawPassCtx.notifyModifiedVertexBuffer(); + else + drawPassCtx.notifyModifiedUniformBuffer(); + }); + if (!drawPassCtx.isWithinDrawPass()) + { + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); return; - cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); + } + break; + } + case IT_SET_ALU_CONST: // uniform register + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + break; + } + case IT_SET_CTL_CONST: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + break; + } + case IT_SET_CONFIG_REG: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + break; + } + case IT_INDEX_TYPE: + { + LatteCP_itIndexType(cmdData, nWords); + break; + } + case IT_NUM_INSTANCES: + { + LatteCP_itNumInstances(cmdData, nWords); + break; + } + case IT_DRAW_INDEX_2: + { + LatteCP_itDrawIndex2(cmdData, nWords, drawPassCtx); + break; + } + case IT_SET_CONTEXT_REG: + { + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); + return; + } + case IT_INDIRECT_BUFFER_PRIV: + { + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + LatteCP_itIndirectBuffer(cmdData, nWords, drawPassCtx); + if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) // switch to sub buffer + cemu_assert_debug(false); + + //if (!drawPassCtx.isWithinDrawPass()) + // return cmdData; + break; + } + default: + // unsupported command for fast draw + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); + return; } -#ifdef CEMU_DEBUG_ASSERT - expectedPostCmd = cmd; -#endif } - break; - case IT_STRMOUT_BUFFER_UPDATE: + else if (itHeaderType == 2) { - cmd = LatteCP_itStreamoutBufferUpdate(cmd, nWords); - } - break; - case IT_INDEX_TYPE: - { - cmd = LatteCP_itIndexType(cmd, nWords); - } - break; - case IT_NUM_INSTANCES: - { - cmd = LatteCP_itNumInstances(cmd, nWords); - } - break; - case IT_DRAW_INDEX_2: - { - drawPassCtx.beginDrawPass(); -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] Starting"); -#endif - cmd = LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); - cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); - cemu_assert_debug(cmd == cmdEnd || drawPassCtx.isWithinDrawPass() == false); // draw sequence should have ended if we didn't reach the end of the command buffer -#ifdef CEMU_DEBUG_ASSERT - expectedPostCmd = cmd; -#endif - } - break; - case IT_DRAW_INDEX_AUTO: - { - drawPassCtx.beginDrawPass(); - cmd = LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); - cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); - cemu_assert_debug(cmd == cmdEnd || drawPassCtx.isWithinDrawPass() == false); // draw sequence should have ended if we didn't reach the end of the command buffer -#ifdef CEMU_DEBUG_ASSERT - expectedPostCmd = cmd; -#endif -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] Auto-draw"); -#endif - } - break; - case IT_DRAW_INDEX_IMMD: - { - DrawPassContext drawPassCtx; - drawPassCtx.beginDrawPass(); - cmd = LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); - drawPassCtx.endDrawPass(); - break; - } - case IT_WAIT_REG_MEM: - { - cmd = LatteCP_itWaitRegMem(cmd, nWords); - LatteTiming_HandleTimedVsync(); - LatteAsyncCommands_checkAndExecute(); - } - break; - case IT_MEM_WRITE: - { - cmd = LatteCP_itMemWrite(cmd, nWords); - } - break; - case IT_CONTEXT_CONTROL: - { - cmd = LatteCP_itContextControl(cmd, nWords); - } - break; - case IT_MEM_SEMAPHORE: - { - cmd = LatteCP_itMemSemaphore(cmd, nWords); - } - break; - case IT_LOAD_CONFIG_REG: - { - cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONFIG); - } - break; - case IT_LOAD_CONTEXT_REG: - { - cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONTEXT); - } - break; - case IT_LOAD_ALU_CONST: - { - cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_ALU_CONST); - } - break; - case IT_LOAD_LOOP_CONST: - { - cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_LOOP_CONST); - } - break; - case IT_LOAD_RESOURCE: - { - cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_RESOURCE); - } - break; - case IT_LOAD_SAMPLER: - { - cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_SAMPLER); - } - break; - case IT_SET_PREDICATION: - { - cmd = LatteCP_itSetPredication(cmd, nWords); - } - break; - case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: - { - cmd = LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); - } - break; - case IT_HLE_TRIGGER_SCANBUFFER_SWAP: - { - cmd = LatteCP_itHLESwapScanBuffer(cmd, nWords); - } - break; - case IT_HLE_WAIT_FOR_FLIP: - { - cmd = LatteCP_itHLEWaitForFlip(cmd, nWords); - } - break; - case IT_HLE_REQUEST_SWAP_BUFFERS: - { - cmd = LatteCP_itHLERequestSwapBuffers(cmd, nWords); - } - break; - case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: - { - cmd = LatteCP_itHLEClearColorDepthStencil(cmd, nWords); - } - break; - case IT_HLE_COPY_SURFACE_NEW: - { - cmd = LatteCP_itHLECopySurfaceNew(cmd, nWords); - } - break; - case IT_HLE_SAMPLE_TIMER: - { - cmd = LatteCP_itHLESampleTimer(cmd, nWords); - } - break; - case IT_HLE_SPECIAL_STATE: - { - cmd = LatteCP_itHLESpecialState(cmd, nWords); - } - break; - case IT_HLE_BEGIN_OCCLUSION_QUERY: - { - cmd = LatteCP_itHLEBeginOcclusionQuery(cmd, nWords); - } - break; - case IT_HLE_END_OCCLUSION_QUERY: - { - cmd = LatteCP_itHLEEndOcclusionQuery(cmd, nWords); - } - break; - case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: - { - cmd = LatteCP_itHLESetRetirementTimestamp(cmd, nWords); - } - break; - case IT_HLE_BOTTOM_OF_PIPE_CB: - { - cmd = LatteCP_itHLEBottomOfPipeCB(cmd, nWords); - } - break; - case IT_HLE_SYNC_ASYNC_OPERATIONS: - { - LatteSkipCMD(nWords); - LatteTextureReadback_UpdateFinishedTransfers(true); - LatteQuery_UpdateFinishedQueriesForceFinishAll(); - } - break; - default: - debug_printf("Unhandled IT %02x\n", itCode); - cemu_assert_debug(false); - LatteSkipCMD(nWords); - } -#ifdef CEMU_DEBUG_ASSERT - if(cmd != expectedPostCmd) - debug_printf("cmd %016p expectedPostCmd %016p\n", cmd, expectedPostCmd); - cemu_assert_debug(cmd == expectedPostCmd); -#endif - } - else if (itHeaderType == 2) - { - // filler packet - // has no body - } - else if (itHeaderType == 0) - { - uint32 registerBase = (itHeader & 0xFFFF); - uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; - if (registerBase == 0x304A) - { - GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_TOP); - LatteSkipCMD(registerCount); - } - else if (registerBase == 0x304B) - { - LatteSkipCMD(registerCount); + // filler packet } else { + // unsupported command for fast draw + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); + return; + } + } + } + if (drawPassCtx.isWithinDrawPass()) + drawPassCtx.endDrawPass(); +} + +void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) +{ + while (true) + { + LatteCMDPtr cmd, cmdStart, cmdEnd; + if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) + break; + while (cmd < cmdEnd) + { + uint32 itHeader = LatteReadCMD(); + uint32 itHeaderType = (itHeader >> 30) & 3; + if (itHeaderType == 3) + { + uint32 itCode = (itHeader >> 8) & 0xFF; + uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; + LatteCMDPtr cmdData = cmd; + cmd += nWords; + switch (itCode) + { + case IT_SET_CONTEXT_REG: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + } + break; + case IT_SET_RESOURCE: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + } + break; + case IT_SET_ALU_CONST: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + } + break; + case IT_SET_CTL_CONST: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + } + break; + case IT_SET_SAMPLER: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + } + break; + case IT_SET_CONFIG_REG: + { + LatteCP_itSetRegistersGeneric(cmdData, nWords); + } + break; + case IT_SET_LOOP_CONST: + { + // todo + } + break; + case IT_SURFACE_SYNC: + { + LatteCP_itSurfaceSync(cmdData); + } + break; + case IT_INDIRECT_BUFFER_PRIV: + { + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + LatteCP_itIndirectBuffer(cmdData, nWords, drawPassCtx); + if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) // switch to sub buffer + cemu_assert_debug(false); + } + break; + case IT_STRMOUT_BUFFER_UPDATE: + { + LatteCP_itStreamoutBufferUpdate(cmdData, nWords); + } + break; + case IT_INDEX_TYPE: + { + LatteCP_itIndexType(cmdData, nWords); + } + break; + case IT_NUM_INSTANCES: + { + LatteCP_itNumInstances(cmdData, nWords); + } + break; + case IT_DRAW_INDEX_2: + { + drawPassCtx.beginDrawPass(); + LatteCP_itDrawIndex2(cmdData, nWords, drawPassCtx); + // enter fast draw mode + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + LatteCP_processCommandBuffer_continuousDrawPass(drawPassCtx); + cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); + if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) + return; + } + break; + case IT_DRAW_INDEX_AUTO: + { + drawPassCtx.beginDrawPass(); + LatteCP_itDrawIndexAuto(cmdData, nWords, drawPassCtx); + // enter fast draw mode + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + LatteCP_processCommandBuffer_continuousDrawPass(drawPassCtx); + cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); + if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) + return; + } + break; + case IT_DRAW_INDEX_IMMD: + { + DrawPassContext drawPassCtx; + drawPassCtx.beginDrawPass(); + LatteCP_itDrawImmediate(cmdData, nWords, drawPassCtx); + drawPassCtx.endDrawPass(); + break; + } + case IT_WAIT_REG_MEM: + { + LatteCP_itWaitRegMem(cmdData, nWords); + LatteTiming_HandleTimedVsync(); + LatteAsyncCommands_checkAndExecute(); + break; + } + case IT_MEM_WRITE: + { + LatteCP_itMemWrite(cmdData, nWords); + break; + } + case IT_CONTEXT_CONTROL: + { + LatteCP_itContextControl(cmdData, nWords); + break; + } + case IT_MEM_SEMAPHORE: + { + LatteCP_itMemSemaphore(cmdData, nWords); + break; + } + case IT_LOAD_CONFIG_REG: + { + LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_CONFIG); + break; + } + case IT_LOAD_CONTEXT_REG: + { + LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_CONTEXT); + break; + } + case IT_LOAD_ALU_CONST: + { + LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_ALU_CONST); + break; + } + case IT_LOAD_LOOP_CONST: + { + LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_LOOP_CONST); + break; + } + case IT_LOAD_RESOURCE: + { + LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_RESOURCE); + break; + } + case IT_LOAD_SAMPLER: + { + LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_SAMPLER); + break; + } + case IT_SET_PREDICATION: + { + LatteCP_itSetPredication(cmdData, nWords); + break; + } + case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: + { + LatteCP_itHLECopyColorBufferToScanBuffer(cmdData, nWords); + break; + } + case IT_HLE_TRIGGER_SCANBUFFER_SWAP: + { + LatteCP_itHLESwapScanBuffer(cmdData, nWords); + break; + } + case IT_HLE_WAIT_FOR_FLIP: + { + LatteCP_itHLEWaitForFlip(cmdData, nWords); + break; + } + case IT_HLE_REQUEST_SWAP_BUFFERS: + { + LatteCP_itHLERequestSwapBuffers(cmdData, nWords); + break; + } + case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: + { + LatteCP_itHLEClearColorDepthStencil(cmdData, nWords); + break; + } + case IT_HLE_COPY_SURFACE_NEW: + { + LatteCP_itHLECopySurfaceNew(cmdData, nWords); + break; + } + case IT_HLE_SAMPLE_TIMER: + { + LatteCP_itHLESampleTimer(cmdData, nWords); + break; + } + case IT_HLE_SPECIAL_STATE: + { + LatteCP_itHLESpecialState(cmdData, nWords); + break; + } + case IT_HLE_BEGIN_OCCLUSION_QUERY: + { + LatteCP_itHLEBeginOcclusionQuery(cmdData, nWords); + break; + } + case IT_HLE_END_OCCLUSION_QUERY: + { + LatteCP_itHLEEndOcclusionQuery(cmdData, nWords); + break; + } + case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: + { + LatteCP_itHLESetRetirementTimestamp(cmdData, nWords); + break; + } + case IT_HLE_BOTTOM_OF_PIPE_CB: + { + LatteCP_itHLEBottomOfPipeCB(cmdData, nWords); + break; + } + case IT_HLE_SYNC_ASYNC_OPERATIONS: + { + LatteTextureReadback_UpdateFinishedTransfers(true); + LatteQuery_UpdateFinishedQueriesForceFinishAll(); + break; + } + default: + debug_printf("Unhandled IT %02x\n", itCode); + cemu_assert_debug(false); + break; + } + } + else if (itHeaderType == 2) + { + // filler packet + // has no body + } + else if (itHeaderType == 0) + { + uint32 registerBase = (itHeader & 0xFFFF); + uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; + if (registerBase == 0x304A) + { + GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_TOP); + LatteSkipCMD(registerCount); + } + else if (registerBase == 0x304B) + { + LatteSkipCMD(registerCount); + } + else + { + LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); + cemu_assert_debug(false); + } + } + else + { + debug_printf("invalid itHeaderType %08x\n", itHeaderType); LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); cemu_assert_debug(false); } } - else - { - debug_printf("invalid itHeaderType %08x\n", itHeaderType); - LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); - cemu_assert_debug(false); - } + cemu_assert_debug(cmd == cmdEnd); } - cemu_assert_debug(cmd == cmdEnd); } void LatteCP_ProcessRingbuffer() { - sint32 timerRecheck = 0; // estimates how much CP processing time passed based on the executed commands, if the value exceeds CP_TIMER_RECHECK then _handleTimers() is called + sint32 timerRecheck = 0; // estimates how much CP processing time has elapsed based on the executed commands, if the value exceeds CP_TIMER_RECHECK then _handleTimers() is called while (true) { uint32 itHeader = LatteCP_readU32Deprc(); @@ -1365,80 +1391,73 @@ void LatteCP_ProcessRingbuffer() uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; LatteCP_waitForNWords(nWords); LatteCMDPtr cmd = (LatteCMDPtr)gxRingBufferReadPtr; - uint8* expectedGxRingBufferReadPtr = gxRingBufferReadPtr + nWords*4; + uint8* cmdEnd = gxRingBufferReadPtr + nWords * 4; + gxRingBufferReadPtr = cmdEnd; switch (itCode) { case IT_SURFACE_SYNC: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSurfaceSync(cmd); + LatteCP_itSurfaceSync(cmd); timerRecheck += CP_TIMER_RECHECK / 512; } break; case IT_SET_CONTEXT_REG: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric(cmd, nWords); + LatteCP_itSetRegistersGeneric(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; } break; case IT_SET_RESOURCE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric(cmd, nWords); + LatteCP_itSetRegistersGeneric(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; } break; case IT_SET_ALU_CONST: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric(cmd, nWords); + LatteCP_itSetRegistersGeneric(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_SET_CTL_CONST: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric(cmd, nWords); + LatteCP_itSetRegistersGeneric(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_SET_SAMPLER: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric(cmd, nWords); + LatteCP_itSetRegistersGeneric(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_SET_CONFIG_REG: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric(cmd, nWords); + LatteCP_itSetRegistersGeneric(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_INDIRECT_BUFFER_PRIV: { -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] BEGIN CMD BUFFER"); -#endif - LatteCP_itIndirectBufferDepr(nWords); + LatteCP_itIndirectBufferDepr(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; -#ifdef FAST_DRAW_LOGGING - if (GetAsyncKeyState('A')) - forceLogRemoveMe_printf("[FAST-DRAW] END CMD BUFFER"); -#endif break; } case IT_STRMOUT_BUFFER_UPDATE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itStreamoutBufferUpdate(cmd, nWords); + LatteCP_itStreamoutBufferUpdate(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_INDEX_TYPE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itIndexType(cmd, nWords); + LatteCP_itIndexType(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1024; break; } case IT_NUM_INSTANCES: { - gxRingBufferReadPtr = (uint8*)LatteCP_itNumInstances(cmd, nWords); + LatteCP_itNumInstances(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1024; break; } @@ -1446,7 +1465,7 @@ void LatteCP_ProcessRingbuffer() { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); - gxRingBufferReadPtr = (uint8*)LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); + LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 64; break; @@ -1455,7 +1474,7 @@ void LatteCP_ProcessRingbuffer() { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); - gxRingBufferReadPtr = (uint8*)LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); + LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 512; break; @@ -1464,165 +1483,162 @@ void LatteCP_ProcessRingbuffer() { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); - gxRingBufferReadPtr = (uint8*)LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); + LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_WAIT_REG_MEM: { - gxRingBufferReadPtr = (uint8*)LatteCP_itWaitRegMem(cmd, nWords); + LatteCP_itWaitRegMem(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 16; break; } case IT_MEM_WRITE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itMemWrite(cmd, nWords); + LatteCP_itMemWrite(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_CONTEXT_CONTROL: { - gxRingBufferReadPtr = (uint8*)LatteCP_itContextControl(cmd, nWords); + LatteCP_itContextControl(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_MEM_SEMAPHORE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itMemSemaphore(cmd, nWords); + LatteCP_itMemSemaphore(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_LOAD_CONFIG_REG: { - gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONFIG); + LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONFIG); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_CONTEXT_REG: { - gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONTEXT); + LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONTEXT); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_ALU_CONST: { - gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_ALU_CONST); + LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_ALU_CONST); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_LOOP_CONST: { - gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_LOOP_CONST); + LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_LOOP_CONST); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_RESOURCE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_RESOURCE); + LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_RESOURCE); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_SAMPLER: { - gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_SAMPLER); + LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_SAMPLER); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_SET_LOOP_CONST: { - LatteSkipCMD(nWords); - gxRingBufferReadPtr = (uint8*)cmd; // todo break; } case IT_SET_PREDICATION: { - gxRingBufferReadPtr = (uint8*)LatteCP_itSetPredication(cmd, nWords); + LatteCP_itSetPredication(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); + LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_HLE_TRIGGER_SCANBUFFER_SWAP: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLESwapScanBuffer(cmd, nWords); + LatteCP_itHLESwapScanBuffer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_HLE_WAIT_FOR_FLIP: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLEWaitForFlip(cmd, nWords); + LatteCP_itHLEWaitForFlip(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1; break; } case IT_HLE_REQUEST_SWAP_BUFFERS: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLERequestSwapBuffers(cmd, nWords); + LatteCP_itHLERequestSwapBuffers(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 32; break; } case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLEClearColorDepthStencil(cmd, nWords); + LatteCP_itHLEClearColorDepthStencil(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_HLE_COPY_SURFACE_NEW: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLECopySurfaceNew(cmd, nWords); + LatteCP_itHLECopySurfaceNew(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_HLE_FIFO_WRAP_AROUND: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLEFifoWrapAround(cmd, nWords); - expectedGxRingBufferReadPtr = gxRingBufferReadPtr; + LatteCP_itHLEFifoWrapAround(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_SAMPLE_TIMER: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLESampleTimer(cmd, nWords); + LatteCP_itHLESampleTimer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_SPECIAL_STATE: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLESpecialState(cmd, nWords); + LatteCP_itHLESpecialState(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_BEGIN_OCCLUSION_QUERY: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLEBeginOcclusionQuery(cmd, nWords); + LatteCP_itHLEBeginOcclusionQuery(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_END_OCCLUSION_QUERY: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLEEndOcclusionQuery(cmd, nWords); + LatteCP_itHLEEndOcclusionQuery(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLESetRetirementTimestamp(cmd, nWords); + LatteCP_itHLESetRetirementTimestamp(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_BOTTOM_OF_PIPE_CB: { - gxRingBufferReadPtr = (uint8*)LatteCP_itHLEBottomOfPipeCB(cmd, nWords); + LatteCP_itHLEBottomOfPipeCB(cmd, nWords); break; } case IT_HLE_SYNC_ASYNC_OPERATIONS: { - LatteCP_skipWords(nWords); + //LatteCP_skipWords(nWords); LatteTextureReadback_UpdateFinishedTransfers(true); LatteQuery_UpdateFinishedQueriesForceFinishAll(); break; @@ -1630,7 +1646,6 @@ void LatteCP_ProcessRingbuffer() default: cemu_assert_debug(false); } - cemu_assert_debug(expectedGxRingBufferReadPtr == gxRingBufferReadPtr); } else if (itHeaderType == 2) { @@ -1668,3 +1683,275 @@ void LatteCP_ProcessRingbuffer() } } } + +#ifdef LATTE_CP_LOGGING +void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size) +{ + uint32be* bufferPtrInitial = bufferPtr; + uint32be* bufferPtrEnd = bufferPtr + (size/4); + while (bufferPtr < bufferPtrEnd) + { + std::string strPrefix = fmt::format("[PM4 Buf {:08x} Offs {:04x}]", MEMPTR(bufferPtr).GetMPTR(), (bufferPtr - bufferPtrInitial) * 4); + uint32 itHeader = *bufferPtr; + bufferPtr++; + uint32 itHeaderType = (itHeader >> 30) & 3; + if (itHeaderType == 3) + { + uint32 itCode = (itHeader >> 8) & 0xFF; + uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; + uint32be* cmdData = bufferPtr; + bufferPtr += nWords; + switch (itCode) + { + case IT_SURFACE_SYNC: + { + cemuLog_log(LogType::Force, "{} IT_SURFACE_SYNC", strPrefix); + break; + } + case IT_SET_CONTEXT_REG: + { + std::string regVals; + for (uint32 i = 0; i < std::min(nWords - 1, 8); i++) + regVals.append(fmt::format("{:08x} ", cmdData[1 + i].value())); + cemuLog_log(LogType::Force, "{} IT_SET_CONTEXT_REG Reg {:04x} RegValues {}", strPrefix, cmdData[0].value(), regVals); + } + case IT_SET_RESOURCE: + { + std::string regVals; + for (uint32 i = 0; i < std::min(nWords - 1, 8); i++) + regVals.append(fmt::format("{:08x} ", cmdData[1+i].value())); + cemuLog_log(LogType::Force, "{} IT_SET_RESOURCE Reg {:04x} RegValues {}", strPrefix, cmdData[0].value(), regVals); + break; + } + case IT_SET_ALU_CONST: + { + cemuLog_log(LogType::Force, "{} IT_SET_ALU_CONST", strPrefix); + break; + } + case IT_SET_CTL_CONST: + { + cemuLog_log(LogType::Force, "{} IT_SET_CTL_CONST", strPrefix); + break; + } + case IT_SET_SAMPLER: + { + cemuLog_log(LogType::Force, "{} IT_SET_SAMPLER", strPrefix); + break; + } + case IT_SET_CONFIG_REG: + { + cemuLog_log(LogType::Force, "{} IT_SET_CONFIG_REG", strPrefix); + break; + } + case IT_INDIRECT_BUFFER_PRIV: + { + if (nWords != 3) + { + cemuLog_log(LogType::Force, "{} IT_INDIRECT_BUFFER_PRIV (malformed!)", strPrefix); + } + else + { + uint32 physicalAddress = cmdData[0]; + uint32 physicalAddressHigh = cmdData[1]; + uint32 sizeInDWords = cmdData[2]; + cemuLog_log(LogType::Force, "{} IT_INDIRECT_BUFFER_PRIV Addr {:08x} Size {:08x}", strPrefix, physicalAddress, sizeInDWords*4); + LatteCP_DebugPrintCmdBuffer(MEMPTR(physicalAddress), sizeInDWords * 4); + } + break; + } + case IT_STRMOUT_BUFFER_UPDATE: + { + cemuLog_log(LogType::Force, "{} IT_STRMOUT_BUFFER_UPDATE", strPrefix); + break; + } + case IT_INDEX_TYPE: + { + cemuLog_log(LogType::Force, "{} IT_INDEX_TYPE", strPrefix); + break; + } + case IT_NUM_INSTANCES: + { + cemuLog_log(LogType::Force, "{} IT_NUM_INSTANCES", strPrefix); + break; + } + case IT_DRAW_INDEX_2: + { + if (nWords != 5) + { + cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_2 (malformed!)", strPrefix); + } + else + { + uint32 ukn1 = cmdData[0]; + MPTR physIndices = cmdData[1]; + uint32 ukn2 = cmdData[2]; + uint32 count = cmdData[3]; + uint32 ukn3 = cmdData[4]; + cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_2 | Count {}", strPrefix, count); + } + break; + } + case IT_DRAW_INDEX_AUTO: + { + cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_AUTO", strPrefix); + break; + } + case IT_DRAW_INDEX_IMMD: + { + cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_IMMD", strPrefix); + break; + } + case IT_WAIT_REG_MEM: + { + cemuLog_log(LogType::Force, "{} IT_WAIT_REG_MEM", strPrefix); + break; + } + case IT_MEM_WRITE: + { + cemuLog_log(LogType::Force, "{} IT_MEM_WRITE", strPrefix); + break; + } + case IT_CONTEXT_CONTROL: + { + cemuLog_log(LogType::Force, "{} IT_CONTEXT_CONTROL", strPrefix); + break; + } + case IT_MEM_SEMAPHORE: + { + cemuLog_log(LogType::Force, "{} IT_MEM_SEMAPHORE", strPrefix); + break; + } + case IT_LOAD_CONFIG_REG: + { + cemuLog_log(LogType::Force, "{} IT_LOAD_CONFIG_REG", strPrefix); + break; + } + case IT_LOAD_CONTEXT_REG: + { + cemuLog_log(LogType::Force, "{} IT_LOAD_CONTEXT_REG", strPrefix); + break; + } + case IT_LOAD_ALU_CONST: + { + cemuLog_log(LogType::Force, "{} IT_LOAD_ALU_CONST", strPrefix); + break; + } + case IT_LOAD_LOOP_CONST: + { + cemuLog_log(LogType::Force, "{} IT_LOAD_LOOP_CONST", strPrefix); + break; + } + case IT_LOAD_RESOURCE: + { + cemuLog_log(LogType::Force, "{} IT_LOAD_RESOURCE", strPrefix); + break; + } + case IT_LOAD_SAMPLER: + { + cemuLog_log(LogType::Force, "{} IT_LOAD_SAMPLER", strPrefix); + break; + } + case IT_SET_LOOP_CONST: + { + cemuLog_log(LogType::Force, "{} IT_SET_LOOP_CONST", strPrefix); + break; + } + case IT_SET_PREDICATION: + { + cemuLog_log(LogType::Force, "{} IT_SET_PREDICATION", strPrefix); + break; + } + case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: + { + cemuLog_log(LogType::Force, "{} IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER", strPrefix); + break; + } + case IT_HLE_TRIGGER_SCANBUFFER_SWAP: + { + cemuLog_log(LogType::Force, "{} IT_HLE_TRIGGER_SCANBUFFER_SWAP", strPrefix); + break; + } + case IT_HLE_WAIT_FOR_FLIP: + { + cemuLog_log(LogType::Force, "{} IT_HLE_WAIT_FOR_FLIP", strPrefix); + break; + } + case IT_HLE_REQUEST_SWAP_BUFFERS: + { + cemuLog_log(LogType::Force, "{} IT_HLE_REQUEST_SWAP_BUFFERS", strPrefix); + break; + } + case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: + { + cemuLog_log(LogType::Force, "{} IT_HLE_CLEAR_COLOR_DEPTH_STENCIL", strPrefix); + break; + } + case IT_HLE_COPY_SURFACE_NEW: + { + cemuLog_log(LogType::Force, "{} IT_HLE_COPY_SURFACE_NEW", strPrefix); + break; + } + case IT_HLE_FIFO_WRAP_AROUND: + { + cemuLog_log(LogType::Force, "{} IT_HLE_FIFO_WRAP_AROUND", strPrefix); + break; + } + case IT_HLE_SAMPLE_TIMER: + { + cemuLog_log(LogType::Force, "{} IT_HLE_SAMPLE_TIMER", strPrefix); + break; + } + case IT_HLE_SPECIAL_STATE: + { + cemuLog_log(LogType::Force, "{} IT_HLE_SPECIAL_STATE", strPrefix); + break; + } + case IT_HLE_BEGIN_OCCLUSION_QUERY: + { + cemuLog_log(LogType::Force, "{} IT_HLE_BEGIN_OCCLUSION_QUERY", strPrefix); + break; + } + case IT_HLE_END_OCCLUSION_QUERY: + { + cemuLog_log(LogType::Force, "{} IT_HLE_END_OCCLUSION_QUERY", strPrefix); + break; + } + case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: + { + cemuLog_log(LogType::Force, "{} IT_HLE_SET_CB_RETIREMENT_TIMESTAMP", strPrefix); + break; + } + case IT_HLE_BOTTOM_OF_PIPE_CB: + { + cemuLog_log(LogType::Force, "{} IT_HLE_BOTTOM_OF_PIPE_CB", strPrefix); + break; + } + case IT_HLE_SYNC_ASYNC_OPERATIONS: + { + cemuLog_log(LogType::Force, "{} IT_HLE_SYNC_ASYNC_OPERATIONS", strPrefix); + break; + } + default: + cemuLog_log(LogType::Force, "{} Unsupported operation code", strPrefix); + return; + } + } + else if (itHeaderType == 2) + { + // filler packet + } + else if (itHeaderType == 0) + { + uint32 registerBase = (itHeader & 0xFFFF); + uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; + LatteCP_skipWords(registerCount); + cemuLog_log(LogType::Force, "[LatteCP] itType=0 registerBase={:04x}", registerBase); + } + else + { + cemuLog_log(LogType::Force, "Invalid itHeaderType %08x\n", itHeaderType); + return; + } + } +} +#endif \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteConst.h b/src/Cafe/HW/Latte/Core/LatteConst.h index ffbead1c..04c7b888 100644 --- a/src/Cafe/HW/Latte/Core/LatteConst.h +++ b/src/Cafe/HW/Latte/Core/LatteConst.h @@ -1,21 +1,27 @@ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" -// this file contains legacy C-style defines, modernize and merge into LatteReg.h +// todo - this file contains legacy C-style defines, modernize and merge into LatteReg.h // GPU7/Latte hardware info -#define LATTE_NUM_GPR (128) -#define LATTE_NUM_STREAMOUT_BUFFER (4) -#define LATTE_NUM_COLOR_TARGET (8) +#define LATTE_NUM_GPR 128 +#define LATTE_NUM_STREAMOUT_BUFFER 4 +#define LATTE_NUM_COLOR_TARGET 8 -#define LATTE_NUM_MAX_TEX_UNITS (18) // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?) -#define LATTE_NUM_MAX_UNIFORM_BUFFERS (16) // number of supported uniform buffer binding locations +#define LATTE_NUM_MAX_TEX_UNITS 18 // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?) +#define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations -#define LATTE_VS_ATTRIBUTE_LIMIT (32) // todo: verify -#define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS (256) // should this be 128 since there are only 128 GPRs? +#define LATTE_VS_ATTRIBUTE_LIMIT 32 // todo: verify +#define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS 256 // should this be 128 since there are only 128 GPRs? -#define LATTE_MAX_VERTEX_BUFFERS (16) +#define LATTE_MAX_VERTEX_BUFFERS 16 + +// Cemu-specific constants + +#define LATTE_CEMU_PS_TEX_UNIT_BASE 0 +#define LATTE_CEMU_VS_TEX_UNIT_BASE 32 +#define LATTE_CEMU_GS_TEX_UNIT_BASE 64 // vertex formats diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp index 22f4e262..479f601a 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp @@ -27,6 +27,7 @@ struct OverlayStats double fps{}; uint32 draw_calls_per_frame{}; + uint32 fast_draw_calls_per_frame{}; float cpu_usage{}; // cemu cpu usage in % std::vector cpu_per_core; // global cpu usage in % per core uint32 ram_usage{}; // ram usage in MB @@ -87,7 +88,7 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio ImGui::Text("FPS: %.2lf", g_state.fps); if (config.overlay.drawcalls) - ImGui::Text("Draws/f: %d", g_state.draw_calls_per_frame); + ImGui::Text("Draws/f: %d (fast: %d)", g_state.draw_calls_per_frame, g_state.fast_draw_calls_per_frame); if (config.overlay.cpu_usage) ImGui::Text("CPU: %.2lf%%", g_state.cpu_usage); @@ -589,13 +590,14 @@ static void UpdateStats_CpuPerCore() } } -void LatteOverlay_updateStats(double fps, sint32 drawcalls) +void LatteOverlay_updateStats(double fps, sint32 drawcalls, sint32 fastDrawcalls) { if (GetConfig().overlay.position == ScreenPosition::kDisabled) return; g_state.fps = fps; g_state.draw_calls_per_frame = drawcalls; + g_state.fast_draw_calls_per_frame = fastDrawcalls; UpdateStats_CemuCpu(); UpdateStats_CpuPerCore(); diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.h b/src/Cafe/HW/Latte/Core/LatteOverlay.h index e497abb0..824c68b2 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.h +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.h @@ -2,6 +2,6 @@ void LatteOverlay_init(); void LatteOverlay_render(bool pad_view); -void LatteOverlay_updateStats(double fps, sint32 drawcalls); +void LatteOverlay_updateStats(double fps, sint32 drawcalls, sint32 fastDrawcalls); void LatteOverlay_pushNotification(const std::string& text, sint32 duration); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp index 7ed59522..ee22892e 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp @@ -38,6 +38,7 @@ void LattePerformanceMonitor_frameEnd() uint64 indexDataCached = 0; uint32 frameCounter = 0; uint32 drawCallCounter = 0; + uint32 fastDrawCallCounter = 0; uint32 shaderBindCounter = 0; uint32 recompilerLeaveCount = 0; uint32 threadLeaveCount = 0; @@ -53,6 +54,7 @@ void LattePerformanceMonitor_frameEnd() indexDataCached += performanceMonitor.cycle[i].indexDataCached; frameCounter += performanceMonitor.cycle[i].frameCounter; drawCallCounter += performanceMonitor.cycle[i].drawCallCounter; + fastDrawCallCounter += performanceMonitor.cycle[i].fastDrawCallCounter; shaderBindCounter += performanceMonitor.cycle[i].shaderBindCount; recompilerLeaveCount += performanceMonitor.cycle[i].recompilerLeaveCount; threadLeaveCount += performanceMonitor.cycle[i].threadLeaveCount; @@ -75,7 +77,6 @@ void LattePerformanceMonitor_frameEnd() indexDataUploadPerFrame /= 1024ULL; double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS; - uint32 drawCallsPerFrame = drawCallCounter / elapsedFrames; uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames; passedCycles = passedCycles * 1000ULL / totalElapsedTime; uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime); @@ -85,6 +86,7 @@ void LattePerformanceMonitor_frameEnd() // next counter cycle sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES; performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0; + performanceMonitor.cycle[nextCycleIndex].fastDrawCallCounter = 0; performanceMonitor.cycle[nextCycleIndex].frameCounter = 0; performanceMonitor.cycle[nextCycleIndex].shaderBindCount = 0; performanceMonitor.cycle[nextCycleIndex].lastCycleCount = PPCInterpreter_getMainCoreCycleCounter(); @@ -105,13 +107,13 @@ void LattePerformanceMonitor_frameEnd() auto cafeSystemCallbacks = CafeSystem::getCafeSystemCallbacks(); if (isFirstUpdate) { - LatteOverlay_updateStats(0.0, 0); + LatteOverlay_updateStats(0.0, 0, 0); if (cafeSystemCallbacks) cafeSystemCallbacks->updateWindowTitles(false, false, 0.0); } else { - LatteOverlay_updateStats(fps, drawCallCounter / elapsedFrames); + LatteOverlay_updateStats(fps, drawCallCounter / elapsedFrames, fastDrawCallCounter / elapsedFrames); if (cafeSystemCallbacks) cafeSystemCallbacks->updateWindowTitles(false, false, fps); } diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h index 77554e80..713e094e 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h @@ -84,6 +84,7 @@ typedef struct uint32 lastUpdate; uint32 frameCounter; uint32 drawCallCounter; + uint32 fastDrawCallCounter; uint32 shaderBindCount; uint64 vertexDataUploaded; // amount of vertex data uploaded to GPU (bytes) uint64 vertexDataCached; // amount of vertex data reused from GPU cache (bytes) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index f835140c..40dee43d 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -239,8 +239,6 @@ LatteTextureView* LatteMRT_CreateColorBuffer(MPTR colorBufferPhysMem, uint32 wid 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); - // unbind texture - g_renderer->texture_bindAndActivate(nullptr, 0); return textureView; } @@ -253,8 +251,6 @@ LatteTextureView* LatteMRT_CreateDepthBuffer(MPTR depthBufferPhysMem, uint32 wid 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); LatteMRT::SetDepthAndStencilAttachment(textureView, textureView->baseTexture->hasStencil); - // unbind texture - g_renderer->texture_bindAndActivate(nullptr, 0); return textureView; } @@ -295,6 +291,34 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN uint32 colorBufferHeight = pitchHeight / colorBufferPitch; uint32 colorBufferWidth = colorBufferPitch; + // 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 + + // 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 +#if 0 + uint32 colorBufferHeight2 = pitchHeight / colorBufferPitch; + static std::unordered_set s_foundColorBufferResMappings; + if (colorBufferPitch != colorBufferWidth || colorBufferHeight != colorBufferHeight2) + { + // only log unique, source and dest resolution. Encode into a key with 16 bits per component + uint64 resHash = (uint64)colorBufferWidth | ((uint64)colorBufferHeight << 16) | ((uint64)colorBufferPitch << 32) | ((uint64)colorBufferHeight2 << 48); + if( !s_foundColorBufferResMappings.contains(resHash) ) + { + s_foundColorBufferResMappings.insert(resHash); + cemuLog_log(LogType::Force, "[COLORBUFFER-DBG] Using res {}x{} instead of {}x{}", colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferHeight2); + } + } +#endif + bool colorBufferWasFound = false; sint32 viewFirstMip = 0; // todo @@ -1048,9 +1072,9 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin } } - if (renderTarget == 4 && g_renderer->IsPadWindowActive()) + if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive()) LatteRenderTarget_copyToBackbuffer(texView, true); - if ((renderTarget == 1 && !showDRC) || (renderTarget == 4 && showDRC)) + if (((renderTarget & RENDER_TARGET_TV) && !showDRC) || ((renderTarget & RENDER_TARGET_DRC) && showDRC)) LatteRenderTarget_copyToBackbuffer(texView, false); } diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index c0ad06a1..503fb664 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -838,7 +838,6 @@ LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash, void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) { // todo - should include VTX_SEMANTIC table in state - LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader); uint64 vsAuxHash = 0; auto itBaseShader = sVertexShaders.find(_shaderBaseHash_vs); @@ -855,15 +854,13 @@ void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSiz LatteGPUState.activeShaderHasError = true; return; } - g_renderer->shader_bind(vertexShader->shader); _activeVertexShader = vertexShader; } void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) { - if (usesGeometryShader == false || _activeVertexShader == nullptr) + if (!usesGeometryShader || !_activeVertexShader) { - g_renderer->shader_unbind(RendererShader::ShaderType::kGeometry); _shaderBaseHash_gs = 0; _activeGeometryShader = nullptr; return; @@ -887,21 +884,11 @@ void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShad LatteGPUState.activeShaderHasError = true; return; } - g_renderer->shader_bind(geometryShader->shader); _activeGeometryShader = geometryShader; } void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) { - if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0 && g_renderer->GetType() == RendererAPI::OpenGL) - { - if (_activePixelShader) - { - g_renderer->shader_unbind(RendererShader::ShaderType::kFragment); - _activePixelShader = nullptr; - } - return; - } LatteSHRC_UpdatePSBaseHash(pixelShaderPtr, pixelShaderSize, usesGeometryShader); uint64 psAuxHash = 0; auto itBaseShader = sPixelShaders.find(_shaderBaseHash_ps); @@ -918,7 +905,6 @@ void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, LatteGPUState.activeShaderHasError = true; return; } - g_renderer->shader_bind(pixelShader->shader); _activePixelShader = pixelShader; } diff --git a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp index df99307c..4f5b24ad 100644 --- a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp @@ -46,9 +46,7 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s // mark source and destination texture as still in use LatteTC_MarkTextureStillInUse(destinationTexture); LatteTC_MarkTextureStillInUse(sourceTexture); - // determine GL slice indices sint32 realSrcSlice = srcSlice; - sint32 realDstSlice = dstSlice; if (LatteTexture_doesEffectiveRescaleRatioMatch(sourceTexture, sourceView->firstMip, destinationTexture, destinationView->firstMip)) { // adjust copy size @@ -62,29 +60,11 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); // copy slice if (sourceView->baseTexture->isDepth != destinationView->baseTexture->isDepth) - { g_renderer->surfaceCopy_copySurfaceWithFormatConversion(sourceTexture, sourceView->firstMip, sourceView->firstSlice, destinationTexture, destinationView->firstMip, destinationView->firstSlice, copyWidth, copyHeight); - uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); - LatteTexture_MarkDynamicTextureAsChanged(destinationTexture->baseView, destinationView->firstSlice, destinationView->firstMip, eventCounter); - } else - { - // calculate mip levels relative to texture base - sint32 texDstMipLevel; - if (destinationTexture->physAddress == dstPhysAddr) - { - texDstMipLevel = dstLevel; - } - else - { - // todo - handle mip addresses properly - texDstMipLevel = dstLevel - destinationView->firstMip; - } - - g_renderer->texture_copyImageSubData(sourceTexture, sourceView->firstMip, 0, 0, realSrcSlice, destinationTexture, texDstMipLevel, 0, 0, realDstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); - uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); - LatteTexture_MarkDynamicTextureAsChanged(destinationTexture->baseView, destinationView->firstSlice, texDstMipLevel, eventCounter); - } + g_renderer->texture_copyImageSubData(sourceTexture, sourceView->firstMip, 0, 0, realSrcSlice, destinationTexture, destinationView->firstMip, 0, 0, destinationView->firstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); + const uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); + LatteTexture_MarkDynamicTextureAsChanged(destinationTexture->baseView, destinationView->firstSlice, destinationView->firstMip, eventCounter); } else { diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 19162e04..d38af8ec 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -795,6 +795,8 @@ bool IsDimensionCompatibleForView(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) @@ -836,6 +838,11 @@ bool IsDimensionCompatibleForView(Latte::E_DIM baseDim, Latte::E_DIM viewDim) // 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)) { @@ -872,7 +879,9 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT return VIEW_NOT_COMPATIBLE; // depth and non-depth formats are never compatible (on OpenGL) if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) || baseTexture->width != width || baseTexture->height != height) return VIEW_NOT_COMPATIBLE; - if (!IsDimensionCompatibleForView(baseTexture->dim, dimView)) + // 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)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { @@ -978,7 +987,7 @@ void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* textur newDim = Latte::E_DIM::DIM_2D_ARRAY; else if (newDim == Latte::E_DIM::DIM_1D && newDepth > 1) newDim = Latte::E_DIM::DIM_1D_ARRAY; - LatteTextureView* view = LatteTexture_CreateTexture(0, newDim, texture->physAddress, physMipAddr, texture->format, texture->width, texture->height, newDepth, texture->pitch, newMipCount, texture->swizzle, texture->tileMode, texture->isDepth); + LatteTextureView* view = LatteTexture_CreateTexture(newDim, texture->physAddress, physMipAddr, texture->format, texture->width, texture->height, newDepth, texture->pitch, newMipCount, texture->swizzle, texture->tileMode, texture->isDepth); cemu_assert(!(view->baseTexture->mipLevels <= 1 && physMipAddr == MPTR_NULL && newMipCount > 1)); // copy data from old texture if its dynamically updated if (texture->isUpdatedOnGPU) @@ -999,6 +1008,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 +// 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) { if (format == Latte::E_GX2SURFFMT::INVALID_FORMAT) @@ -1104,12 +1114,18 @@ LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, si // create new texture if (allowCreateNewDataTexture == false) return nullptr; - LatteTextureView* view = LatteTexture_CreateTexture(0, dimBase, physAddr, physMipAddr, format, width, height, depth, pitch, firstMip + numMip, swizzle, tileMode, isDepth); + LatteTextureView* view = LatteTexture_CreateTexture(dimBase, physAddr, physMipAddr, format, width, height, depth, pitch, firstMip + numMip, swizzle, tileMode, isDepth); + LatteTexture* newTexture = view->baseTexture; LatteTexture_GatherTextureRelations(view->baseTexture); LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture); // delete any individual smaller slices/mips that have become redundant LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture); - return view; + // create view + sint32 relativeMipIndex; + sint32 relativeSliceIndex; + VIEWCOMPATIBILITY viewCompatibility = LatteTexture_CanTextureBeRepresentedAsView(newTexture, physAddr, width, height, pitch, dimView, format, isDepth, firstMip, numMip, firstSlice, numSlice, relativeMipIndex, relativeSliceIndex); + cemu_assert(viewCompatibility == VIEW_COMPATIBLE); + return view->baseTexture->GetOrCreateView(dimView, format, relativeMipIndex + firstMip, numMip, relativeSliceIndex + firstSlice, numSlice); } LatteTextureView* LatteTC_LookupTextureByData(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32* searchIndex) @@ -1177,12 +1193,8 @@ LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR void LatteTexture_UpdateDataToLatest(LatteTexture* texture) { if (LatteTC_HasTextureChanged(texture)) - { - g_renderer->texture_rememberBoundTexture(0); - g_renderer->texture_bindAndActivateRawTex(texture, 0); - LatteTexture_ReloadData(texture, 0); - g_renderer->texture_restoreBoundTexture(0); - } + LatteTexture_ReloadData(texture); + if (texture->reloadFromDynamicTextures) { LatteTexture_UpdateCacheFromDynamicTextures(texture); @@ -1231,7 +1243,7 @@ std::vector& LatteTexture::GetAllTextures() return sAllTextures; } -LatteTexture::LatteTexture(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, +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) { _AddTextureToGlobalList(this); diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.h b/src/Cafe/HW/Latte/Core/LatteTexture.h index 6cdc528e..d5e872e6 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.h +++ b/src/Cafe/HW/Latte/Core/LatteTexture.h @@ -24,11 +24,9 @@ struct LatteSamplerState class LatteTexture { public: - LatteTexture(uint32 textureUnit, 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(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 InitTextureState() {}; - LatteTextureView* GetOrCreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { for (auto& itr : views) @@ -307,7 +305,7 @@ std::vector LatteTexture_QueryCacheInfo(); float* LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit); -LatteTextureView* LatteTexture_CreateTexture(uint32 textureUnit, 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); +LatteTextureView* LatteTexture_CreateTexture(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); void LatteTexture_Delete(LatteTexture* texture); void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData); diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index 1bf17c51..0260002b 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -1,10 +1,8 @@ #include "Cafe/HW/Latte/ISA/RegDefines.h" -#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove this dependency #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" -#include "Cafe/GraphicPack/GraphicPack2.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" @@ -34,9 +32,9 @@ void LatteTexture_setEffectiveTextureScale(LatteConst::ShaderType shaderType, si t[1] = v; } -void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, sint32 textureUnit, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex); +void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex); -void LatteTexture_ReloadData(LatteTexture* tex, uint32 textureUnit) +void LatteTexture_ReloadData(LatteTexture* tex) { tex->reloadCount++; for(sint32 mip=0; mipmipLevels; mip++) @@ -46,35 +44,35 @@ void LatteTexture_ReloadData(LatteTexture* tex, uint32 textureUnit) { sint32 numSlices = std::max(tex->depth, 1); for(sint32 s=0; sphysAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + LatteTextureLoader_UpdateTextureSliceData(tex, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } else if( tex->dim == Latte::E_DIM::DIM_CUBEMAP ) { cemu_assert_debug((tex->depth % 6) == 0); sint32 numFullCubeMaps = tex->depth/6; // number of cubemaps (if numFullCubeMaps is >1 then this texture is a cubemap array) for(sint32 s=0; sphysAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + LatteTextureLoader_UpdateTextureSliceData(tex, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } else if( tex->dim == Latte::E_DIM::DIM_3D ) { sint32 mipDepth = std::max(tex->depth>>mip, 1); for(sint32 s=0; sphysAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + LatteTextureLoader_UpdateTextureSliceData(tex, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } } else { // load slice 0 - LatteTextureLoader_UpdateTextureSliceData(tex, textureUnit, 0, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + LatteTextureLoader_UpdateTextureSliceData(tex, 0, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } } tex->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); } -LatteTextureView* LatteTexture_CreateTexture(uint32 textureUnit, 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) +LatteTextureView* LatteTexture_CreateTexture(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) { - const auto tex = g_renderer->texture_createTextureEx(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); + const auto tex = g_renderer->texture_createTextureEx(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); // init slice/mip info array LatteTexture_InitSliceAndMipInfo(tex); LatteTexture_RegisterTextureMemoryOccupancy(tex); @@ -112,7 +110,7 @@ LatteTextureView* LatteTexture_CreateTexture(uint32 textureUnit, Latte::E_DIM di } } } - LatteTexture_ReloadData(tex, textureUnit); + LatteTexture_ReloadData(tex); LatteTC_MarkTextureStillInUse(tex); LatteTC_RegisterTexture(tex); // create initial view that maps to the whole texture @@ -249,7 +247,7 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter; rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler); } - g_renderer->texture_bindOnly(textureView, textureIndex + glBackendBaseTexUnit); + g_renderer->texture_setLatteTexture(textureView, textureIndex + glBackendBaseTexUnit); // update if data changed bool swizzleChanged = false; if (textureView->baseTexture->swizzle != swizzle) @@ -287,9 +285,8 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u textureView->baseTexture->physMipAddress = physMipAddr; } } - g_renderer->texture_bindAndActivateRawTex(textureView->baseTexture, textureIndex + glBackendBaseTexUnit); debug_printf("Reload reason: Data-change when bound as texture (new hash 0x%08x)\n", textureView->baseTexture->texDataHash2); - LatteTexture_ReloadData(textureView->baseTexture, textureIndex + glBackendBaseTexUnit); + LatteTexture_ReloadData(textureView->baseTexture); } LatteTexture* baseTexture = textureView->baseTexture; if (baseTexture->reloadFromDynamicTextures) @@ -330,15 +327,15 @@ void LatteTexture_updateTextures() // pixel shader LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); if (pixelShader) - LatteTexture_updateTexturesForStage(pixelShader, CEMU_PS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_PS); + LatteTexture_updateTexturesForStage(pixelShader, LATTE_CEMU_PS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_PS); // vertex shader LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); cemu_assert_debug(vertexShader != nullptr); - LatteTexture_updateTexturesForStage(vertexShader, CEMU_VS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_VS); + LatteTexture_updateTexturesForStage(vertexShader, LATTE_CEMU_VS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_VS); // geometry shader LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (geometryShader) - LatteTexture_updateTexturesForStage(geometryShader, CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS); + LatteTexture_updateTexturesForStage(geometryShader, LATTE_CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS); } // returns the width, height, depth of the texture diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp index 331c1500..862fff06 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp @@ -599,7 +599,7 @@ void LatteTextureLoader_loadTextureDataIntoSlice(LatteTexture* hostTexture, sint } } -void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, sint32 textureUnit, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex) +void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex) { LatteTextureLoaderCtx textureLoader = { 0 }; diff --git a/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp b/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp index 0483e8ee..8df5dcea 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp @@ -8,10 +8,11 @@ #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" -// #define LOG_READBACK_TIME +#define LOG_READBACK_TIME struct LatteTextureReadbackQueueEntry { + HRTick initiateTime; uint32 lastUpdateDrawcallIndex; LatteTextureView* textureView; }; @@ -22,12 +23,12 @@ std::queue sTextureActiveReadbackQueue; // readbacks void LatteTextureReadback_StartTransfer(LatteTextureView* textureView) { cemuLog_log(LogType::TextureReadback, "[TextureReadback-Start] PhysAddr {:08x} Res {}x{} Fmt {} Slice {} Mip {}", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->format, textureView->firstSlice, textureView->firstMip); + HRTick currentTick = HighResolutionTimer().now().getTick(); // create info entry and store in ordered linked list LatteTextureReadbackInfo* readbackInfo = g_renderer->texture_createReadback(textureView); sTextureActiveReadbackQueue.push(readbackInfo); readbackInfo->StartTransfer(); - //debug_printf("[Tex-Readback] %08x %dx%d TM %d FMT %04x\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->tileMode, textureView->baseTexture->format); - readbackInfo->transferStartTime = HighResolutionTimer().now().getTick(); + readbackInfo->transferStartTime = currentTick; } /* @@ -41,9 +42,13 @@ bool LatteTextureReadback_Update(bool forceStart) for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) { LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; - uint32 numPassedDrawcalls = LatteGPUState.drawCallCounter - entry.lastUpdateDrawcallIndex; - if (forceStart || numPassedDrawcalls >= 5) + uint32 numElapsedDrawcalls = LatteGPUState.drawCallCounter - entry.lastUpdateDrawcallIndex; + if (forceStart || numElapsedDrawcalls >= 5) { +#ifdef LOG_READBACK_TIME + double elapsedSecondsSinceInitiate = HighResolutionTimer::getTimeDiff(entry.initiateTime, HighResolutionTimer().now().getTick()); + cemuLog_log(LogType::TextureReadback, "[TextureReadback-Update] Starting transfer for {:08x} after {} elapsed drawcalls. Time since initiate: {:.4} Force-start: {}", entry.textureView->baseTexture->physAddress, numElapsedDrawcalls, elapsedSecondsSinceInitiate, forceStart?"yes":"no"); +#endif LatteTextureReadback_StartTransfer(entry.textureView); // remove element vectorRemoveByIndex(sTextureScheduledReadbacks, i); @@ -76,7 +81,7 @@ void LatteTextureReadback_Initate(LatteTextureView* textureView) // currently we don't support readback for resized textures if (textureView->baseTexture->overwriteInfo.hasResolutionOverwrite) { - cemuLog_log(LogType::Force, "_initate(): Readback is not supported for textures with modified resolution"); + cemuLog_log(LogType::Force, "Texture readback is not supported for textures with modified resolution. Texture: {:08x} {}x{}", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height); return; } // check if texture isn't already queued for transfer @@ -91,6 +96,7 @@ void LatteTextureReadback_Initate(LatteTextureView* textureView) } // queue LatteTextureReadbackQueueEntry queueEntry; + queueEntry.initiateTime = HighResolutionTimer().now().getTick(); queueEntry.textureView = textureView; queueEntry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter; sTextureScheduledReadbacks.emplace_back(queueEntry); @@ -112,6 +118,14 @@ void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish) if (!readbackInfo->IsFinished()) { readbackInfo->waitStartTime = HighResolutionTimer().now().getTick(); +#ifdef LOG_READBACK_TIME + if (cemuLog_isLoggingEnabled(LogType::TextureReadback)) + { + double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, HighResolutionTimer().now().getTick()); + cemuLog_log(LogType::TextureReadback, "[Texture-Readback] Force-finish: {:08x} Res {:}/{:} TM {:} FMT {:04x} Transfer time so far: {:.4}ms", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0); + } +#endif + readbackInfo->forceFinish = true; readbackInfo->ForceFinish(); // rerun logic since ->ForceFinish() can recurively call this function and thus modify the queue continue; @@ -125,10 +139,13 @@ void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish) } // performance testing #ifdef LOG_READBACK_TIME - HRTick currentTick = HighResolutionTimer().now().getTick(); - double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, currentTick); - double elapsedSecondsWaiting = HighResolutionTimer::getTimeDiff(readbackInfo->waitStartTime, currentTick); - cemuLog_log(LogType::Force, "[Texture-Readback] {:08x} Res {:4}/{:4} TM {} FMT {:04x} ReadbackLatency: {:6.3}ms WaitTime: {:6.3}ms ForcedWait {}", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0, elapsedSecondsWaiting * 1000.0, forceFinish?"yes":"no"); + if (cemuLog_isLoggingEnabled(LogType::TextureReadback)) + { + HRTick currentTick = HighResolutionTimer().now().getTick(); + double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, currentTick); + double elapsedSecondsWaiting = HighResolutionTimer::getTimeDiff(readbackInfo->waitStartTime, currentTick); + cemuLog_log(LogType::TextureReadback, "[Texture-Readback] {:08x} Res {}/{} TM {} FMT {:04x} ReadbackLatency: {:6.3}ms WaitTime: {:6.3}ms ForcedWait {}", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0, elapsedSecondsWaiting * 1000.0, readbackInfo->forceFinish ? "yes" : "no"); + } #endif uint8* pixelData = readbackInfo->GetData(); LatteTextureLoader_writeReadbackTextureToMemory(&readbackInfo->hostTextureCopy, 0, 0, pixelData); diff --git a/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h b/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h index 4f3a3069..535e9442 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h +++ b/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h @@ -21,6 +21,7 @@ public: HRTick transferStartTime; HRTick waitStartTime; + bool forceFinish{ false }; // set to true if not finished in time for dependent operation // texture info LatteTextureDefinition hostTextureCopy{}; diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index cdf21e8c..8b940a92 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -44,7 +44,7 @@ LatteTextureView* LatteHandleOSScreen_getOrCreateScreenTex(MPTR physAddress, uin LatteTextureView* texView = LatteTextureViewLookupCache::lookup(physAddress, width, height, 1, pitch, 0, 1, 0, 1, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, Latte::E_DIM::DIM_2D); if (texView) return texView; - return LatteTexture_CreateTexture(0, Latte::E_DIM::DIM_2D, physAddress, 0, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, width, height, 1, pitch, 1, 0, Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED, false); + return LatteTexture_CreateTexture(Latte::E_DIM::DIM_2D, physAddress, 0, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, width, height, 1, pitch, 1, 0, Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED, false); } void LatteHandleOSScreen_prepareTextures() @@ -71,8 +71,7 @@ bool LatteHandleOSScreen_TV() const uint32 bufferIndexTV = (bufferDisplayTV); const uint32 bufferIndexDRC = bufferDisplayDRC; - g_renderer->texture_bindAndActivate(osScreenTVTex[bufferIndexTV], 0); - LatteTexture_ReloadData(osScreenTVTex[bufferIndexTV]->baseTexture, 0); + LatteTexture_ReloadData(osScreenTVTex[bufferIndexTV]->baseTexture); // TV screen LatteRenderTarget_copyToBackbuffer(osScreenTVTex[bufferIndexTV]->baseTexture->baseView, false); @@ -94,8 +93,7 @@ bool LatteHandleOSScreen_DRC() const uint32 bufferIndexDRC = bufferDisplayDRC; - g_renderer->texture_bindAndActivate(osScreenDRCTex[bufferIndexDRC], 0); - LatteTexture_ReloadData(osScreenDRCTex[bufferIndexDRC]->baseTexture, 0); + LatteTexture_ReloadData(osScreenDRCTex[bufferIndexDRC]->baseTexture); // GamePad screen LatteRenderTarget_copyToBackbuffer(osScreenDRCTex[bufferIndexDRC]->baseTexture->baseView, true); diff --git a/src/Cafe/HW/Latte/ISA/LatteReg.h b/src/Cafe/HW/Latte/ISA/LatteReg.h index e539902f..d571dc6e 100644 --- a/src/Cafe/HW/Latte/ISA/LatteReg.h +++ b/src/Cafe/HW/Latte/ISA/LatteReg.h @@ -381,6 +381,9 @@ namespace Latte PA_SC_GENERIC_SCISSOR_TL = 0xA090, PA_SC_GENERIC_SCISSOR_BR = 0xA091, + SQ_VTX_SEMANTIC_0 = 0xA0E0, + SQ_VTX_SEMANTIC_31 = 0xA0FF, + VGT_MULTI_PRIM_IB_RESET_INDX = 0xA103, SX_ALPHA_TEST_CONTROL = 0xA104, CB_BLEND_RED = 0xA105, @@ -398,6 +401,10 @@ namespace Latte PA_CL_VPORT_ZSCALE = 0xA113, PA_CL_VPORT_ZOFFSET = 0xA114, + SPI_VS_OUT_ID_0 = 0xA185, + + SPI_VS_OUT_CONFIG = 0xA1B1, + CB_BLEND0_CONTROL = 0xA1E0, // first CB_BLEND7_CONTROL = 0xA1E7, // last @@ -408,7 +415,23 @@ namespace Latte PA_CL_CLIP_CNTL = 0xA204, PA_SU_SC_MODE_CNTL = 0xA205, PA_CL_VTE_CNTL = 0xA206, - + PA_CL_VS_OUT_CNTL = 0xA207, + + // shader program descriptors: + SQ_PGM_START_PS = 0xA210, + SQ_PGM_RESOURCES_PS = 0xA214, + SQ_PGM_EXPORTS_PS = 0xA215, + SQ_PGM_START_VS = 0xA216, + SQ_PGM_RESOURCES_VS = 0xA21A, + SQ_PGM_START_GS = 0xA21B, + SQ_PGM_RESOURCES_GS = 0xA21F, + SQ_PGM_START_ES = 0xA220, + SQ_PGM_RESOURCES_ES = 0xA224, + SQ_PGM_START_FS = 0xA225, + SQ_PGM_RESOURCES_FS = 0xA229, + + SQ_VTX_SEMANTIC_CLEAR = 0xA238, + PA_SU_POINT_SIZE = 0xA280, PA_SU_POINT_MINMAX = 0xA281, @@ -416,6 +439,35 @@ namespace Latte VGT_DMA_INDEX_TYPE = 0xA29F, // todo - verify offset + VGT_PRIMITIVEID_EN = 0xA2A1, + + VGT_MULTI_PRIM_IB_RESET_EN = 0xA2A5, + + VGT_INSTANCE_STEP_RATE_0 = 0xA2A8, + VGT_INSTANCE_STEP_RATE_1 = 0xA2A9, + + VGT_STRMOUT_BUFFER_SIZE_0 = 0xA2B4, + VGT_STRMOUT_VTX_STRIDE_0 = 0xA2B5, + VGT_STRMOUT_BUFFER_BASE_0 = 0xA2B6, + VGT_STRMOUT_BUFFER_OFFSET_0 = 0xA2B7, + VGT_STRMOUT_BUFFER_SIZE_1 = 0xA2B8, + VGT_STRMOUT_VTX_STRIDE_1 = 0xA2B9, + VGT_STRMOUT_BUFFER_BASE_1 = 0xA2BA, + VGT_STRMOUT_BUFFER_OFFSET_1 = 0xA2BB, + VGT_STRMOUT_BUFFER_SIZE_2 = 0xA2BC, + VGT_STRMOUT_VTX_STRIDE_2 = 0xA2BD, + VGT_STRMOUT_BUFFER_BASE_2 = 0xA2BE, + VGT_STRMOUT_BUFFER_OFFSET_2 = 0xA2BF, + VGT_STRMOUT_BUFFER_SIZE_3 = 0xA2C0, + VGT_STRMOUT_VTX_STRIDE_3 = 0xA2C1, + VGT_STRMOUT_BUFFER_BASE_3 = 0xA2C2, + VGT_STRMOUT_BUFFER_OFFSET_3 = 0xA2C3, + VGT_STRMOUT_BASE_OFFSET_0 = 0xA2C4, + VGT_STRMOUT_BASE_OFFSET_1 = 0xA2C5, + VGT_STRMOUT_BASE_OFFSET_2 = 0xA2C6, + VGT_STRMOUT_BASE_OFFSET_3 = 0xA2C7, + VGT_STRMOUT_BUFFER_EN = 0xA2C8, + // HiZ early stencil test? DB_SRESULTS_COMPARE_STATE0 = 0xA34A, DB_SRESULTS_COMPARE_STATE1 = 0xA34B, @@ -432,7 +484,7 @@ namespace Latte SQ_TEX_RESOURCE_WORD0_N_GS = 0xE930, SQ_TEX_RESOURCE_WORD_FIRST = SQ_TEX_RESOURCE_WORD0_N_PS, SQ_TEX_RESOURCE_WORD_LAST = (SQ_TEX_RESOURCE_WORD0_N_GS + GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7 - 1), - // there are 54 samplers with 3 registers each. 18 per stage. For stage indices see SAMPLER_BASE_INDEX_* + // there are 54 samplers with 3 registers each. 18 (actually only 16?) per stage. For stage indices see SAMPLER_BASE_INDEX_* SQ_TEX_SAMPLER_WORD0_0 = 0xF000, SQ_TEX_SAMPLER_WORD1_0 = 0xF001, SQ_TEX_SAMPLER_WORD2_0 = 0xF002, @@ -842,6 +894,12 @@ float get_##__regname() const \ LATTE_BITFIELD_BOOL(VTX_W0_FMT, 10); }; + struct LATTE_PA_CL_VS_OUT_CNTL : LATTEREG // 0xA207 + { + LATTE_BITFIELD(CLIP_DIST_ENA_MASK, 0, 8); + LATTE_BITFIELD(CULL_DIST_ENA_MASK, 8, 8); + }; + struct LATTE_PA_SU_POINT_SIZE : LATTEREG // 0xA280 { LATTE_BITFIELD(HEIGHT, 0, 16); @@ -909,6 +967,54 @@ float get_##__regname() const \ LATTE_BITFIELD_FULL_TYPED(INDEX_TYPE, E_INDEX_TYPE); }; + struct LATTE_VGT_PRIMITIVEID_EN : LATTEREG // 0xA2A1 + { + LATTE_BITFIELD_BOOL(PRIMITIVEID_EN, 0); + }; + + struct LATTE_VGT_MULTI_PRIM_IB_RESET_EN : LATTEREG // 0xA2A5 + { + LATTE_BITFIELD_BOOL(RESET_EN, 0); + }; + + struct LATTE_VGT_INSTANCE_STEP_RATE_X : LATTEREG // 0xA2A8-0xA2A9 + { + LATTE_BITFIELD_FULL_TYPED(STEP_RATE, uint32); + }; + + struct LATTE_VGT_STRMOUT_BUFFER_SIZE_X : LATTEREG // 0xA2B4 + index * 4 + { + LATTE_BITFIELD_FULL_TYPED(SIZE, uint32); + }; + + struct LATTE_VGT_STRMOUT_STRIDE_X : LATTEREG // 0xA2B5 + index * 4 + { + LATTE_BITFIELD_FULL_TYPED(STRIDE, uint32); + }; + + struct LATTE_VGT_STRMOUT_BUFFER_BASE_X : LATTEREG // 0xA2B6 + index * 4 + { + LATTE_BITFIELD_FULL_TYPED(BASE, uint32); + }; + + struct LATTE_VGT_STRMOUT_BUFFER_OFFSET_X : LATTEREG // 0xA2B7 + index * 4 + { + LATTE_BITFIELD_FULL_TYPED(BUFFER_OFFSET, uint32); + }; + + struct LATTE_VGT_STRMOUT_BASE_OFFSET_X : LATTEREG // 0xA2C4-0xA2C7 + { + LATTE_BITFIELD_FULL_TYPED(BASE_OFFSET, uint32); + }; + + struct LATTE_VGT_STRMOUT_BUFFER_EN : LATTEREG // 0xA2C8 + { + LATTE_BITFIELD_BOOL(BUFFER_ENABLE_0, 0); + LATTE_BITFIELD_BOOL(BUFFER_ENABLE_1, 1); + LATTE_BITFIELD_BOOL(BUFFER_ENABLE_2, 2); + LATTE_BITFIELD_BOOL(BUFFER_ENABLE_3, 3); + }; + struct LATTE_PA_SU_POLY_OFFSET_CLAMP : LATTEREG // 0xA37F { LATTE_BITFIELD_FLOAT(CLAMP); @@ -934,6 +1040,16 @@ float get_##__regname() const \ LATTE_BITFIELD_FLOAT(OFFSET); }; + struct LATTE_SQ_VTX_SEMANTIC_CLEAR : LATTEREG // 0xA238 + { + LATTE_BITFIELD_FULL_TYPED(CLEAR_MASK, uint32); // probably a bitmask + }; + + struct LATTE_SQ_VTX_SEMANTIC_X : LATTEREG // 0xA0E0 - 0xA0FF + { + LATTE_BITFIELD(SEMANTIC_ID, 0, 8); + }; + struct LATTE_SQ_TEX_RESOURCE_WORD0_N : LATTEREG // 0xE000 + index * 7 { LATTE_BITFIELD_TYPED(DIM, 0, 3, E_DIM); @@ -1154,6 +1270,65 @@ float get_##__regname() const \ LATTE_BITFIELD_TYPED(TYPE, 31, 1, E_SAMPLER_TYPE); }; + struct LATTE_SQ_PGM_START_X : LATTEREG // 0xA210 / 0xA216 / 0xA21B / 0xA220 / 0xA225 + { + LATTE_BITFIELD_FULL_TYPED(PGM_START, uint32); + }; + + struct LATTE_SQ_PGM_RESOURCES_PS : LATTEREG // 0xA214 + { + LATTE_BITFIELD(NUM_GPRS, 0, 8); + LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); + LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN + LATTE_BITFIELD(FETCH_CACHE_LINES, 24, 3); + LATTE_BITFIELD_BOOL(UNCACHED_FIRST_INST, 28); + LATTE_BITFIELD_BOOL(CLAMP_CONSTS, 31); + }; + + struct LATTE_SQ_PGM_RESOURCES_VS : LATTEREG // 0xA21A + { + LATTE_BITFIELD(NUM_GPRS, 0, 8); + LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); + LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN + LATTE_BITFIELD(FETCH_CACHE_LINES, 24, 3); + LATTE_BITFIELD_BOOL(UNCACHED_FIRST_INST, 28); + }; + + struct LATTE_SQ_PGM_RESOURCES_GS : LATTEREG // 0xA21F + { + LATTE_BITFIELD(NUM_GPRS, 0, 8); + LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); + LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN + }; + + struct LATTE_SQ_PGM_RESOURCES_ES : LATTEREG // 0xA224 + { + LATTE_BITFIELD(NUM_GPRS, 0, 8); + LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); + LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN + }; + + struct LATTE_SQ_PGM_RESOURCES_FS : LATTEREG // 0xA229 + { + LATTE_BITFIELD(NUM_GPRS, 0, 8); + LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); + LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN + }; + + struct LATTE_SQ_XX_ITEMSIZE : LATTEREG // 0xA227 - 0xA2XX + { + // used by: + // SQ_ESGS_RING_ITEMSIZE + // SQ_GSVS_RING_ITEMSIZE + // SQ_ESTMP_RING_ITEMSIZE + // SQ_GSTMP_RING_ITEMSIZE + // SQ_VSTMP_RING_ITEMSIZE + // SQ_PSTMP_RING_ITEMSIZE + // SQ_FBUF_RING_ITEMSIZE + // SQ_REDUC_RING_ITEMSIZE + LATTE_BITFIELD(ITEMSIZE, 0, 15); + }; + struct LATTE_PA_SU_SC_MODE_CNTL : LATTEREG // 0xA205 { enum class E_FRONTFACE @@ -1185,7 +1360,32 @@ float get_##__regname() const \ LATTE_BITFIELD_BOOL(OFFSET_PARA_ENABLED, 13); // offset enable for lines and points? // additional fields? }; -} + + struct LATTE_SPI_VS_OUT_CONFIG : LATTEREG // 0xA1B1 + { + LATTE_BITFIELD_BOOL(VS_PER_COMPONENT, 0); + LATTE_BITFIELD(VS_EXPORT_COUNT, 1, 5); + LATTE_BITFIELD_BOOL(EXPORTS_FOG, 8); + LATTE_BITFIELD(VS_OUT_FOG_VEC_ADDR, 9, 5); + }; + + struct LATTE_SPI_VS_OUT_ID_N : LATTEREG // 0xA185 - 0xA18E(?) - 0xA1B2 - 0xA1B3 + { + uint8 get_SEMANTIC(sint32 index) + { + cemu_assert_debug(index < 4); + return (uint8)((v >> (index * 8)) & 0xFF); + } + + void set_SEMANTIC(sint32 index, uint8 value) + { + cemu_assert_debug(index < 4); + v &= ~(0xFF << (index * 8)); + v |= (value & 0xFF) << (index * 8); + } + }; + +}; struct _LatteRegisterSetTextureUnit { @@ -1219,6 +1419,16 @@ struct _LatteRegisterSetSamplerBorderColor static_assert(sizeof(_LatteRegisterSetSamplerBorderColor) == 16); +struct _LatteRegisterSetStreamoutBuffer +{ + Latte::LATTE_VGT_STRMOUT_BUFFER_SIZE_X SIZE; + Latte::LATTE_VGT_STRMOUT_STRIDE_X STRIDE; + Latte::LATTE_VGT_STRMOUT_BUFFER_BASE_X BASE; + Latte::LATTE_VGT_STRMOUT_BUFFER_OFFSET_X BUFFER_OFFSET; +}; + +static_assert(sizeof(_LatteRegisterSetStreamoutBuffer) == 16); + struct LatteContextRegister { uint8 padding0[0x08958]; @@ -1235,7 +1445,9 @@ struct LatteContextRegister uint8 padding_2823C[4]; /* +0x28240 */ Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL PA_SC_GENERIC_SCISSOR_TL; /* +0x28244 */ Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR PA_SC_GENERIC_SCISSOR_BR; - uint8 padding_28248[0x2840C - 0x28248]; + uint8 padding_28248[0x28380 - 0x28248]; + /* +0x28380 */ Latte::LATTE_SQ_VTX_SEMANTIC_X SQ_VTX_SEMANTIC_X[32]; + /* +0x28400 */ uint8 padding_28400[0x2840C - 0x28400]; /* +0x2840C */ Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_INDX VGT_MULTI_PRIM_IB_RESET_INDX; /* +0x28410 */ Latte::LATTE_SX_ALPHA_TEST_CONTROL SX_ALPHA_TEST_CONTROL; /* +0x28414 */ Latte::LATTE_CB_BLEND_RED CB_BLEND_RED; @@ -1253,7 +1465,15 @@ struct LatteContextRegister /* +0x2844C */ Latte::LATTE_PA_CL_VPORT_ZSCALE PA_CL_VPORT_ZSCALE; /* +0x28450 */ Latte::LATTE_PA_CL_VPORT_ZOFFSET PA_CL_VPORT_ZOFFSET; - uint8 padding_28450[0x28780 - 0x28454]; + uint8 padding_28450[0x28614 - 0x28454]; + + /* +0x28614 */ Latte::LATTE_SPI_VS_OUT_ID_N LATTE_SPI_VS_OUT_ID_N[10]; + + uint8 padding_2863C[0x286C4 - 0x2863C]; + + /* +0x286C4 */ Latte::LATTE_SPI_VS_OUT_CONFIG SPI_VS_OUT_CONFIG; + + uint8 padding_286C8[0x28780 - 0x286C8]; /* +0x28780 */ Latte::LATTE_CB_BLENDN_CONTROL CB_BLENDN_CONTROL[8]; @@ -1266,9 +1486,44 @@ struct LatteContextRegister /* +0x28810 */ Latte::LATTE_PA_CL_CLIP_CNTL PA_CL_CLIP_CNTL; /* +0x28814 */ Latte::LATTE_PA_SU_SC_MODE_CNTL PA_SU_SC_MODE_CNTL; /* +0x28818 */ Latte::LATTE_PA_CL_VTE_CNTL PA_CL_VTE_CNTL; + /* +0x2881C */ Latte::LATTE_PA_CL_VS_OUT_CNTL PA_CL_VS_OUT_CNTL; - uint8 padding_2881C[0x28A00 - 0x2881C]; + uint8 padding_2881C[0x28840 - 0x28820]; + /* +0x28840 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_PS; + /* +0x28844 */ uint32 ukn28844; // PS size + /* +0x28848 */ uint32 ukn28848; + /* +0x2884C */ uint32 ukn2884C; + /* +0x28850 */ Latte::LATTE_SQ_PGM_RESOURCES_PS SQ_PGM_RESOURCES_PS; + /* +0x28854 */ uint32 ukn28854; // SQ_PGM_EXPORTS_PS + /* +0x28858 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_VS; + /* +0x2885C */ uint32 ukn2885C; // VS size + /* +0x28860 */ uint32 ukn28860; + /* +0x28864 */ uint32 ukn28864; + /* +0x28868 */ Latte::LATTE_SQ_PGM_RESOURCES_VS SQ_PGM_RESOURCES_VS; + /* +0x2886C */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_GS; + /* +0x28870 */ uint32 ukn28870; // GS size + /* +0x28874 */ uint32 ukn28874; + /* +0x28878 */ uint32 ukn28878; + /* +0x2887C */ Latte::LATTE_SQ_PGM_RESOURCES_GS SQ_PGM_RESOURCES_GS; + /* +0x28880 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_ES; + /* +0x28884 */ uint32 ukn28884; // ES size + /* +0x28888 */ uint32 ukn28888; + /* +0x2888C */ uint32 ukn2888C; + /* +0x28890 */ Latte::LATTE_SQ_PGM_RESOURCES_ES SQ_PGM_RESOURCES_ES; + /* +0x28894 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_FS; + /* +0x28898 */ uint32 ukn28898; // FS size + /* +0x2889C */ uint32 ukn2889C; + /* +0x288A0 */ uint32 ukn288A0; + /* +0x288A4 */ Latte::LATTE_SQ_PGM_RESOURCES_FS SQ_PGM_RESOURCES_FS; + /* +0x288A8 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_ESGS_RING_ITEMSIZE; + /* +0x288AC */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_GSVS_RING_ITEMSIZE; + /* +0x288B0 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_ESTMP_RING_ITEMSIZE; + /* +0x288B4 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_GSTMP_RING_ITEMSIZE; + /* +0x288B8 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_VSTMP_RING_ITEMSIZE; + uint8 padding_288BC[0x288E0 - 0x288BC]; + /* +0x288E0 */ Latte::LATTE_SQ_VTX_SEMANTIC_CLEAR SQ_VTX_SEMANTIC_CLEAR; + uint8 padding_288E4[0x28A00 - 0x288E4]; /* +0x28A00 */ Latte::LATTE_PA_SU_POINT_SIZE PA_SU_POINT_SIZE; /* +0x28A04 */ Latte::LATTE_PA_SU_POINT_MINMAX PA_SU_POINT_MINMAX; @@ -1279,8 +1534,24 @@ struct LatteContextRegister uint8 padding_28A44[0x28A7C - 0x28A44]; /* +0x28A7C */ Latte::LATTE_VGT_DMA_INDEX_TYPE VGT_DMA_INDEX_TYPE; + /* +0x28A80 */ uint32 ukn28A80; + /* +0x28A84 */ Latte::LATTE_VGT_PRIMITIVEID_EN VGT_PRIMITIVEID_EN; + /* +0x28A88 */ uint32 ukn28A88; + /* +0x28A8C */ uint32 ukn28A8C; + /* +0x28A90 */ uint32 ukn28A90; + /* +0x28A94 */ Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_EN VGT_MULTI_PRIM_IB_RESET_EN; + /* +0x28A98 */ uint32 ukn28A98; + /* +0x28A9C */ uint32 ukn28A9C; + /* +0x28AA0 */ Latte::LATTE_VGT_INSTANCE_STEP_RATE_X VGT_INSTANCE_STEP_RATE_0; + /* +0x28AA4 */ Latte::LATTE_VGT_INSTANCE_STEP_RATE_X VGT_INSTANCE_STEP_RATE_1; - uint8 padding_28A80[0x28DFC - 0x28A80]; + uint8 padding_28AA8[0x28AD0 - 0x28AA8]; + + /* +0x28AD0 */ _LatteRegisterSetStreamoutBuffer VGT_STRMOUT_BUFFER_X[4]; + /* +0x28B10 */ Latte::LATTE_VGT_STRMOUT_BASE_OFFSET_X VGT_STRMOUT_BASE_OFFSET_X[4]; + /* +0x28B20 */ Latte::LATTE_VGT_STRMOUT_BUFFER_EN VGT_STRMOUT_BUFFER_EN; + + uint8 padding_28B24[0x28DFC - 0x28B24]; /* +0x28DFC */ Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP PA_SU_POLY_OFFSET_CLAMP; /* +0x28E00 */ Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE PA_SU_POLY_OFFSET_FRONT_SCALE; @@ -1334,6 +1605,13 @@ static_assert(offsetof(LatteContextRegister, CB_TARGET_MASK) == Latte::REGADDR:: static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_TL) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_TL * 4); static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_BR) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_BR * 4); static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_INDX) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX * 4); +static_assert(offsetof(LatteContextRegister, VGT_PRIMITIVEID_EN) == Latte::REGADDR::VGT_PRIMITIVEID_EN * 4); +static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_EN) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_EN * 4); +static_assert(offsetof(LatteContextRegister, VGT_INSTANCE_STEP_RATE_0) == Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0 * 4); +static_assert(offsetof(LatteContextRegister, VGT_INSTANCE_STEP_RATE_1) == Latte::REGADDR::VGT_INSTANCE_STEP_RATE_1 * 4); +static_assert(offsetof(LatteContextRegister, VGT_STRMOUT_BUFFER_X) == Latte::REGADDR::VGT_STRMOUT_BUFFER_SIZE_0 * 4); +static_assert(offsetof(LatteContextRegister, VGT_STRMOUT_BASE_OFFSET_X) == Latte::REGADDR::VGT_STRMOUT_BASE_OFFSET_0 * 4); +static_assert(offsetof(LatteContextRegister, VGT_STRMOUT_BUFFER_EN) == Latte::REGADDR::VGT_STRMOUT_BUFFER_EN * 4); static_assert(offsetof(LatteContextRegister, SX_ALPHA_TEST_CONTROL) == Latte::REGADDR::SX_ALPHA_TEST_CONTROL * 4); static_assert(offsetof(LatteContextRegister, DB_STENCILREFMASK) == Latte::REGADDR::DB_STENCILREFMASK * 4); static_assert(offsetof(LatteContextRegister, DB_STENCILREFMASK_BF) == Latte::REGADDR::DB_STENCILREFMASK_BF * 4); @@ -1351,6 +1629,7 @@ static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_ZOFFSET) == Latte::REGA static_assert(offsetof(LatteContextRegister, PA_CL_CLIP_CNTL) == Latte::REGADDR::PA_CL_CLIP_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_SU_SC_MODE_CNTL) == Latte::REGADDR::PA_SU_SC_MODE_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VTE_CNTL) == Latte::REGADDR::PA_CL_VTE_CNTL * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VS_OUT_CNTL) == Latte::REGADDR::PA_CL_VS_OUT_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POINT_SIZE) == Latte::REGADDR::PA_SU_POINT_SIZE * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POINT_MINMAX) == Latte::REGADDR::PA_SU_POINT_MINMAX * 4); static_assert(offsetof(LatteContextRegister, CB_BLENDN_CONTROL) == Latte::REGADDR::CB_BLEND0_CONTROL * 4); @@ -1363,7 +1642,21 @@ static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_FRONT_SCALE) == L static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_FRONT_OFFSET) == Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_OFFSET * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_BACK_SCALE) == Latte::REGADDR::PA_SU_POLY_OFFSET_BACK_SCALE * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_BACK_OFFSET) == Latte::REGADDR::PA_SU_POLY_OFFSET_BACK_OFFSET * 4); +static_assert(offsetof(LatteContextRegister, SQ_VTX_SEMANTIC_X) == Latte::REGADDR::SQ_VTX_SEMANTIC_0 * 4); +static_assert(offsetof(LatteContextRegister, SQ_VTX_SEMANTIC_CLEAR) == Latte::REGADDR::SQ_VTX_SEMANTIC_CLEAR * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_START_PS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_START_VS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_START_GS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_SAMPLER) == Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_START_PS) == Latte::REGADDR::SQ_PGM_START_PS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_PS) == Latte::REGADDR::SQ_PGM_RESOURCES_PS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_START_VS) == Latte::REGADDR::SQ_PGM_START_VS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_VS) == Latte::REGADDR::SQ_PGM_RESOURCES_VS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_START_FS) == Latte::REGADDR::SQ_PGM_START_FS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_FS) == Latte::REGADDR::SQ_PGM_RESOURCES_FS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_START_ES) == Latte::REGADDR::SQ_PGM_START_ES * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_ES) == Latte::REGADDR::SQ_PGM_RESOURCES_ES * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_START_GS) == Latte::REGADDR::SQ_PGM_START_GS * 4); +static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_GS) == Latte::REGADDR::SQ_PGM_RESOURCES_GS * 4); +static_assert(offsetof(LatteContextRegister, SPI_VS_OUT_CONFIG) == Latte::REGADDR::SPI_VS_OUT_CONFIG * 4); +static_assert(offsetof(LatteContextRegister, LATTE_SPI_VS_OUT_ID_N) == Latte::REGADDR::SPI_VS_OUT_ID_0 * 4); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/ISA/RegDefines.h b/src/Cafe/HW/Latte/ISA/RegDefines.h index d28739c0..b99c2126 100644 --- a/src/Cafe/HW/Latte/ISA/RegDefines.h +++ b/src/Cafe/HW/Latte/ISA/RegDefines.h @@ -50,8 +50,6 @@ #define mmVGT_PRIMITIVEID_EN 0xA2A1 #define mmVGT_VTX_CNT_EN 0xA2AE #define mmVGT_REUSE_OFF 0xA2AD -#define mmVGT_INSTANCE_STEP_RATE_0 0xA2A8 -#define mmVGT_INSTANCE_STEP_RATE_1 0xA2A9 #define mmVGT_MAX_VTX_INDX 0xA100 #define mmVGT_MIN_VTX_INDX 0xA101 #define mmVGT_INDX_OFFSET 0xA102 diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp index 30e3d7a2..cf88b901 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp @@ -666,6 +666,9 @@ void LatteDecompiler_ParseTEXClause(LatteDecompilerShader* shaderContext, LatteD uint32 offsetY = (word2 >> 5) & 0x1F; uint32 offsetZ = (word2 >> 10) & 0x1F; + sint8 lodBias = (word2 >> 21) & 0x7F; + if ((lodBias&0x40) != 0) + lodBias |= 0x80; // bufferID -> Texture index // samplerId -> Sampler index sint32 textureIndex = bufferId - 0x00; @@ -693,6 +696,7 @@ void LatteDecompiler_ParseTEXClause(LatteDecompilerShader* shaderContext, LatteD texInstruction.textureFetch.unnormalized[1] = coordTypeY == 0; texInstruction.textureFetch.unnormalized[2] = coordTypeZ == 0; texInstruction.textureFetch.unnormalized[3] = coordTypeW == 0; + texInstruction.textureFetch.lodBias = (sint8)lodBias; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else if( inst0_4 == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h index f7a0ea5f..1159614e 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h @@ -1,6 +1,7 @@ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Renderer/RendererShader.h" +#include namespace LatteDecompiler { @@ -158,11 +159,13 @@ struct LatteDecompilerShader struct LatteFetchShader* compatibleFetchShader{}; // error tracking bool hasError{false}; // if set, the shader cannot be used - // optimized access / iteration - // list of uniform buffers used - uint8 uniformBufferList[LATTE_NUM_MAX_UNIFORM_BUFFERS]; - uint8 uniformBufferListCount{ 0 }; - // list of used texture units (faster access than iterating textureUnitMask) + // compact resource lists for optimized access + struct QuickBufferEntry + { + uint32 index : 8; + uint32 size : 24; + }; + boost::container::static_vector list_quickBufferList; uint8 textureUnitList[LATTE_NUM_MAX_TEX_UNITS]; uint8 textureUnitListCount{ 0 }; // input diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index e482be2c..2e837198 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -1,9 +1,7 @@ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" -#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove this dependency #include "Cafe/HW/Latte/Core/Latte.h" -#include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" @@ -230,47 +228,39 @@ void LatteDecompiler_analyzeALUClause(LatteDecompilerShaderContext* shaderContex // check input for uniform access if( aluInstruction.sourceOperand[f].sel == 0xFFFFFFFF ) continue; // source operand not set/used + // about uniform register and buffer access tracking: + // for absolute indices we can determine a maximum size that is accessed + // relative accesses are tricky because the upper bound of accessed indices is unknown + // worst case we have to load the full file (256 * 16 byte entries) or for buffers an arbitrary upper bound (64KB in our case) if( GPU7_ALU_SRC_IS_CFILE(aluInstruction.sourceOperand[f].sel) ) { - // uniform register access - - // relative register file accesses are tricky because the range of possible indices is unknown - // worst case we have to load the full file (256 * 16 byte entries) - // by tracking the accessed base indices the shader analyzer can determine bounds for the potentially accessed ranges - - shaderContext->analyzer.uniformRegisterAccess = true; if (aluInstruction.sourceOperand[f].rel) { - shaderContext->analyzer.uniformRegisterDynamicAccess = true; - shaderContext->analyzer.uniformRegisterAccessIndices.emplace_back(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), true); + shaderContext->analyzer.uniformRegisterAccessTracker.TrackAccess(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), true); } else { _remapUniformAccess(shaderContext, true, 0, GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel)); - shaderContext->analyzer.uniformRegisterAccessIndices.emplace_back(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), false); + shaderContext->analyzer.uniformRegisterAccessTracker.TrackAccess(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), false); } } else if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction.sourceOperand[f].sel) ) { // uniform bank 0 (uniform buffer with index cfInstruction->cBank0Index) uint32 uniformBufferIndex = cfInstruction->cBank0Index; - if( uniformBufferIndex >= LATTE_NUM_MAX_UNIFORM_BUFFERS) - debugBreakpoint(); - shaderContext->analyzer.uniformBufferAccessMask |= (1<analyzer.uniformBufferDynamicAccessMask |= (1<cBank0AddrBase); + cemu_assert(uniformBufferIndex < LATTE_NUM_MAX_UNIFORM_BUFFERS); + uint32 offset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction.sourceOperand[f].sel)+cfInstruction->cBank0AddrBase; + _remapUniformAccess(shaderContext, false, uniformBufferIndex, offset); + shaderContext->analyzer.uniformBufferAccessTracker[uniformBufferIndex].TrackAccess(offset, aluInstruction.sourceOperand[f].rel); } else if( GPU7_ALU_SRC_IS_CBANK1(aluInstruction.sourceOperand[f].sel) ) { // uniform bank 1 (uniform buffer with index cfInstruction->cBank1Index) uint32 uniformBufferIndex = cfInstruction->cBank1Index; - if( uniformBufferIndex >= LATTE_NUM_MAX_UNIFORM_BUFFERS) - debugBreakpoint(); - shaderContext->analyzer.uniformBufferAccessMask |= (1<analyzer.uniformBufferDynamicAccessMask |= (1<cBank1AddrBase); + cemu_assert(uniformBufferIndex < LATTE_NUM_MAX_UNIFORM_BUFFERS); + uint32 offset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction.sourceOperand[f].sel)+cfInstruction->cBank1AddrBase; + _remapUniformAccess(shaderContext, false, uniformBufferIndex, offset); + shaderContext->analyzer.uniformBufferAccessTracker[uniformBufferIndex].TrackAccess(offset, aluInstruction.sourceOperand[f].rel); } else if( GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel) ) { @@ -360,8 +350,7 @@ void LatteDecompiler_analyzeTEXClause(LatteDecompilerShaderContext* shaderContex if( texInstruction.textureFetch.textureIndex >= 0x80 && texInstruction.textureFetch.textureIndex <= 0x8F ) { uint32 uniformBufferIndex = texInstruction.textureFetch.textureIndex - 0x80; - shaderContext->analyzer.uniformBufferAccessMask |= (1<analyzer.uniformBufferDynamicAccessMask |= (1<analyzer.uniformBufferAccessTracker[uniformBufferIndex].TrackAccess(0, true); } else if( texInstruction.textureFetch.textureIndex == 0x9F && shader->shaderType == LatteConst::ShaderType::Geometry ) { @@ -486,11 +475,11 @@ namespace LatteDecompiler continue; sint32 textureBindingPoint; if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) - textureBindingPoint = i + CEMU_VS_TEX_UNIT_BASE; + textureBindingPoint = i + LATTE_CEMU_VS_TEX_UNIT_BASE; else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) - textureBindingPoint = i + CEMU_GS_TEX_UNIT_BASE; + textureBindingPoint = i + LATTE_CEMU_GS_TEX_UNIT_BASE; else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) - textureBindingPoint = i + CEMU_PS_TEX_UNIT_BASE; + textureBindingPoint = i + LATTE_CEMU_PS_TEX_UNIT_BASE; decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = textureBindingPoint; } @@ -576,7 +565,7 @@ namespace LatteDecompiler // for Vulkan we use consecutive indices for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { - if ((decompilerContext->analyzer.uniformBufferAccessMask&(1 << i)) == 0) + if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; sint32 uniformBindingPoint = i; if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) @@ -592,7 +581,7 @@ namespace LatteDecompiler // for OpenGL we use the relative buffer index for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { - if ((decompilerContext->analyzer.uniformBufferAccessMask&(1 << i)) == 0) + if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; sint32 uniformBindingPoint = i; if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) @@ -765,17 +754,24 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD LatteDecompiler_analyzeSubroutine(shaderContext, subroutineAddr); } // decide which uniform mode to use - if(shaderContext->analyzer.uniformBufferAccessMask != 0 && shaderContext->analyzer.uniformRegisterAccess ) - debugBreakpoint(); // not allowed - if(shaderContext->analyzer.uniformBufferDynamicAccessMask != 0 ) + bool hasAnyDynamicBufferAccess = false; + bool hasAnyBufferAccess = false; + for(auto& it : shaderContext->analyzer.uniformBufferAccessTracker) + { + if( it.HasRelativeAccess() ) + hasAnyDynamicBufferAccess = true; + if( it.HasAccess() ) + hasAnyBufferAccess = true; + } + if (hasAnyDynamicBufferAccess) { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; } - else if(shaderContext->analyzer.uniformRegisterDynamicAccess ) + else if(shaderContext->analyzer.uniformRegisterAccessTracker.HasRelativeAccess() ) { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE; } - else if(shaderContext->analyzer.uniformBufferAccessMask != 0 || shaderContext->analyzer.uniformRegisterAccess != 0 ) + else if(hasAnyBufferAccess || shaderContext->analyzer.uniformRegisterAccessTracker.HasAccess() ) { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED; } @@ -783,16 +779,18 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_NONE; } - // generate list of uniform buffers based on uniformBufferAccessMask (for faster access) - shader->uniformBufferListCount = 0; + // generate compact list of uniform buffers (for faster access) + cemu_assert_debug(shader->list_quickBufferList.empty()); for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { - if( !HAS_FLAG(shaderContext->analyzer.uniformBufferAccessMask, (1<analyzer.uniformBufferAccessTracker[i].HasAccess() ) continue; - shader->uniformBufferList[shader->uniformBufferListCount] = i; - shader->uniformBufferListCount++; + LatteDecompilerShader::QuickBufferEntry entry; + entry.index = i; + entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16; + shader->list_quickBufferList.push_back(entry); } - // get dimension of each used textures + // get dimension of each used texture _LatteRegisterSetTextureUnit* texRegs = nullptr; if( shader->shaderType == LatteConst::ShaderType::Vertex ) texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_VS; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index 5ce0b76f..f3d2c7a8 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -246,6 +246,22 @@ static void _appendPVPS(LatteDecompilerShaderContext* shaderContext, StringBuf* _appendChannel(src, aluUnit); } +std::string _FormatFloatAsGLSLConstant(float f) +{ + char floatAsStr[64]; + size_t floatAsStrLen = fmt::format_to_n(floatAsStr, 64, "{:#}", f).size; + size_t floatAsStrLenOrg = floatAsStrLen; + if(floatAsStrLen > 0 && floatAsStr[floatAsStrLen-1] == '.') + { + floatAsStr[floatAsStrLen] = '0'; + floatAsStrLen++; + } + cemu_assert(floatAsStrLen < 50); // constant suspiciously long? + floatAsStr[floatAsStrLen] = '\0'; + cemu_assert_debug(floatAsStrLen >= 3); // shortest possible form is "0.0" + return floatAsStr; +} + // tracks PV/PS and register backups struct ALUClauseTemporariesState { @@ -507,7 +523,7 @@ void _emitRegisterAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 { _emitTypeConversionPrefix(shaderContext, registerElementDataType, dataType); } - if(shaderContext->typeTracker.useArrayGPRs ) + if (shaderContext->typeTracker.useArrayGPRs) src->add("R"); else src->addFmt("R{}", gprIndex); @@ -540,6 +556,26 @@ void _emitRegisterAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 _emitTypeConversionSuffix(shaderContext, registerElementDataType, dataType); } +// optimized variant of _emitRegisterAccessCode for raw one channel reads +void _emitRegisterChannelAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, sint32 channel, sint32 dataType) +{ + cemu_assert_debug(gprIndex >= 0 && gprIndex <= 127); + cemu_assert_debug(channel >= 0 && channel < 4); + StringBuf* src = shaderContext->shaderSource; + sint32 registerElementDataType = shaderContext->typeTracker.defaultDataType; + _emitTypeConversionPrefix(shaderContext, registerElementDataType, dataType); + if (shaderContext->typeTracker.useArrayGPRs) + src->add("R"); + else + src->addFmt("R{}", gprIndex); + _appendRegisterTypeSuffix(src, registerElementDataType); + if (shaderContext->typeTracker.useArrayGPRs) + src->addFmt("[{}]", gprIndex); + src->add("."); + src->add(_getElementStrByIndex(channel)); + _emitTypeConversionSuffix(shaderContext, registerElementDataType, dataType); +} + void _emitALURegisterInputAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) { StringBuf* src = shaderContext->shaderSource; @@ -906,10 +942,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec exponent -= 127; if ((constVal & 0xFF) == 0 && exponent >= -10 && exponent <= 10) { - char floatAsStr[32]; - size_t floatAsStrLen = fmt::format_to_n(floatAsStr, 32, "{:#}", *(float*)&constVal).size; - cemu_assert_debug(floatAsStrLen >= 3); // shortest possible form is "0.0" - src->add(std::string_view(floatAsStr, floatAsStrLen)); + src->add(_FormatFloatAsGLSLConstant(*(float*)&constVal)); } else src->addFmt("intBitsToFloat(0x{:08x})", constVal); @@ -2124,63 +2157,31 @@ void _emitALUClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecomp /* * Emits code to access one component (xyzw) of the texture coordinate input vector */ -void _emitTEXSampleCoordInputComponent(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction, sint32 componentIndex, sint32 varType) +void _emitTEXSampleCoordInputComponent(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction, sint32 componentIndex, sint32 interpretSrcAsType) { + cemu_assert(componentIndex >= 0 && componentIndex < 4); + cemu_assert_debug(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_SIGNED_INT || interpretSrcAsType == LATTE_DECOMPILER_DTYPE_FLOAT); StringBuf* src = shaderContext->shaderSource; - if( componentIndex >= 4 ) + sint32 elementSel = texInstruction->textureFetch.srcSel[componentIndex]; + if (elementSel < 4) { - debugBreakpoint(); + _emitRegisterChannelAccessCode(shaderContext, texInstruction->srcGpr, elementSel, interpretSrcAsType); return; } - sint32 elementSel = texInstruction->textureFetch.srcSel[componentIndex]; const char* resultElemTable[4] = {"x","y","z","w"}; - if( varType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + if(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { - if (elementSel < 4) - { - if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) - src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); - else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) - src->addFmt("floatBitsToInt({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); - else - { - cemu_assert_unimplemented(); - } - } - else if( elementSel == 4 ) + if( elementSel == 4 ) src->add("floatBitsToInt(0.0)"); else if( elementSel == 5 ) src->add("floatBitsToInt(1.0)"); - else - { - cemu_assert_unimplemented(); - } } - else if( varType == LATTE_DECOMPILER_DTYPE_FLOAT ) + else if(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_FLOAT ) { - if (elementSel < 4) - { - if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) - src->addFmt("intBitsToFloat({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); - else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) - src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); - else - { - cemu_assert_unimplemented(); - } - } - else if( elementSel == 4 ) - src->addFmt("0.0"); + if( elementSel == 4 ) + src->add("0.0"); else if( elementSel == 5 ) - src->addFmt("1.0"); - else - { - cemu_assert_unimplemented(); - } - } - else - { - cemu_assert_unimplemented(); + src->add("1.0"); } } @@ -2425,10 +2426,6 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt cemu_assert_unimplemented(); src->add("texture("); } - if( texInstruction->textureFetch.srcSel[0] >= 4 ) - cemu_assert_unimplemented(); - if( texInstruction->textureFetch.srcSel[1] >= 4 ) - cemu_assert_unimplemented(); src->addFmt("{}{}, ", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); // for textureGather() add shift (todo: depends on rounding mode set in sampler registers?) @@ -2450,7 +2447,7 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt } } - + const sint32 texCoordDataType = (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT; if(useTexelCoordinates) { // handle integer coordinates for texelFetch @@ -2458,9 +2455,9 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt { src->add("ivec2("); src->add("vec2("); - _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, texCoordDataType); src->addFmt(", "); - _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, texCoordDataType); src->addFmt(")*uf_tex{}Scale", texInstruction->textureFetch.textureIndex); // close vec2 and scale @@ -2480,7 +2477,7 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt else cemu_assert_debug(false); } - else + else /* useTexelCoordinates == false */ { // float coordinates if ( (texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_L || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) ) @@ -2544,10 +2541,8 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt else if( texDim == Latte::E_DIM::DIM_CUBEMAP ) { // 2 coords + faceId - if( texInstruction->textureFetch.srcSel[0] >= 4 || texInstruction->textureFetch.srcSel[1] >= 4 ) - { - debugBreakpoint(); - } + cemu_assert_debug(texInstruction->textureFetch.srcSel[0] < 4); + cemu_assert_debug(texInstruction->textureFetch.srcSel[1] < 4); src->add("vec4("); src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); @@ -2562,8 +2557,11 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt else { // 2 coords - src->add(_getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); - + src->add("vec2("); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); // avoid truncate to effectively round downwards on texel edges if (ActiveSettings::ForceSamplerRoundToPrecision()) src->addFmt("+ vec2(1.0)/vec2(textureSize({}{}, 0))/512.0", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); @@ -2571,9 +2569,11 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt // lod or lod bias parameter if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LB || texOpcode == GPU7_TEX_INST_SAMPLE_C_L) { - if( texInstruction->textureFetch.srcSel[3] >= 4 ) - debugBreakpoint(); - src->addFmt(",{}", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer0)); + src->add(","); + if(texOpcode == GPU7_TEX_INST_SAMPLE_LB) + src->add(_FormatFloatAsGLSLConstant((float)texInstruction->textureFetch.lodBias / 16.0f)); + else + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 3, LATTE_DECOMPILER_DTYPE_FLOAT); } else if( texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ ) { @@ -4037,8 +4037,8 @@ void LatteDecompiler_emitGLSLShader(LatteDecompilerShaderContext* shaderContext, for (auto& subroutineInfo : shaderContext->list_subroutines) { sint32 subroutineMaxStackDepth = 0; - src->addFmt("bool activeMaskStackSub%04x[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 1); - src->addFmt("bool activeMaskStackCSub%04x[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 2); + src->addFmt("bool activeMaskStackSub{:04x}[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 1); + src->addFmt("bool activeMaskStackCSub{:04x}[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 2); } } // helper variables for cube maps (todo: Only emit when used) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp index 0bd4eb6f..21cae093 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp @@ -37,36 +37,14 @@ namespace LatteDecompiler } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { - // here we try to predict the accessed range so we dont have to upload the whole register file - // we assume that if there is a fixed-index access on an index higher than a relative access, it bounds the prior relative access - sint16 highestAccessIndex = -1; - bool highestAccessIndexIsRel = false; - for(auto& accessItr : decompilerContext->analyzer.uniformRegisterAccessIndices) - { - if (accessItr.index > highestAccessIndex || (accessItr.index == highestAccessIndex && accessItr.isRelative && !highestAccessIndexIsRel)) - { - highestAccessIndex = accessItr.index; - highestAccessIndexIsRel = accessItr.isRelative; - } - } - if (highestAccessIndex < 0) - highestAccessIndex = 0; - - uint32 cfileSize; - if (highestAccessIndexIsRel) - cfileSize = 256; - else - cfileSize = highestAccessIndex + 1; - - // full uniform register file has to be present + uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(256); + // full or partial uniform register file has to be present if (shaderType == LatteConst::ShaderType::Vertex) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterVS[{}];" _CRLF, cfileSize); else if (shaderType == LatteConst::ShaderType::Pixel) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterPS[{}];" _CRLF, cfileSize); else if (shaderType == LatteConst::ShaderType::Geometry) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterGS[{}];" _CRLF, cfileSize); - else - debugBreakpoint(); uniformOffsets.offset_uniformRegister = uniformCurrentOffset; uniformOffsets.count_uniformRegister = cfileSize; uniformCurrentOffset += 16 * cfileSize; @@ -168,7 +146,7 @@ namespace LatteDecompiler { for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { - if ((decompilerContext->analyzer.uniformBufferAccessMask&(1 << i)) == 0) + if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; cemu_assert_debug(decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i] >= 0); @@ -178,7 +156,7 @@ namespace LatteDecompiler shaderSrc->addFmt("uniform {}{}" _CRLF, _getShaderUniformBlockInterfaceName(decompilerContext->shaderType), i); shaderSrc->add("{" _CRLF); - shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE); + shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(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 53fb61ef..ac2a1fe1 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h @@ -57,6 +57,7 @@ struct LatteDecompilerTEXInstruction sint8 offsetY{}; sint8 offsetZ{}; bool unnormalized[4]{}; // set if texture coordinates are in [0,dim] range instead of [0,1] + sint8 lodBias{}; // divide by 16 to get actual value }textureFetch; // memRead struct @@ -125,19 +126,66 @@ struct LatteDecompilerCFInstruction LatteDecompilerCFInstruction& operator=(LatteDecompilerCFInstruction&& mE) = default; }; -struct LatteDecompilerCFileAccess -{ - LatteDecompilerCFileAccess(uint8 index, bool isRelative) : index(index), isRelative(isRelative) {}; - uint8 index; - bool isRelative; -}; - struct LatteDecompilerSubroutineInfo { uint32 cfAddr; std::vector instructions; }; +// helper struct to track the highest accessed offset within a buffer +struct LatteDecompilerBufferAccessTracker +{ + bool hasStaticIndexAccess{false}; + bool hasDynamicIndexAccess{false}; + sint32 highestAccessDynamicIndex{0}; + sint32 highestAccessStaticIndex{0}; + + // track access, index is the array index and not a byte offset + void TrackAccess(sint32 index, bool isDynamicIndex) + { + if (isDynamicIndex) + { + hasDynamicIndexAccess = true; + if (index > highestAccessDynamicIndex) + highestAccessDynamicIndex = index; + } + else + { + hasStaticIndexAccess = true; + if (index > highestAccessStaticIndex) + highestAccessStaticIndex = index; + } + } + + sint32 DetermineSize(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 + 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; + } + + bool HasAccess() const + { + return hasStaticIndexAccess || hasDynamicIndexAccess; + } + + bool HasRelativeAccess() const + { + return hasDynamicIndexAccess; + } +}; + struct LatteDecompilerShaderContext { LatteDecompilerOutput_t* output; @@ -174,12 +222,9 @@ struct LatteDecompilerShaderContext bool isPointsPrimitive{}; // set if current render primitive is points bool outputPointSize{}; // set if the current shader should output the point size std::bitset<256> inputAttributSemanticMask; // one set bit for every used semanticId - todo: there are only 128 bit available semantic locations? The MSB has special meaning? - // uniform - bool uniformRegisterAccess; // set to true if cfile (uniform register) is accessed - bool uniformRegisterDynamicAccess; // set to true if cfile (uniform register) is accessed with a dynamic index - uint32 uniformBufferAccessMask; // 1 bit per buffer, set if the uniform buffer is accessed - uint32 uniformBufferDynamicAccessMask; // 1 bit per buffer, set if the uniform buffer is accessed by dynamic index - std::vector uniformRegisterAccessIndices; + // uniforms + LatteDecompilerBufferAccessTracker uniformRegisterAccessTracker; + LatteDecompilerBufferAccessTracker uniformBufferAccessTracker[LATTE_NUM_MAX_UNIFORM_BUFFERS]; // ssbo bool hasSSBORead; // shader has instructions that read from SSBO bool hasSSBOWrite; // shader has instructions that write to SSBO diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index c9541470..584af40c 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -19,20 +19,17 @@ static GLuint _genTextureHandleGL() return texIdPool[texIdPoolIndex - 1]; } -LatteTextureGL::LatteTextureGL(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, +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(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth) + : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth) { - GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget); + GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget, true); // set format info FormatInfoGL glFormatInfo; GetOpenGLFormatInfo(isDepth, format, dim, &glFormatInfo); this->glInternalFormat = glFormatInfo.glInternalFormat; this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format? - // bind texture - g_renderer->texture_bindAndActivateRawTex(this, textureUnit); - LatteTextureGL::InitTextureState(); // set debug name bool useGLDebugNames = false; #ifdef CEMU_DEBUG_ASSERT @@ -54,9 +51,8 @@ LatteTextureGL::~LatteTextureGL() catchOpenGLError(); } -void LatteTextureGL::GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget) +void LatteTextureGL::GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType) { - texId = _genTextureHandleGL(); if (dim == Latte::E_DIM::DIM_2D) texTarget = GL_TEXTURE_2D; else if (dim == Latte::E_DIM::DIM_1D) @@ -73,6 +69,10 @@ void LatteTextureGL::GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& te { cemu_assert_unimplemented(); } + if(createForTargetType) + texId = glCreateTextureWrapper(texTarget); // initializes the texture to texTarget (equivalent to calling glGenTextures + glBindTexture) + else + glGenTextures(1, &texId); } LatteTextureView* LatteTextureGL::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) @@ -80,18 +80,6 @@ LatteTextureView* LatteTextureGL::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFF return new LatteTextureViewGL(this, dim, format, firstMip, mipCount, firstSlice, sliceCount); } -void LatteTextureGL::InitTextureState() -{ - // init texture with some default parameters (todo - this shouldn't be necessary if we properly set parameters when a texture is used) - catchOpenGLError(); - glTexParameteri(glTexTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(glTexTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(glTexTarget, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(glTexTarget, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); - catchOpenGLError(); -} - void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, FormatInfoGL* formatInfoOut) { formatInfoOut->isUsingAlternativeFormat = false; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h index fabd1bac..9169bb29 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h @@ -6,14 +6,12 @@ class LatteTextureGL : public LatteTexture { public: - LatteTextureGL(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, + 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); ~LatteTextureGL(); - static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget); - - void InitTextureState() override; + static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType); protected: LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp index f33fd7ff..29085642 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp @@ -11,7 +11,7 @@ LatteTextureViewGL::LatteTextureViewGL(LatteTextureGL* texture, Latte::E_DIM dim firstSlice != 0 || firstMip != 0 || mipCount != texture->mipLevels || sliceCount != texture->depth || forceCreateNewTexId) { - LatteTextureGL::GenerateEmptyTextureFromGX2Dim(dim, glTexId, glTexTarget); + LatteTextureGL::GenerateEmptyTextureFromGX2Dim(dim, glTexId, glTexTarget, false); this->glInternalFormat = 0; InitAliasView(); } diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 6bfa890c..d3dd6803 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -78,8 +78,6 @@ static const GLenum glAlphaTestFunc[] = GL_ALWAYS }; - - OpenGLRenderer::OpenGLRenderer() { glRendererState.useTextureUploadBuffer = false; @@ -281,10 +279,6 @@ void OpenGLRenderer::Initialize() cemuLog_log(LogType::Force, "ARB_copy_image: {}", (glCopyImageSubData != NULL) ? "available" : "not supported"); cemuLog_log(LogType::Force, "NV_depth_buffer_float: {}", (glDepthRangedNV != NULL) ? "available" : "not supported"); - // generate default frame buffer - glGenFramebuffers(1, &m_defaultFramebufferId); - catchOpenGLError(); - // enable framebuffer SRGB support glEnable(GL_FRAMEBUFFER_SRGB); @@ -378,9 +372,6 @@ void OpenGLRenderer::UnregisterOpenGLCallbacks() { m_openGLCallbacks = nullptr; } - -bool IsRunningInWine(); - void OpenGLRenderer::GetVendorInformation() { // example vendor strings: @@ -584,17 +575,16 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu sint32 effectiveHeight; LatteTexture_getEffectiveSize(texView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, 0); - g_renderer->shader_unbind(RendererShader::ShaderType::kVertex); - g_renderer->shader_unbind(RendererShader::ShaderType::kGeometry); - g_renderer->shader_unbind(RendererShader::ShaderType::kFragment); - shader->Bind(); + shader_unbind(RendererShader::ShaderType::kGeometry); + shader_bind(shader->GetVertexShader()); + shader_bind(shader->GetFragmentShader()); shader->SetUniformParameters(*texView, { effectiveWidth, effectiveHeight }, { imageWidth, imageHeight }); // set viewport glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight); LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView; - g_renderer->texture_bindAndActivate(texView, 0); + texture_bindAndActivate(texView, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); texViewGL->samplerState.filterMag = 0xFFFFFFFF; @@ -609,7 +599,7 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu glEnable(GL_FRAMEBUFFER_SRGB); // unbind texture - g_renderer->texture_bindAndActivate(nullptr, 0); + texture_bindAndActivate(nullptr, 0); catchOpenGLError(); @@ -1013,8 +1003,9 @@ void OpenGLRenderer::texture_destroy(LatteTexture* hostTexture) delete hostTexture; } -void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* 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; @@ -1035,25 +1026,25 @@ void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture) if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) { cemu_assert_debug(effectiveBaseDepth == 1); - glTexStorage2D(GL_TEXTURE_2D, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); + glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); } else if (hostTexture->dim == Latte::E_DIM::DIM_1D) { cemu_assert_debug(effectiveBaseHeight == 1); cemu_assert_debug(effectiveBaseDepth == 1); - glTexStorage1D(GL_TEXTURE_1D, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth); + glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth); } else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) { - glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_3D) { - glTexStorage3D(GL_TEXTURE_3D, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) { - glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); + glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); } else { @@ -1065,7 +1056,6 @@ void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture) void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { auto hostTexture = (LatteTextureGL*)hostTextureGeneric; - sint32 effectiveWidth = width; sint32 effectiveHeight = height; sint32 effectiveDepth = depth; @@ -1076,58 +1066,37 @@ void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, s LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo); // upload slice catchOpenGLError(); + if (mipIndex >= hostTexture->maxPossibleMipLevels) + { + cemuLog_logDebug(LogType::Force, "2D texture mip level allocated out of range"); + return; + } if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) { if (glFormatInfo.glIsCompressed) - { - if (glCompressedTextureSubImage2D) - glCompressedTextureSubImage2D(hostTexture->glId_texture, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glInternalFormat, imageSize, pixelData); - else - glCompressedTexSubImage2D(GL_TEXTURE_2D, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glInternalFormat, imageSize, pixelData); - } + glCompressedTextureSubImage2DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glInternalFormat, imageSize, pixelData); else - { - if (mipIndex < hostTexture->maxPossibleMipLevels) - glTexSubImage2D(GL_TEXTURE_2D, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); - else - cemuLog_logDebug(LogType::Force, "2D texture mip level allocated out of range"); - } + glTextureSubImage2DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); } else if (hostTexture->dim == Latte::E_DIM::DIM_1D) { - if (glFormatInfo.glIsCompressed == true) - cemu_assert_unimplemented(); - glTexSubImage1D(GL_TEXTURE_1D, mipIndex, 0, width, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); + if (glFormatInfo.glIsCompressed) + glCompressedTextureSubImage1DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, width, glFormatInfo.glInternalFormat, imageSize, pixelData); + else + glTextureSubImage1DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, width, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); } - else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA || + hostTexture->dim == Latte::E_DIM::DIM_3D || + hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) { if (glFormatInfo.glIsCompressed) - glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); + glCompressedTextureSubImage3DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); else - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_3D) - { - if (glFormatInfo.glIsCompressed) - glCompressedTexSubImage3D(GL_TEXTURE_3D, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); - else - glTexSubImage3D(GL_TEXTURE_3D, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) - { - if (glFormatInfo.glIsCompressed) - glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mipIndex, 0, 0, sliceIndex, width, height, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); - else - glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mipIndex, 0, 0, sliceIndex, width, height, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); - } - else - { - cemu_assert_debug(false); + glTextureSubImage3DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); } catchOpenGLError(); } - // use persistent buffers to upload data void OpenGLRenderer_texture_loadSlice_viaBuffers(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { @@ -1243,10 +1212,10 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 glClearTexSubImage(hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, formatInfoGL.glSuppliedFormat, formatInfoGL.glSuppliedFormatType, NULL); } -LatteTexture* OpenGLRenderer::texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, +LatteTexture* OpenGLRenderer::texture_createTextureEx(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) { - return new LatteTextureGL(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); + return new LatteTextureGL(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); } @@ -1262,42 +1231,18 @@ void OpenGLRenderer::texture_setActiveTextureUnit(sint32 index) void OpenGLRenderer::texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) { const auto textureViewGL = (LatteTextureViewGL*)textureView; - cemu_assert_debug(textureUnit < (sizeof(LatteBoundTexturesBackup) / sizeof(LatteBoundTexturesBackup[0]))); // don't call glBindTexture if the texture is already bound - if (LatteBoundTextures[textureUnit] == textureViewGL) + if (m_latteBoundTextures[textureUnit] == textureViewGL) { texture_setActiveTextureUnit(textureUnit); return; // already bound } // bind - LatteBoundTextures[textureUnit] = textureViewGL; + m_latteBoundTextures[textureUnit] = textureViewGL; texture_setActiveTextureUnit(textureUnit); if (textureViewGL) { glBindTexture(textureViewGL->glTexTarget, textureViewGL->glTexId); - texUnitTexId[textureUnit] = textureViewGL->glTexId; - texUnitTexTarget[textureUnit] = textureViewGL->glTexTarget; - } -} - -void OpenGLRenderer::texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) -{ - cemu_assert_debug(textureUnit < (sizeof(LatteBoundTexturesBackup) / sizeof(LatteBoundTexturesBackup[0]))); - // don't call glBindTexture if the texture is already bound - if (LatteBoundTextures[textureUnit] == texture) - { - texture_setActiveTextureUnit(textureUnit); - return; // already bound - } - // bind - LatteBoundTextures[textureUnit] = texture; - texture_setActiveTextureUnit(textureUnit); - if (texture) - { - auto textureGL = (LatteTextureGL*)texture; - glBindTexture(textureGL->glTexTarget, textureGL->glId_texture); - texUnitTexId[textureUnit] = textureGL->glId_texture; - texUnitTexTarget[textureUnit] = textureGL->glTexTarget; } } @@ -1305,18 +1250,18 @@ void OpenGLRenderer::texture_notifyDelete(LatteTextureView* textureView) { for (uint32 i = 0; i < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3; i++) { - if (LatteBoundTextures[i] == textureView) - LatteBoundTextures[i] = nullptr; + if (m_latteBoundTextures[i] == textureView) + m_latteBoundTextures[i] = nullptr; } } -// similar to _bindAndActivate() but doesn't call _setActiveTextureUnit() if texture is already bound -void OpenGLRenderer::texture_bindOnly(LatteTextureView* textureView1, uint32 textureUnit) +// set Latte texture, on the OpenGL renderer this behaves like _bindAndActivate() but doesn't call _setActiveTextureUnit() if the texture is already bound +void OpenGLRenderer::texture_setLatteTexture(LatteTextureView* textureView1, uint32 textureUnit) { auto textureView = ((LatteTextureViewGL*)textureView1); cemu_assert_debug(textureUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3); - if (LatteBoundTextures[textureUnit] == textureView) + if (m_latteBoundTextures[textureUnit] == textureView) return; if (textureView == nullptr) return; @@ -1324,45 +1269,17 @@ void OpenGLRenderer::texture_bindOnly(LatteTextureView* textureView1, uint32 tex if (glBindTextureUnit) { glBindTextureUnit(textureUnit, textureView->glTexId); - LatteBoundTextures[textureUnit] = textureView; - texUnitTexId[textureUnit] = textureView->glTexId; - texUnitTexTarget[textureUnit] = textureView->glTexTarget; + m_latteBoundTextures[textureUnit] = textureView; activeTextureUnit = -1; } else { texture_setActiveTextureUnit(textureUnit); glBindTexture(textureView->glTexTarget, textureView->glTexId); - LatteBoundTextures[textureUnit] = textureView; - texUnitTexId[textureUnit] = textureView->glTexId; - texUnitTexTarget[textureUnit] = textureView->glTexTarget; + m_latteBoundTextures[textureUnit] = textureView; } } -void OpenGLRenderer::texture_rememberBoundTexture(uint32 textureUnit) -{ - cemu_assert_debug(texUnitBackupSlotUsed[textureUnit] == false); - texUnitBackupSlotUsed[textureUnit] = true; - LatteBoundTexturesBackup[textureUnit] = LatteBoundTextures[textureUnit]; - texUnitTexIdBackup[textureUnit] = texUnitTexId[textureUnit]; - texUnitTexTargetBackup[textureUnit] = texUnitTexTarget[textureUnit]; -} - -void OpenGLRenderer::texture_restoreBoundTexture(uint32 textureUnit) -{ - cemu_assert_debug(texUnitBackupSlotUsed[textureUnit] == true); - texUnitBackupSlotUsed[textureUnit] = false; - if (LatteBoundTextures[textureUnit] == LatteBoundTexturesBackup[textureUnit]) - { - return; // already bound - } - LatteBoundTextures[textureUnit] = LatteBoundTexturesBackup[textureUnit]; - texUnitTexId[textureUnit] = texUnitTexIdBackup[textureUnit]; - texUnitTexTarget[textureUnit] = texUnitTexTargetBackup[textureUnit]; - texture_setActiveTextureUnit(textureUnit); - glBindTexture(texUnitTexTargetBackup[textureUnit], texUnitTexIdBackup[textureUnit]); -} - void OpenGLRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) { @@ -1451,31 +1368,25 @@ RendererShader* OpenGLRenderer::shader_create(RendererShader::ShaderType type, u void OpenGLRenderer::shader_bind(RendererShader* shader) { auto shaderGL = (RendererShaderGL*)shader; - GLbitfield shaderBit; - const auto program = shaderGL->GetProgram(); - switch(shader->GetType()) { case RendererShader::ShaderType::kVertex: if (program == prevVertexShaderProgram) return; - shaderBit = GL_VERTEX_SHADER_BIT; prevVertexShaderProgram = program; break; case RendererShader::ShaderType::kFragment: if (program == prevPixelShaderProgram) return; - shaderBit = GL_FRAGMENT_SHADER_BIT; prevPixelShaderProgram = program; break; case RendererShader::ShaderType::kGeometry: if (program == prevGeometryShaderProgram) return; - shaderBit = GL_GEOMETRY_SHADER_BIT; prevGeometryShaderProgram = program; break; @@ -1488,13 +1399,6 @@ void OpenGLRenderer::shader_bind(RendererShader* shader) catchOpenGLError(); } -void OpenGLRenderer::shader_bind(GLuint program, GLbitfield shaderType) -{ - catchOpenGLError(); - glUseProgramStages(m_pipeline, shaderType, program); - catchOpenGLError(); -} - void OpenGLRenderer::shader_unbind(RendererShader::ShaderType shaderType) { switch (shaderType) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index fe66348e..b46109f8 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -79,13 +79,10 @@ public: 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; - LatteTexture* texture_createTextureEx(uint32 textureUnit, 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) override; + LatteTexture* texture_createTextureEx(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) override; - void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) override; - void texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) override; - void texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) override; - void texture_rememberBoundTexture(uint32 textureUnit) override; - void texture_restoreBoundTexture(uint32 textureUnit) override; + void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) override; + void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit); void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; void texture_notifyDelete(LatteTextureView* textureView); @@ -127,9 +124,8 @@ public: // shader RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; - void shader_bind(RendererShader* shader) override; - void shader_bind(GLuint program, GLbitfield shaderType); - void shader_unbind(RendererShader::ShaderType shaderType) override; + void shader_bind(RendererShader* shader); + void shader_unbind(RendererShader::ShaderType shaderType); // streamout void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; @@ -179,7 +175,6 @@ private: OpenGLCallbacks* m_openGLCallbacks = nullptr; - GLuint m_defaultFramebufferId; GLuint m_pipeline = 0; bool m_isPadViewContext{}; @@ -204,14 +199,7 @@ private: bool m_isXfbActive = false; sint32 activeTextureUnit = 0; - void* LatteBoundTextures[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; - GLuint texUnitTexId[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; - GLenum texUnitTexTarget[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; - - void* LatteBoundTexturesBackup[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; - GLuint texUnitTexIdBackup[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; - GLenum texUnitTexTargetBackup[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; - bool texUnitBackupSlotUsed[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + void* m_latteBoundTextures[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; // attribute stream GLuint glAttributeCacheAB{}; @@ -230,8 +218,6 @@ private: uint32 prevLogicOp = 0; uint32 prevBlendColorConstant[4] = { 0 }; uint8 prevAlphaTestEnable = 0; - uint8 prevAlphaTestFunc = 0; - uint32 prevAlphaTestRefU32 = 0; bool prevDepthEnable = 0; bool prevDepthWriteEnable = 0; Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC prevDepthFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC)-1; @@ -277,9 +263,9 @@ private: std::vector list_queryCacheOcclusion; // cache for unused queries // resource garbage collection - struct bufferCacheReleaseQueueEntry_t + struct BufferCacheReleaseQueueEntry { - bufferCacheReleaseQueueEntry_t(VirtualBufferHeap_t* heap, VirtualBufferHeapEntry_t* entry) : m_heap(heap), m_entry(entry) {}; + BufferCacheReleaseQueueEntry(VirtualBufferHeap_t* heap, VirtualBufferHeapEntry_t* entry) : m_heap(heap), m_entry(entry) {}; void free() { @@ -293,7 +279,7 @@ private: struct { sint32 index; - std::vector bufferCacheEntries; + std::vector bufferCacheEntries; }m_destructionQueues; }; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index d5cec237..51d0d206 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -912,6 +912,21 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta { beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); LatteSHRC_UpdateActiveShaders(); + LatteDecompilerShader* vs = (LatteDecompilerShader*)LatteSHRC_GetActiveVertexShader(); + LatteDecompilerShader* gs = (LatteDecompilerShader*)LatteSHRC_GetActiveGeometryShader(); + LatteDecompilerShader* ps = (LatteDecompilerShader*)LatteSHRC_GetActivePixelShader(); + if (vs) + shader_bind(vs->shader); + else + shader_unbind(RendererShader::ShaderType::kVertex); + if (ps && LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] == 0) + shader_bind(ps->shader); + else + shader_unbind(RendererShader::ShaderType::kFragment); + if (gs) + shader_bind(gs->shader); + else + shader_unbind(RendererShader::ShaderType::kGeometry); endPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); } if (LatteGPUState.activeShaderHasError) @@ -1327,7 +1342,7 @@ uint32 _correctTextureCompSelGL(Latte::E_GX2SURFFMT format, uint32 compSel) return compSel; } -#define quickBindTexture() if( textureIsActive == false ) { g_renderer->texture_bindAndActivate(hostTextureView, hostTextureUnit); textureIsActive = true; } +#define quickBindTexture() if( textureIsActive == false ) { texture_bindAndActivate(hostTextureView, hostTextureUnit); textureIsActive = true; } uint32 _getGLMinFilter(Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filterMin, Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER filterMip) { @@ -1350,11 +1365,9 @@ uint32 _getGLMinFilter(Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filterMi /* * Update channel swizzling and other texture settings for a texture unit * hostTextureView is the texture unit view used on the host side -* The baseGX2TexUnit parameter is used to identify the shader stage in which this texture is accessed */ void OpenGLRenderer::renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler) { - // todo - this is OpenGL-specific, decouple this from the renderer-neutral backend auto hostTextureView = (LatteTextureViewGL*)_hostTextureView; LatteTexture* baseTexture = hostTextureView->baseTexture; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp index 8c8c36d7..c49a57e4 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp @@ -52,7 +52,7 @@ void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s LatteTextureView* sourceView = sourceTexture->GetOrCreateView(srcMip, 1, srcSlice, 1); LatteTextureView* destinationView = destinationTexture->GetOrCreateView(dstMip, 1, dstSlice, 1); - g_renderer->texture_bindAndActivate(sourceView, 0); + texture_bindAndActivate(sourceView, 0); catchOpenGLError(); // setup texture attributes _setDepthCompareMode((LatteTextureViewGL*)sourceView, 0); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp index 5530b4ec..3d46f206 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp @@ -230,11 +230,6 @@ sint32 RendererShaderGL::GetUniformLocation(const char* name) return glGetUniformLocation(m_program, name); } -void RendererShaderGL::SetUniform1iv(sint32 location, void* data, sint32 count) -{ - glProgramUniform1iv(m_program, location, count, (const GLint*)data); -} - void RendererShaderGL::SetUniform2fv(sint32 location, void* data, sint32 count) { glProgramUniform2fv(m_program, location, count, (const GLfloat*)data); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h index abc62358..60c51cc1 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h @@ -18,7 +18,6 @@ public: GLuint GetShaderObject() const { cemu_assert_debug(m_isCompiled); return m_shader_object; } sint32 GetUniformLocation(const char* name) override; - void SetUniform1iv(sint32 location, void* data, sint32 count) override; void SetUniform2fv(sint32 location, void* data, sint32 count) override; void SetUniform4iv(sint32 location, void* data, sint32 count) override; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp index 56011dab..b2966706 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp @@ -1,4 +1,5 @@ #include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" @@ -93,8 +94,7 @@ LatteTextureReadbackInfoGL::~LatteTextureReadbackInfoGL() void LatteTextureReadbackInfoGL::StartTransfer() { cemu_assert(m_textureView); - - g_renderer->texture_bindAndActivate(m_textureView, 0); + ((OpenGLRenderer*)g_renderer.get())->texture_bindAndActivate(m_textureView, 0); // create unsynchronized buffer glGenBuffers(1, &texImageBufferGL); glBindBuffer(GL_PIXEL_PACK_BUFFER, texImageBufferGL); diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index ec2c9a36..2c2ab44b 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -113,13 +113,9 @@ public: virtual void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) = 0; virtual void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) = 0; - virtual LatteTexture* texture_createTextureEx(uint32 textureUnit, 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) = 0; + virtual LatteTexture* texture_createTextureEx(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) = 0; - virtual void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) = 0; - virtual void texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) = 0; - virtual void texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) = 0; - virtual void texture_rememberBoundTexture(uint32 textureUnit) = 0; - virtual void texture_restoreBoundTexture(uint32 textureUnit) = 0; + virtual void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) = 0; virtual void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) = 0; virtual LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) = 0; @@ -138,8 +134,6 @@ public: // shader virtual RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool compileAsync, bool isGfxPackSource) = 0; - virtual void shader_bind(RendererShader* shader) = 0; - virtual void shader_unbind(RendererShader::ShaderType shaderType) = 0; // streamout virtual void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) = 0; diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index 70f5052d..936418a3 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -233,12 +233,6 @@ void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_ } } -void RendererOutputShader::Bind() const -{ - g_renderer->shader_bind(m_vertex_shader); - g_renderer->shader_bind(m_fragment_shader); -} - RendererOutputShader* RendererOutputShader::s_copy_shader; RendererOutputShader* RendererOutputShader::s_copy_shader_ud; diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h index 253990e2..398ac663 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -18,7 +18,6 @@ public: virtual ~RendererOutputShader() = default; void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const; - void Bind() const; RendererShader* GetVertexShader() const { diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.h b/src/Cafe/HW/Latte/Renderer/RendererShader.h index 1c15211f..e3f254c6 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.h @@ -19,8 +19,7 @@ public: virtual bool WaitForCompiled() = 0; virtual sint32 GetUniformLocation(const char* name) = 0; - - virtual void SetUniform1iv(sint32 location, void* data, sint32 count) = 0; + virtual void SetUniform2fv(sint32 location, void* data, sint32 count) = 0; virtual void SetUniform4iv(sint32 location, void* data, sint32 count) = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h index 4e6be012..bf72996e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h @@ -75,10 +75,6 @@ private: VkRenderingAttachmentInfoKHR m_vkColorAttachments[8]; VkRenderingAttachmentInfoKHR m_vkDepthAttachment; VkRenderingAttachmentInfoKHR m_vkStencilAttachment; - //uint8 m_vkColorAttachmentsCount{0}; - bool m_vkHasDepthAttachment{ false }; - bool m_vkHasStencilAttachment{ false }; - std::vector m_usedByPipelines; // PipelineInfo objects which use this renderpass/framebuffer }; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index b41760bc..b5f62707 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -3,9 +3,9 @@ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" -LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, +LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, 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(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth), m_vkr(vkRenderer) + : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth), m_vkr(vkRenderer) { vkObjTex = new VKRObjectTexture(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h index 2131ed9c..714c4e17 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h @@ -9,7 +9,7 @@ class LatteTextureVk : public LatteTexture { public: - LatteTextureVk(class VulkanRenderer* vkRenderer, uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, + LatteTextureVk(class VulkanRenderer* vkRenderer, 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); ~LatteTextureVk(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 804d03cc..970f5517 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -139,7 +139,7 @@ public: } } - ~_ShaderVkThreadPool() + void StopThreads() { m_shutdownThread.store(true); for (uint32 i = 0; i < s_threads.size(); ++i) @@ -149,6 +149,11 @@ public: s_threads.clear(); } + ~_ShaderVkThreadPool() + { + StopThreads(); + } + void CompilerThreadFunc() { while (!m_shutdownThread.load(std::memory_order::relaxed)) @@ -176,6 +181,8 @@ public: } } + bool HasThreadsRunning() const { return !m_shutdownThread; } + public: std::vector s_threads; @@ -195,8 +202,8 @@ RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxH m_compilationState.setValue(COMPILATION_STATE::QUEUED); ShaderVkThreadPool.s_compilationQueue.push_back(this); ShaderVkThreadPool.s_compilationQueueCount.increment(); - ShaderVkThreadPool.StartThreads(); ShaderVkThreadPool.s_compilationQueueMutex.unlock(); + cemu_assert_debug(ShaderVkThreadPool.HasThreadsRunning()); // make sure .StartThreads() was called } RendererShaderVk::~RendererShaderVk() @@ -204,17 +211,22 @@ RendererShaderVk::~RendererShaderVk() VulkanRenderer::GetInstance()->destroyShader(this); } +void RendererShaderVk::Init() +{ + ShaderVkThreadPool.StartThreads(); +} + +void RendererShaderVk::Shutdown() +{ + ShaderVkThreadPool.StopThreads(); +} + sint32 RendererShaderVk::GetUniformLocation(const char* name) { cemu_assert_suspicious(); return 0; } -void RendererShaderVk::SetUniform1iv(sint32 location, void* data, sint32 count) -{ - cemu_assert_suspicious(); -} - void RendererShaderVk::SetUniform2fv(sint32 location, void* data, sint32 count) { cemu_assert_suspicious(); @@ -335,6 +347,7 @@ void RendererShaderVk::CompileInternal(bool isRenderThread) if (!Shader.parse(&Resources, 100, false, messagesParseLink)) { cemuLog_log(LogType::Force, fmt::format("GLSL parsing failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); + cemuLog_logDebug(LogType::Force, "GLSL source:\n{}", m_glslCode); cemu_assert_debug(false); FinishCompilation(); return; @@ -439,7 +452,7 @@ void RendererShaderVk::ShaderCacheLoading_begin(uint64 cacheTitleId) } uint32 spirvCacheMagic = GeneratePrecompiledCacheId(); const std::string cacheFilename = fmt::format("{:016x}_spirv.bin", cacheTitleId); - const std::wstring cachePath = ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename).generic_wstring(); + const fs::path cachePath = ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename); s_spirvCache = FileCache::Open(cachePath, true, spirvCacheMagic); if (s_spirvCache == nullptr) cemuLog_log(LogType::Force, "Unable to open SPIR-V cache {}", cacheFilename); @@ -456,4 +469,4 @@ void RendererShaderVk::ShaderCacheLoading_Close() { delete s_spirvCache; s_spirvCache = nullptr; -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h index 561145f9..f9c3ede1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h @@ -28,8 +28,10 @@ public: RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode); virtual ~RendererShaderVk(); + static void Init(); + static void Shutdown(); + sint32 GetUniformLocation(const char* name) override; - void SetUniform1iv(sint32 location, void* data, sint32 count) override; void SetUniform2fv(sint32 location, void* data, sint32 count) override; void SetUniform4iv(sint32 location, void* data, sint32 count) override; VkShaderModule& GetShaderModule() { return m_shader_module; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index e9936c43..72a1be4c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -3,7 +3,6 @@ #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" -#include #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" @@ -91,4 +90,4 @@ PipelineInfo::~PipelineInfo() // remove from cache VulkanRenderer::GetInstance()->unregisterGraphicsPipeline(this); -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp index d7d139be..ad32b541 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp @@ -1,13 +1,81 @@ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #define VKFUNC_DEFINE #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include // for std::iota #if BOOST_OS_LINUX || BOOST_OS_MACOS #include #endif +#define VULKAN_API_CPU_BENCHMARK 0 // if 1, Cemu will log the CPU time spent per Vulkan API function + bool g_vulkan_available = false; +#if VULKAN_API_CPU_BENCHMARK != 0 +uint64 s_vulkanBenchmarkLastResultsTime = 0; + +struct VulkanBenchmarkFuncInfo +{ + std::string funcName; + uint64 cycles; + uint32 numCalls; +}; + +std::vector s_vulkanBenchmarkFuncs; + +template +auto VkWrapperFuncGenTest(TRet (*func)(Args...), const char* name) +{ + static VulkanBenchmarkFuncInfo _FuncInfo; + static auto _FuncPtrCopy = func; + TRet (*newFunc)(Args...); + if constexpr(std::is_void_v) + { + newFunc = +[](Args... args) { uint64 t = __rdtsc(); _mm_mfence(); _FuncPtrCopy(args...); _mm_mfence(); _FuncInfo.cycles += (__rdtsc() - t); _FuncInfo.numCalls++; }; + } + else + newFunc = +[](Args... args) -> TRet { uint64 t = __rdtsc(); _mm_mfence(); TRet r = _FuncPtrCopy(args...); _mm_mfence(); _FuncInfo.cycles += (__rdtsc() - t); _FuncInfo.numCalls++; return r; }; + if(func && func != newFunc) + _FuncPtrCopy = func; + if(_FuncInfo.funcName.empty()) + { + _FuncInfo = {.funcName = name, .cycles = 0, .numCalls = 0}; + s_vulkanBenchmarkFuncs.emplace_back(&_FuncInfo); + } + return newFunc; +}; +#endif + +// called when a TV SwapBuffers is called +void VulkanBenchmarkPrintResults() +{ +#if VULKAN_API_CPU_BENCHMARK != 0 + // note: This could be done by hooking vk present functions + uint64 currentCycle = __rdtsc(); + uint64 elapsedCycles = currentCycle - s_vulkanBenchmarkLastResultsTime; + s_vulkanBenchmarkLastResultsTime = currentCycle; + double elapsedCyclesDbl = (double)elapsedCycles; + cemuLog_log(LogType::Force, "--- Vulkan API CPU benchmark ---"); + cemuLog_log(LogType::Force, "Elapsed cycles this frame: {:} | Current cycle {:} | NumFunc {:}", elapsedCycles, currentCycle, s_vulkanBenchmarkFuncs.size()); + + std::vector sortedIndices(s_vulkanBenchmarkFuncs.size()); + std::iota(sortedIndices.begin(), sortedIndices.end(), 0); + std::sort(sortedIndices.begin(), sortedIndices.end(), + [](int32_t a, int32_t b) { + return s_vulkanBenchmarkFuncs[a]->cycles > s_vulkanBenchmarkFuncs[b]->cycles; + }); + for (sint32 idx : sortedIndices) + { + auto& func = s_vulkanBenchmarkFuncs[idx]; + if(func->cycles == 0) + return; + cemuLog_log(LogType::Force, "{}: {} cycles ({:.4}%) {} calls", func->funcName.c_str(), func->cycles, ((double)func->cycles / elapsedCyclesDbl) * 100.0, func->numCalls); + func->cycles = 0; + func->numCalls = 0; + } +#endif +} + #if BOOST_OS_WINDOWS bool InitializeGlobalVulkan() @@ -57,7 +125,12 @@ bool InitializeDeviceVulkan(VkDevice device) #define VKFUNC_DEVICE_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" - + +#if VULKAN_API_CPU_BENCHMARK != 0 + #define VKFUNC_DEFINE_CUSTOM(__func) __func = VkWrapperFuncGenTest(__func, #__func) + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#endif + return true; } @@ -121,7 +194,12 @@ bool InitializeDeviceVulkan(VkDevice device) #define VKFUNC_DEVICE_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" - + +#if VULKAN_API_CPU_BENCHMARK != 0 + #define VKFUNC_DEFINE_CUSTOM(__func) __func = VkWrapperFuncGenTest(__func, #__func) + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#endif + return true; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index b50c5a74..ce802ca6 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -14,7 +14,11 @@ extern bool g_vulkan_available; #endif -#ifdef VKFUNC_DEFINE +#ifdef VKFUNC_DEFINE_CUSTOM + #define VKFUNC(__FUNC__) VKFUNC_DEFINE_CUSTOM(__FUNC__) + #define VKFUNC_INSTANCE(__FUNC__) VKFUNC_DEFINE_CUSTOM(__FUNC__) + #define VKFUNC_DEVICE(__FUNC__) VKFUNC_DEFINE_CUSTOM(__FUNC__) +#elif defined(VKFUNC_DEFINE) #define VKFUNC(__FUNC__) NOEXPORT PFN_##__FUNC__ __FUNC__ = nullptr #define VKFUNC_INSTANCE(__FUNC__) NOEXPORT PFN_##__FUNC__ __FUNC__ = nullptr #define VKFUNC_DEVICE(__FUNC__) NOEXPORT PFN_##__FUNC__ __FUNC__ = nullptr @@ -242,4 +246,5 @@ VKFUNC_DEVICE(vkDestroyDescriptorSetLayout); #undef VKFUNC #undef VKFUNC_INSTANCE -#undef VKFUNC_DEVICE \ No newline at end of file +#undef VKFUNC_DEVICE +#undef VKFUNC_DEFINE_CUSTOM diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 74247b9a..0ee9f023 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -59,10 +59,10 @@ uint32 VulkanPipelineStableCache::BeginLoading(uint64 cacheTitleId) // open cache file or create it cemu_assert_debug(s_cache == nullptr); - s_cache = FileCache::Open(pathCacheFile.generic_wstring(), true, LatteShaderCache_getPipelineCacheExtraVersion(cacheTitleId)); + s_cache = FileCache::Open(pathCacheFile, true, LatteShaderCache_getPipelineCacheExtraVersion(cacheTitleId)); if (!s_cache) { - cemuLog_log(LogType::Force, "Failed to open or create Vulkan pipeline cache file: {}", pathCacheFile.generic_string()); + cemuLog_log(LogType::Force, "Failed to open or create Vulkan pipeline cache file: {}", _pathToUtf8(pathCacheFile)); return 0; } else diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 0e6469d4..d8cb4c4d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -26,7 +26,7 @@ #include "Cafe/HW/Latte/Core/LatteTiming.h" // vsync control -#include +#include #ifndef VK_API_VERSION_MAJOR #define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU) @@ -183,7 +183,6 @@ std::vector VulkanRenderer::GetDevices() } -bool IsRunningInWine(); void VulkanRenderer::DetermineVendor() { VkPhysicalDeviceProperties2 properties{}; @@ -350,7 +349,15 @@ VulkanRenderer::VulkanRenderer() create_info.ppEnabledLayerNames = m_layerNames.data(); create_info.enabledLayerCount = m_layerNames.size(); - if ((err = vkCreateInstance(&create_info, nullptr, &m_instance)) != VK_SUCCESS) + err = vkCreateInstance(&create_info, nullptr, &m_instance); + + if (err == VK_ERROR_LAYER_NOT_PRESENT) { + cemuLog_log(LogType::Force, "Failed to enable vulkan validation (VK_LAYER_KHRONOS_validation)"); + create_info.enabledLayerCount = 0; + err = vkCreateInstance(&create_info, nullptr, &m_instance); + } + + if (err != VK_SUCCESS) throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err)); if (!InitializeInstanceVulkan(m_instance)) @@ -584,6 +591,9 @@ VulkanRenderer::VulkanRenderer() { //cemuLog_log(LogType::Force, "Disable surface copies via buffer (Requires 2GB. Has only {}MB available)", availableSurfaceCopyBufferMem / 1024ull / 1024ull); } + + // start compilation threads + RendererShaderVk::Init(); } VulkanRenderer::~VulkanRenderer() @@ -591,6 +601,8 @@ VulkanRenderer::~VulkanRenderer() SubmitCommandBuffer(); WaitDeviceIdle(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); + // shut down compilation threads + RendererShaderVk::Shutdown(); // shut down pipeline save thread m_destructionRequested = true; m_pipeline_cache_semaphore.notify(); @@ -1078,17 +1090,6 @@ RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, u return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } -void VulkanRenderer::shader_bind(RendererShader* shader) -{ - // does nothing on Vulkan - // remove from main render backend and internalize into GL backend -} - -void VulkanRenderer::shader_unbind(RendererShader::ShaderType shaderType) -{ - // does nothing on Vulkan -} - bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info) { std::vector availableDeviceExtensions; @@ -1529,34 +1530,37 @@ void VulkanRenderer::DeleteNullObjects() void VulkanRenderer::ImguiInit() { - // TODO: renderpass swapchain format may change between srgb and rgb -> need reinit - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - 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; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + if (m_imguiRenderPass == VK_NULL_HANDLE) + { + // TODO: renderpass swapchain format may change between srgb and rgb -> need reinit + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + 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; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); - if (result != VK_SUCCESS) - throw VkException(result, "can't create imgui renderpass"); + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); + if (result != VK_SUCCESS) + throw VkException(result, "can't create imgui renderpass"); + } ImGui_ImplVulkan_InitInfo info{}; info.Instance = m_instance; @@ -1585,6 +1589,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; + } } void VulkanRenderer::UnrecoverableError(const char* errMsg) const @@ -2023,7 +2033,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor occlusionQuery_notifyBeginCommandBuffer(); m_recordedDrawcalls = 0; - m_submitThreshold = 500; // this used to be 750 before 1.25.5, but more frequent submission is actually better for latency + m_submitThreshold = 300; m_submitOnIdle = false; } @@ -2140,18 +2150,14 @@ void VulkanRenderer::CreatePipelineCache() if (fs::exists(dir)) { const auto filename = dir / fmt::format("{:016x}.bin", CafeSystem::GetForegroundTitleId()); - auto file = std::ifstream(filename, std::ios::in | std::ios::binary | std::ios::ate); if (file.is_open()) { const size_t fileSize = file.tellg(); file.seekg(0, std::ifstream::beg); - cacheData.resize(fileSize); file.read((char*)cacheData.data(), cacheData.size()); file.close(); - - cemuLog_logDebug(LogType::Force, "pipeline cache loaded"); } } @@ -2161,7 +2167,16 @@ void VulkanRenderer::CreatePipelineCache() createInfo.pInitialData = cacheData.data(); VkResult result = vkCreatePipelineCache(m_logicalDevice, &createInfo, nullptr, &m_pipeline_cache); if (result != VK_SUCCESS) - UnrecoverableError(fmt::format("Failed to create pipeline cache: {}", result).c_str()); + { + cemuLog_log(LogType::Force, "Failed to open Vulkan pipeline cache: {}", result); + // unable to load the existing cache, start with an empty cache instead + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + createInfo.initialDataSize = 0; + createInfo.pInitialData = nullptr; + result = vkCreatePipelineCache(m_logicalDevice, &createInfo, nullptr, &m_pipeline_cache); + if (result != VK_SUCCESS) + UnrecoverableError(fmt::format("Failed to create new Vulkan pipeline cache: {}", result).c_str()); + } size_t cache_size = 0; vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &cache_size, nullptr); @@ -2781,6 +2796,8 @@ void VulkanRenderer::NotifyLatteCommandProcessorIdle() SubmitCommandBuffer(); } +void VulkanBenchmarkPrintResults(); + void VulkanRenderer::SwapBuffers(bool swapTV, bool swapDRC) { SubmitCommandBuffer(); @@ -2790,6 +2807,9 @@ void VulkanRenderer::SwapBuffers(bool swapTV, bool swapDRC) if (swapDRC && IsSwapchainInfoValid(false)) SwapBuffer(false); + + if(swapTV) + VulkanBenchmarkPrintResults(); } void VulkanRenderer::ClearColorbuffer(bool padView) @@ -2809,47 +2829,18 @@ void VulkanRenderer::ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 { draw_endRenderPass(); - VkImageMemoryBarrier barrier = {}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = inputLayout; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = mipIndex; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = sliceIndex; - barrier.subresourceRange.layerCount = 1; + VkImageSubresourceRange subresourceRange{}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = mipIndex; + subresourceRange.levelCount = 1; + subresourceRange.baseArrayLayer = sliceIndex; + subresourceRange.layerCount = 1; - VkPipelineStageFlags srcStages = 0; - VkPipelineStageFlags dstStages = 0; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = 0; - barrier_calcStageAndMask(srcStages, barrier.srcAccessMask); - barrier_calcStageAndMask(dstStages, barrier.dstAccessMask); + barrier_image(image, subresourceRange, inputLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 1, &barrier); + vkCmdClearColorImage(m_state.currentCommandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &subresourceRange); - VkImageSubresourceRange imageRange{}; - imageRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - imageRange.baseArrayLayer = sliceIndex; - imageRange.layerCount = 1; - imageRange.baseMipLevel = mipIndex; - imageRange.levelCount = 1; - - vkCmdClearColorImage(m_state.currentCommandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &imageRange); - - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.newLayout = outputLayout; - - srcStages = 0; - dstStages = 0; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = 0; - barrier_calcStageAndMask(srcStages, barrier.srcAccessMask); - barrier_calcStageAndMask(dstStages, barrier.dstAccessMask); - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 1, &barrier); + barrier_image(image, subresourceRange, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, outputLayout); } void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout) @@ -3387,18 +3378,13 @@ void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, barrier_image(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL); } -LatteTexture* VulkanRenderer::texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, +LatteTexture* VulkanRenderer::texture_createTextureEx(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) { - return new LatteTextureVk(this, textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); + return new LatteTextureVk(this, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); } -void VulkanRenderer::texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) -{ - m_state.boundTexture[textureUnit] = static_cast(textureView); -} - -void VulkanRenderer::texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) +void VulkanRenderer::texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) { m_state.boundTexture[textureUnit] = static_cast(textureView); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index ad5cf15f..909ef24e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -308,15 +308,10 @@ public: void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; - LatteTexture* texture_createTextureEx(uint32 textureUnit, 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) override; + LatteTexture* texture_createTextureEx(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) override; - void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) override; - void texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) override; + void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) override; - void texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) override {}; - - void texture_rememberBoundTexture(uint32 textureUnit) override {}; - void texture_restoreBoundTexture(uint32 textureUnit) override {}; void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; @@ -358,8 +353,6 @@ public: void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; - void shader_bind(RendererShader* shader) override; - void shader_unbind(RendererShader::ShaderType shaderType) override; void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override; void indexData_uploadIndexMemory(uint32 offset, uint32 size) override; @@ -449,7 +442,7 @@ private: bool m_destroyPadSwapchainNextAcquire = false; bool IsSwapchainInfoValid(bool mainWindow) const; - VkRenderPass m_imguiRenderPass = nullptr; + VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE; VkDescriptorPool m_descriptorPool; @@ -919,10 +912,8 @@ private: } template - void barrier_image(LatteTextureVk* vkTexture, VkImageSubresourceLayers& subresourceLayers, VkImageLayout newLayout) + void barrier_image(VkImage imageVk, VkImageSubresourceRange& subresourceRange, VkImageLayout oldLayout, VkImageLayout newLayout) { - VkImage imageVk = vkTexture->GetImageObj()->m_image; - VkPipelineStageFlags srcStages = 0; VkPipelineStageFlags dstStages = 0; @@ -935,22 +926,33 @@ private: barrier_calcStageAndMask(srcStages, imageMemBarrier.srcAccessMask); barrier_calcStageAndMask(dstStages, imageMemBarrier.dstAccessMask); imageMemBarrier.image = imageVk; - imageMemBarrier.subresourceRange.aspectMask = subresourceLayers.aspectMask; - imageMemBarrier.subresourceRange.baseArrayLayer = subresourceLayers.baseArrayLayer; - imageMemBarrier.subresourceRange.layerCount = subresourceLayers.layerCount; - imageMemBarrier.subresourceRange.baseMipLevel = subresourceLayers.mipLevel; - imageMemBarrier.subresourceRange.levelCount = 1; - imageMemBarrier.oldLayout = vkTexture->GetImageLayout(imageMemBarrier.subresourceRange); + imageMemBarrier.subresourceRange = subresourceRange; + imageMemBarrier.oldLayout = oldLayout; imageMemBarrier.newLayout = newLayout; vkCmdPipelineBarrier(m_state.currentCommandBuffer, - srcStages, dstStages, - 0, - 0, NULL, - 0, NULL, - 1, &imageMemBarrier); + srcStages, dstStages, + 0, + 0, NULL, + 0, NULL, + 1, &imageMemBarrier); + } - vkTexture->SetImageLayout(imageMemBarrier.subresourceRange, newLayout); + template + void barrier_image(LatteTextureVk* vkTexture, VkImageSubresourceLayers& subresourceLayers, VkImageLayout newLayout) + { + VkImage imageVk = vkTexture->GetImageObj()->m_image; + + VkImageSubresourceRange subresourceRange; + subresourceRange.aspectMask = subresourceLayers.aspectMask; + subresourceRange.baseArrayLayer = subresourceLayers.baseArrayLayer; + subresourceRange.layerCount = subresourceLayers.layerCount; + subresourceRange.baseMipLevel = subresourceLayers.mipLevel; + subresourceRange.levelCount = 1; + + barrier_image(imageVk, subresourceRange, vkTexture->GetImageLayout(subresourceRange), newLayout); + + vkTexture->SetImageLayout(subresourceRange, newLayout); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index c68c664f..320357f1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -370,8 +370,12 @@ void VulkanRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size) // does nothing since the index buffer memory is coherent } +float s_vkUniformData[512 * 4]; + void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader) { + auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return s_vkUniformData + (index / 4); }; + sint32 shaderAluConst; sint32 shaderUniformRegisterOffset; @@ -390,27 +394,23 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; break; default: - cemu_assert_debug(false); + UNREACHABLE; } if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) { - float uniformData[512 * 4]; - if (shader->uniform.list_ufTexRescale.empty() == false) { for (auto& entry : shader->uniform.list_ufTexRescale) { float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit); - float* v = uniformData + (entry.uniformLocation / 4); memcpy(entry.currentValue, xyScale, sizeof(float) * 2); - memcpy(v, xyScale, sizeof(float) * 2); + memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2); } } if (shader->uniform.loc_alphaTestRef >= 0) { - float* v = uniformData + (shader->uniform.loc_alphaTestRef / 4); - v[0] = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); + *GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); } if (shader->uniform.loc_pointSize >= 0) { @@ -418,41 +418,38 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; if (pointWidth == 0.0f) pointWidth = 1.0f / 8.0f; // minimum size - float* v = uniformData + (shader->uniform.loc_pointSize / 4); - v[0] = pointWidth; + *GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth; } if (shader->uniform.loc_remapped >= 0) { - LatteBufferCache_LoadRemappedUniforms(shader, uniformData + (shader->uniform.loc_remapped / 4)); + LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); } if (shader->uniform.loc_uniformRegister >= 0) { uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst); - float* v = uniformData + (shader->uniform.loc_uniformRegister / 4); - memcpy(v, uniformRegData, shader->uniform.count_uniformRegister * 16); + memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16); } if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) { sint32 viewportWidth; sint32 viewportHeight; LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() - float* v = uniformData + (shader->uniform.loc_windowSpaceToClipSpaceTransform / 4); + float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform); v[0] = 2.0f / (float)viewportWidth; v[1] = 2.0f / (float)viewportHeight; } if (shader->uniform.loc_fragCoordScale >= 0) { - float* coordScale = uniformData + (shader->uniform.loc_fragCoordScale / 4); - LatteMRT::GetCurrentFragCoordScale(coordScale); + LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale)); } if (shader->uniform.loc_verticesPerInstance >= 0) { - *(int*)(uniformData + (shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance; + *(int*)(s_vkUniformData + ((size_t)shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance; for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) { if (shader->uniform.loc_streamoutBufferBase[b] >= 0) { - *(int*)(uniformData + (shader->uniform.loc_streamoutBufferBase[b] / 4)) = m_streamoutState.buffer[b].ringBufferOffset; + *(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_streamoutState.buffer[b].ringBufferOffset; } } } @@ -463,7 +460,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt } uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; const uint32 uniformOffset = m_uniformVarBufferWriteIndex; - memcpy(m_uniformVarBufferPtr + uniformOffset, uniformData, shader->uniform.uniformRangeSize); + memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize); m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize; m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1; // update dynamic offset @@ -516,15 +513,15 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader) switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: - hostTextureUnit += CEMU_VS_TEX_UNIT_BASE; + hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; break; case LatteConst::ShaderType::Pixel: - hostTextureUnit += CEMU_PS_TEX_UNIT_BASE; + hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; break; case LatteConst::ShaderType::Geometry: - hostTextureUnit += CEMU_GS_TEX_UNIT_BASE; + hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; break; default: @@ -634,15 +631,15 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: - hostTextureUnit += CEMU_VS_TEX_UNIT_BASE; + hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; break; case LatteConst::ShaderType::Pixel: - hostTextureUnit += CEMU_PS_TEX_UNIT_BASE; + hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; break; case LatteConst::ShaderType::Geometry: - hostTextureUnit += CEMU_GS_TEX_UNIT_BASE; + hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; break; default: @@ -1577,7 +1574,7 @@ void VulkanRenderer::draw_updateVertexBuffersDirectAccess() uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; - if (bufferAddress == MPTR_NULL) + if (bufferAddress == MPTR_NULL) [[unlikely]] { bufferAddress = 0x10000000; } @@ -1594,18 +1591,18 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader { if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { - // use full uniform buffers - for (sint32 t = 0; t < shader->uniformBufferListCount; t++) + for(const auto& buf : shader->list_quickBufferList) { - sint32 i = shader->uniformBufferList[t]; + sint32 i = buf.index; MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; - if (physicalAddr == MPTR_NULL) + if (physicalAddr == MPTR_NULL) [[unlikely]] { cemu_assert_unimplemented(); continue; } + uniformSize = std::min(uniformSize, buf.size); cemu_assert_debug(physicalAddr < 0x50000000); @@ -1624,7 +1621,7 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; default: - cemu_assert_debug(false); + UNREACHABLE; } } } diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.cpp b/src/Cafe/IOSU/PDM/iosu_pdm.cpp index 45b4a1d8..e54529a9 100644 --- a/src/Cafe/IOSU/PDM/iosu_pdm.cpp +++ b/src/Cafe/IOSU/PDM/iosu_pdm.cpp @@ -1,4 +1,5 @@ #include "iosu_pdm.h" +#include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "util/helpers/Semaphore.h" @@ -17,7 +18,8 @@ namespace iosu { namespace pdm { - std::mutex sDiaryLock; + std::recursive_mutex sPlaystatsLock; + std::recursive_mutex sDiaryLock; fs::path GetPDFile(const char* filename) { @@ -80,14 +82,16 @@ namespace iosu static_assert((NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry)) == 0x1400); } - void LoadPlaystats() + void OpenPlaystats() { + std::unique_lock _l(sPlaystatsLock); PlayStats.numEntries = 0; for (size_t i = 0; i < NUM_PLAY_STATS_ENTRIES; i++) { auto& e = PlayStats.entry[i]; memset(&e, 0, sizeof(PlayStatsEntry)); } + cemu_assert_debug(!PlayStats.fs); PlayStats.fs = FileStream::openFile2(GetPDFile("PlayStats.dat"), true); if (!PlayStats.fs) { @@ -98,18 +102,39 @@ namespace iosu { delete PlayStats.fs; PlayStats.fs = nullptr; - cemuLog_log(LogType::Force, "PlayStats.dat malformed"); + cemuLog_log(LogType::Force, "PlayStats.dat malformed. Time tracking wont be used"); // dont delete the existing file in case it could still be salvaged (todo) and instead just dont track play time return; } + PlayStats.numEntries = 0; PlayStats.fs->readData(&PlayStats.numEntries, sizeof(uint32be)); if (PlayStats.numEntries > NUM_PLAY_STATS_ENTRIES) PlayStats.numEntries = NUM_PLAY_STATS_ENTRIES; PlayStats.fs->readData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * 20); } + void ClosePlaystats() + { + std::unique_lock _l(sPlaystatsLock); + if (PlayStats.fs) + { + delete PlayStats.fs; + PlayStats.fs = nullptr; + } + } + + void UnloadPlaystats() + { + std::unique_lock _l(sPlaystatsLock); + cemu_assert_debug(!PlayStats.fs); // unloading expects that file is closed + PlayStats.numEntries = 0; + for(auto& it : PlayStats.entry) + it = PlayStatsEntry{}; + } + PlayStatsEntry* PlayStats_GetEntry(uint64 titleId) { + std::unique_lock _l(sPlaystatsLock); uint32be titleIdHigh = (uint32)(titleId>>32); uint32be titleIdLow = (uint32)(titleId & 0xFFFFFFFF); size_t numEntries = PlayStats.numEntries; @@ -121,7 +146,7 @@ namespace iosu return nullptr; } - void PlayStats_WriteEntry(PlayStatsEntry* entry, bool writeEntryCount = false) + void PlayStats_WriteEntryNoLock(PlayStatsEntry* entry, bool writeEntryCount = false) { if (!PlayStats.fs) return; @@ -141,8 +166,15 @@ namespace iosu } } + void PlayStats_WriteEntry(PlayStatsEntry* entry, bool writeEntryCount = false) + { + std::unique_lock _l(sPlaystatsLock); + PlayStats_WriteEntryNoLock(entry, writeEntryCount); + } + PlayStatsEntry* PlayStats_CreateEntry(uint64 titleId) { + std::unique_lock _l(sPlaystatsLock); bool entryCountChanged = false; PlayStatsEntry* newEntry; if(PlayStats.numEntries < NUM_PLAY_STATS_ENTRIES) @@ -168,7 +200,7 @@ namespace iosu newEntry->numTimesLaunched = 1; newEntry->totalMinutesPlayed = 0; newEntry->ukn12 = 0; - PlayStats_WriteEntry(newEntry, entryCountChanged); + PlayStats_WriteEntryNoLock(newEntry, entryCountChanged); return newEntry; } @@ -176,6 +208,7 @@ namespace iosu // if it does not exist it creates a new entry with first and last played set to today PlayStatsEntry* PlayStats_BeginNewTracking(uint64 titleId) { + std::unique_lock _l(sPlaystatsLock); PlayStatsEntry* entry = PlayStats_GetEntry(titleId); if (entry) { @@ -189,11 +222,12 @@ namespace iosu void PlayStats_CountAdditionalMinutes(PlayStatsEntry* entry, uint32 additionalMinutes) { + std::unique_lock _l(sPlaystatsLock); if (additionalMinutes == 0) return; entry->totalMinutesPlayed += additionalMinutes; entry->mostRecentDayIndex = GetTodaysDayIndex(); - PlayStats_WriteEntry(entry); + PlayStats_WriteEntryNoLock(entry); } struct PlayDiaryHeader @@ -218,6 +252,7 @@ namespace iosu void CreatePlayDiary() { MakeDirectory(); + cemu_assert_debug(!PlayDiaryData.fs); PlayDiaryData.fs = FileStream::createFile2(GetPDFile("PlayDiary.dat")); if (!PlayDiaryData.fs) { @@ -230,7 +265,7 @@ namespace iosu PlayDiaryData.fs->writeData(&PlayDiaryData.header, sizeof(PlayDiaryHeader)); } - void LoadPlayDiary() + void OpenPlayDiary() { std::unique_lock _lock(sDiaryLock); cemu_assert_debug(!PlayDiaryData.fs); @@ -268,6 +303,26 @@ namespace iosu } } + void ClosePlayDiary() + { + std::unique_lock _lock(sDiaryLock); + if (PlayDiaryData.fs) + { + delete PlayDiaryData.fs; + PlayDiaryData.fs = nullptr; + } + } + + void UnloadDiaryData() + { + std::unique_lock _lock(sDiaryLock); + cemu_assert_debug(!PlayDiaryData.fs); // unloading expects that file is closed + PlayDiaryData.header.readIndex = 0; + PlayDiaryData.header.writeIndex = 0; + for (auto& it : PlayDiaryData.entry) + it = PlayDiaryEntry{}; + } + uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries) { std::unique_lock _lock(sDiaryLock); @@ -352,25 +407,59 @@ namespace iosu } } - void Initialize() + class : public ::IOSUModule { - // todo - add support for per-account handling - LoadPlaystats(); - LoadPlayDiary(); - } - - void StartTrackingTime(uint64 titleId) - { - sPDMRequestExitThread = false; - sPDMTimeTrackingThread = std::thread(TimeTrackingThread, titleId); - } + void PDMLoadAll() + { + OpenPlaystats(); + OpenPlayDiary(); + } - void Stop() + void PDMUnloadAll() + { + UnloadPlaystats(); + UnloadDiaryData(); + } + + void PDMCloseAll() + { + ClosePlaystats(); + ClosePlayDiary(); + } + + void SystemLaunch() override + { + // todo - add support for per-account handling + PDMLoadAll(); + PDMCloseAll(); // close the files again, user may mess with MLC files or change MLC path while no game is running + } + void SystemExit() override + { + PDMCloseAll(); + PDMUnloadAll(); + } + void TitleStart() override + { + // reload data and keep files open + PDMUnloadAll(); + PDMLoadAll(); + auto titleId = CafeSystem::GetForegroundTitleId(); + sPDMRequestExitThread = false; + sPDMTimeTrackingThread = std::thread(TimeTrackingThread, titleId); + } + void TitleStop() override + { + sPDMRequestExitThread.store(true); + sPDMSem.increment(); + if(sPDMTimeTrackingThread.joinable()) + sPDMTimeTrackingThread.join(); + PDMCloseAll(); + } + }sIOSUModuleNNPDM; + + IOSUModule* GetModule() { - sPDMRequestExitThread.store(true); - sPDMSem.increment(); - if(sPDMTimeTrackingThread.joinable()) - sPDMTimeTrackingThread.join(); + return static_cast(&sIOSUModuleNNPDM); } }; diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.h b/src/Cafe/IOSU/PDM/iosu_pdm.h index fbafbc02..0dd8a39d 100644 --- a/src/Cafe/IOSU/PDM/iosu_pdm.h +++ b/src/Cafe/IOSU/PDM/iosu_pdm.h @@ -1,13 +1,10 @@ #pragma once +#include "Cafe/IOSU/iosu_types_common.h" namespace iosu { namespace pdm { - void Initialize(); - void StartTrackingTime(uint64 titleId); - void Stop(); - inline constexpr size_t NUM_PLAY_STATS_ENTRIES = 256; inline constexpr size_t NUM_PLAY_DIARY_ENTRIES_MAX = 18250; // 0x474A @@ -34,5 +31,7 @@ namespace iosu }; bool GetStatForGamelist(uint64 titleId, GameListStat& stat); + + IOSUModule* GetModule(); }; }; \ No newline at end of file diff --git a/src/Cafe/IOSU/iosu_types_common.h b/src/Cafe/IOSU/iosu_types_common.h index 94c0cd2c..1f7f78f8 100644 --- a/src/Cafe/IOSU/iosu_types_common.h +++ b/src/Cafe/IOSU/iosu_types_common.h @@ -1,6 +1,9 @@ #pragma once using IOSMsgQueueId = uint32; +using IOSTimerId = uint32; + +static constexpr IOSTimerId IOSInvalidTimerId = 0xFFFFFFFF; // returned for syscalls // maybe also shared with IPC? @@ -19,4 +22,13 @@ enum IOS_ERROR : sint32 inline bool IOS_ResultIsError(const IOS_ERROR err) { return (err & 0x80000000) != 0; -} \ No newline at end of file +} + +class IOSUModule +{ + public: + virtual void SystemLaunch() {}; // CafeSystem is initialized + virtual void SystemExit() {}; // CafeSystem is shutdown + virtual void TitleStart() {}; // foreground title is launched + virtual void TitleStop() {}; // foreground title is closed +}; diff --git a/src/Cafe/IOSU/kernel/iosu_kernel.cpp b/src/Cafe/IOSU/kernel/iosu_kernel.cpp index 680170bc..666e0373 100644 --- a/src/Cafe/IOSU/kernel/iosu_kernel.cpp +++ b/src/Cafe/IOSU/kernel/iosu_kernel.cpp @@ -1,6 +1,8 @@ #include "iosu_kernel.h" #include "util/helpers/fspinlock.h" +#include "util/helpers/helpers.h" #include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" namespace iosu { @@ -8,6 +10,9 @@ namespace iosu { std::mutex sInternalMutex; + void IOS_DestroyResourceManagerForQueueId(IOSMsgQueueId msgQueueId); + void _IPCDestroyAllHandlesForMsgQueue(IOSMsgQueueId msgQueueId); + static void _assume_lock() { #ifdef CEMU_DEBUG_ASSERT @@ -84,6 +89,19 @@ namespace iosu return queueHandle; } + IOS_ERROR IOS_DestroyMessageQueue(IOSMsgQueueId msgQueueId) + { + std::unique_lock _l(sInternalMutex); + IOSMessageQueue* msgQueue = nullptr; + IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); + if (r != IOS_ERROR_OK) + return r; + msgQueue->msgArraySize = 0; + msgQueue->queueHandle = 0; + IOS_DestroyResourceManagerForQueueId(msgQueueId); + return IOS_ERROR_OK; + } + IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags) { std::unique_lock _l(sInternalMutex); @@ -146,6 +164,133 @@ namespace iosu return IOS_ERROR_OK; } + /* timer */ + + std::mutex sTimerMutex; + std::condition_variable sTimerCV; + std::atomic_bool sTimerThreadStop; + + struct IOSTimer + { + IOSMsgQueueId queueId; + uint32 message; + HRTick nextFire; + HRTick repeat; + bool isValid; + }; + + std::vector sTimers; + std::vector sTimersFreeHandles; + + auto sTimerSortComparator = [](const IOSTimerId& idA, const IOSTimerId& idB) + { + // order by nextFire, then by timerId to avoid duplicate keys + IOSTimer& timerA = sTimers[idA]; + IOSTimer& timerB = sTimers[idB]; + if (timerA.nextFire != timerB.nextFire) + return timerA.nextFire < timerB.nextFire; + return idA < idB; + }; + std::set sTimerByFireTime; + + IOSTimer& IOS_GetFreeTimer() + { + cemu_assert_debug(!sTimerMutex.try_lock()); // lock must be held by current thread + if (sTimersFreeHandles.empty()) + return sTimers.emplace_back(); + IOSTimerId timerId = sTimersFreeHandles.back(); + sTimersFreeHandles.pop_back(); + return sTimers[timerId]; + } + + void IOS_TimerSetNextFireTime(IOSTimer& timer, HRTick nextFire) + { + cemu_assert_debug(!sTimerMutex.try_lock()); // lock must be held by current thread + IOSTimerId timerId = &timer - sTimers.data(); + auto it = sTimerByFireTime.find(timerId); + if(it != sTimerByFireTime.end()) + sTimerByFireTime.erase(it); + timer.nextFire = nextFire; + if(nextFire != 0) + sTimerByFireTime.insert(timerId); + } + + void IOS_StopTimerInternal(IOSTimerId timerId) + { + cemu_assert_debug(!sTimerMutex.try_lock()); + IOS_TimerSetNextFireTime(sTimers[timerId], 0); + } + + IOS_ERROR IOS_CreateTimer(uint32 startMicroseconds, uint32 repeatMicroseconds, uint32 queueId, uint32 message) + { + std::unique_lock _l(sTimerMutex); + IOSTimer& timer = IOS_GetFreeTimer(); + timer.queueId = queueId; + timer.message = message; + HRTick nextFire = HighResolutionTimer::now().getTick() + HighResolutionTimer::microsecondsToTicks(startMicroseconds); + timer.repeat = HighResolutionTimer::microsecondsToTicks(repeatMicroseconds); + IOS_TimerSetNextFireTime(timer, nextFire); + timer.isValid = true; + sTimerCV.notify_one(); + return (IOS_ERROR)(&timer - sTimers.data()); + } + + IOS_ERROR IOS_StopTimer(IOSTimerId timerId) + { + std::unique_lock _l(sTimerMutex); + if (timerId >= sTimers.size() || !sTimers[timerId].isValid) + return IOS_ERROR_INVALID; + IOS_StopTimerInternal(timerId); + return IOS_ERROR_OK; + } + + IOS_ERROR IOS_DestroyTimer(IOSTimerId timerId) + { + std::unique_lock _l(sTimerMutex); + if (timerId >= sTimers.size() || !sTimers[timerId].isValid) + return IOS_ERROR_INVALID; + IOS_StopTimerInternal(timerId); + sTimers[timerId].isValid = false; + sTimersFreeHandles.push_back(timerId); + return IOS_ERROR_OK; + } + + void IOSTimerThread() + { + SetThreadName("IOS-Timer"); + std::unique_lock _l(sTimerMutex); + while (!sTimerThreadStop) + { + if (sTimerByFireTime.empty()) + { + sTimerCV.wait_for(_l, std::chrono::milliseconds(10000)); + continue; + } + IOSTimerId timerId = *sTimerByFireTime.begin(); + IOSTimer& timer = sTimers[timerId]; + HRTick now = HighResolutionTimer::now().getTick(); + if (now >= timer.nextFire) + { + if(timer.repeat == 0) + IOS_TimerSetNextFireTime(timer, 0); + else + IOS_TimerSetNextFireTime(timer, timer.nextFire + timer.repeat); + IOSMsgQueueId queueId = timer.queueId; + uint32 message = timer.message; + // fire timer + _l.unlock(); + IOSMessage msg; + IOS_SendMessage(queueId, message, 1); + _l.lock(); + continue; + } + else + { + sTimerCV.wait_for(_l, std::chrono::microseconds(HighResolutionTimer::ticksToMicroseconds(timer.nextFire - now))); + } + } + } + /* devices and IPC */ struct IOSResourceManager @@ -209,6 +354,23 @@ namespace iosu return IOS_ERROR_OK; } + void IOS_DestroyResourceManagerForQueueId(IOSMsgQueueId msgQueueId) + { + _assume_lock(); + // destroy all IPC handles associated with this queue + _IPCDestroyAllHandlesForMsgQueue(msgQueueId); + // destroy device resource manager + for (auto& it : sDeviceResources) + { + if (it.isSet && it.msgQueueId == msgQueueId) + { + it.isSet = false; + it.path.clear(); + it.msgQueueId = 0; + } + } + } + IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id) { // not yet implemented @@ -344,6 +506,22 @@ namespace iosu return IOS_ERROR_OK; } + void _IPCDestroyAllHandlesForMsgQueue(IOSMsgQueueId msgQueueId) + { + _assume_lock(); + for (auto& it : sActiveDeviceHandles) + { + if (it.isSet && it.msgQueueId == msgQueueId) + { + it.isSet = false; + it.path.clear(); + it.handleCheckValue = 0; + it.hasDispatchTargetHandle = false; + it.msgQueueId = 0; + } + } + } + IOS_ERROR _IPCAssignDispatchTargetHandle(IOSDevHandle devHandle, IOSDevHandle internalHandle) { std::unique_lock _lock(sInternalMutex); @@ -400,8 +578,12 @@ namespace iosu return r; } + std::mutex sMtxReply[3]; + void _IPCReplyAndRelease(IOSDispatchableCommand* dispatchCmd, uint32 result) { + cemu_assert(dispatchCmd->ppcCoreIndex < 3); + std::unique_lock _l(sMtxReply[(uint32)dispatchCmd->ppcCoreIndex]); cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount()); dispatchCmd->originalBody->result = result; // submit to COS @@ -453,7 +635,6 @@ namespace iosu uint32 numIn = dispatchCmd->body.args[1]; uint32 numOut = dispatchCmd->body.args[2]; IPCIoctlVector* vec = MEMPTR(cmd.args[3]).GetPtr(); - // copy the vector array uint32 numVec = numIn + numOut; if (numVec <= 8) @@ -466,8 +647,23 @@ namespace iosu // reuse the original vector pointer cemuLog_log(LogType::Force, "Info: Ioctlv command with more than 8 vectors"); } - IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); - return r; + return _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); + } + + // normally COS kernel handles this, but currently we skip the IPC getting proxied through it + IOS_ERROR _IPCHandlerIn_TranslateVectorAddresses(IOSDispatchableCommand* dispatchCmd) + { + uint32 numIn = dispatchCmd->body.args[1]; + uint32 numOut = dispatchCmd->body.args[2]; + IPCIoctlVector* vec = MEMPTR(dispatchCmd->body.args[3]).GetPtr(); + for (uint32 i = 0; i < numIn + numOut; i++) + { + if (vec[i].baseVirt == nullptr && vec[i].size != 0) + return IOS_ERROR_INVALID; + // todo - check for valid pointer range + vec[i].basePhys = vec[i].baseVirt; + } + return IOS_ERROR_OK; } // called by COS directly @@ -494,7 +690,11 @@ namespace iosu r = _IPCHandlerIn_IOS_Ioctl(dispatchCmd); break; case IPCCommandId::IOS_IOCTLV: - r = _IPCHandlerIn_IOS_Ioctlv(dispatchCmd); + r = _IPCHandlerIn_TranslateVectorAddresses(dispatchCmd); + if(r < 0) + cemuLog_log(LogType::Force, "Ioctlv error"); + else + r = _IPCHandlerIn_IOS_Ioctlv(dispatchCmd); break; default: cemuLog_log(LogType::Force, "Invalid IPC command {}", (uint32)(IPCCommandId)cmd->cmdId); @@ -547,10 +747,34 @@ namespace iosu return IOS_ERROR_OK; } - void Initialize() + class : public ::IOSUModule { - _IPCInitDispatchablePool(); + void SystemLaunch() override + { + _IPCInitDispatchablePool(); + // start timer thread + sTimerThreadStop = false; + m_timerThread = std::thread(IOSTimerThread); + } + + void SystemExit() override + { + // stop timer thread + sTimerThreadStop = true; + sTimerCV.notify_one(); + m_timerThread.join(); + // reset resources + // todo + } + + std::thread m_timerThread; + }sIOSUModuleKernel; + + IOSUModule* GetModule() + { + return static_cast(&sIOSUModuleKernel); } + } } \ No newline at end of file diff --git a/src/Cafe/IOSU/kernel/iosu_kernel.h b/src/Cafe/IOSU/kernel/iosu_kernel.h index 2b82374e..0355c118 100644 --- a/src/Cafe/IOSU/kernel/iosu_kernel.h +++ b/src/Cafe/IOSU/kernel/iosu_kernel.h @@ -9,15 +9,20 @@ namespace iosu using IOSMessage = uint32; IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount); + IOS_ERROR IOS_DestroyMessageQueue(IOSMsgQueueId msgQueueId); IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags); IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags); + IOS_ERROR IOS_CreateTimer(uint32 startMicroseconds, uint32 repeatMicroseconds, uint32 queueId, uint32 message); + IOS_ERROR IOS_StopTimer(IOSTimerId timerId); + IOS_ERROR IOS_DestroyTimer(IOSTimerId timerId); + IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId); IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id); IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result); void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd); - void Initialize(); + IOSUModule* GetModule(); } } \ 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 9d24a095..dc0fd045 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -112,7 +112,7 @@ void iosuAct_loadAccounts() // } //} - cemuLog_log(LogType::Force, L"IOSU_ACT: using account {} in first slot", first_acc.GetMiiName()); + cemuLog_log(LogType::Force, "IOSU_ACT: using account {} in first slot", boost::nowide::narrow(first_acc.GetMiiName())); _actAccountDataInitialized = true; } @@ -191,6 +191,15 @@ namespace iosu 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); @@ -622,10 +631,19 @@ int iosuAct_thread() } else if (actCemuRequest->requestCode == IOSU_ARC_PERSISTENTID) { - accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); - _cancelIfAccountDoesNotExist(); - actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId; - actCemuRequest->setACTReturnCode(0); + if(actCemuRequest->accountSlot != 0) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId; + actCemuRequest->setACTReturnCode(0); + } + else + { + // F1 Race Stars calls IsSlotOccupied and indirectly GetPersistentId on slot 0 which is not valid + actCemuRequest->resultU32.u32 = 0; + actCemuRequest->setACTReturnCode(0); + } } else if (actCemuRequest->requestCode == IOSU_ARC_COUNTRY) { diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index 04dd579f..5336f519 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -3,6 +3,8 @@ void iosuAct_init_depr(); bool iosuAct_isInitialized(); +#define ACT_ACCOUNTID_LENGTH (17) // includes '\0' + // Mii #define MII_FFL_STORAGE_SIZE (96) @@ -48,6 +50,8 @@ namespace iosu bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getCountryIndex(uint8 slot, uint32* countryIndex); + std::string getAccountId2(uint8 slot); + const uint8 ACT_SLOT_CURRENT = 0xFE; void Initialize(); diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp index 7b7ce250..c2c1eb51 100644 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ b/src/Cafe/IOSU/legacy/iosu_boss.cpp @@ -119,7 +119,7 @@ namespace iosu uint32 turn_state = 0; uint32 wait_state = 0; - uint32 http_status_code = 0; + long http_status_code = 0; ContentType content_type = ContentType::kUnknownContent; std::vector result_buffer; @@ -592,6 +592,7 @@ namespace iosu int curl_result = curl_easy_perform(curl); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &it->http_status_code); + static_assert(sizeof(it->http_status_code) == sizeof(long)); //it->turn_state = kFinished; @@ -909,10 +910,10 @@ namespace iosu return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->processed_length) : std::make_pair(0u, (uint64)0); } - std::pair task_get_http_status_code(const char* taskId, uint32 accountId, uint64 titleId) + std::pair task_get_http_status_code(const char* taskId, uint32 accountId, uint64 titleId) { const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->http_status_code) : std::make_pair(0u, (uint32)0); + return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->http_status_code) : std::make_pair(0u, (long)0); } std::pair task_get_turn_state(const char* taskId, uint32 accountId, uint64 titleId) diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.cpp b/src/Cafe/IOSU/legacy/iosu_crypto.cpp index 9d7ab875..80eb2f01 100644 --- a/src/Cafe/IOSU/legacy/iosu_crypto.cpp +++ b/src/Cafe/IOSU/legacy/iosu_crypto.cpp @@ -615,10 +615,10 @@ void iosuCrypto_init() iosuCrypto_loadSSLCertificates(); } -bool iosuCrypto_checkRequirementMLCFile(std::string_view mlcSubpath, std::wstring& additionalErrorInfo_filePath) +bool iosuCrypto_checkRequirementMLCFile(std::string_view mlcSubpath, std::string& additionalErrorInfo_filePath) { const auto path = ActiveSettings::GetMlcPath(mlcSubpath); - additionalErrorInfo_filePath = path.generic_wstring(); + additionalErrorInfo_filePath = _pathToUtf8(path); sint32 fileDataSize = 0; auto fileData = FileStream::LoadIntoMemory(path); if (!fileData) @@ -626,7 +626,7 @@ bool iosuCrypto_checkRequirementMLCFile(std::string_view mlcSubpath, std::wstrin return true; } -sint32 iosuCrypt_checkRequirementsForOnlineMode(std::wstring& additionalErrorInfo) +sint32 iosuCrypt_checkRequirementsForOnlineMode(std::string& additionalErrorInfo) { std::error_code ec; // check if otp.bin is present diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.h b/src/Cafe/IOSU/legacy/iosu_crypto.h index bf3d7e2f..9f1429c7 100644 --- a/src/Cafe/IOSU/legacy/iosu_crypto.h +++ b/src/Cafe/IOSU/legacy/iosu_crypto.h @@ -74,7 +74,7 @@ enum IOS_CRYPTO_ONLINE_REQ_MISSING_FILE }; -sint32 iosuCrypt_checkRequirementsForOnlineMode(std::wstring& additionalErrorInfo); +sint32 iosuCrypt_checkRequirementsForOnlineMode(std::string& additionalErrorInfo); void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); std::vector iosuCrypt_getCertificateKeys(); diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp index 4457d602..9130b28d 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.cpp +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -1,4 +1,3 @@ -#include "iosu_ioctl.h" #include "iosu_act.h" #include "iosu_fpd.h" #include "Cemu/nex/nex.h" @@ -9,12 +8,10 @@ #include "config/ActiveSettings.h" #include "Cemu/napi/napi.h" #include "util/helpers/StringHelpers.h" -#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/IOSU/nn/iosu_nn_service.h" -uint32 memory_getVirtualOffsetFromPointer(void* ptr); // remove once we updated everything to MEMPTR - -SysAllocator _fpdAsyncLoginRetCode; -SysAllocator _fpdAsyncAddFriendRetCode; +#include "Common/CafeString.h" std::mutex g_friend_notification_mutex; std::vector< std::pair > g_friend_notifications; @@ -23,78 +20,114 @@ namespace iosu { namespace fpd { + using NotificationRunningId = uint64; + + struct NotificationEntry + { + NotificationEntry(uint64 index, NexFriends::NOTIFICATION_TYPE type, uint32 pid) : timestamp(std::chrono::steady_clock::now()), runningId(index), type(type), pid(pid) {} + std::chrono::steady_clock::time_point timestamp; + NotificationRunningId runningId; + NexFriends::NOTIFICATION_TYPE type; + uint32 pid; + }; + + class + { + public: + void TrackNotification(NexFriends::NOTIFICATION_TYPE type, uint32 pid) + { + std::unique_lock _l(m_mtxNotificationQueue); + m_notificationQueue.emplace_back(m_notificationQueueIndex++, type, pid); + } + + void RemoveExpired() + { + // remove entries older than 10 seconds + std::chrono::steady_clock::time_point expireTime = std::chrono::steady_clock::now() - std::chrono::seconds(10); + std::erase_if(m_notificationQueue, [expireTime](const auto& notification) { + return notification.timestamp < expireTime; + }); + } + + std::optional GetNextNotification(NotificationRunningId& previousRunningId) + { + std::unique_lock _l(m_mtxNotificationQueue); + auto it = std::lower_bound(m_notificationQueue.begin(), m_notificationQueue.end(), previousRunningId, [](const auto& notification, const auto& runningId) { + return notification.runningId <= runningId; + }); + size_t itIndex = it - m_notificationQueue.begin(); + if(it == m_notificationQueue.end()) + return std::nullopt; + previousRunningId = it->runningId; + return *it; + } + + private: + std::vector m_notificationQueue; + std::mutex m_mtxNotificationQueue; + std::atomic_uint64_t m_notificationQueueIndex{1}; + }g_NotificationQueue; struct { bool isThreadStarted; bool isInitialized2; NexFriends* nexFriendSession; - // notification handler - MPTR notificationFunc; - MPTR notificationCustomParam; - uint32 notificationMask; - // login callback - struct - { - MPTR func; - MPTR customParam; - }asyncLoginCallback; + std::mutex mtxFriendSession; + // session state + std::atomic_bool sessionStarted{false}; // current state nexPresenceV2 myPresence; }g_fpd = {}; - void notificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) + void OverlayNotificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) { cemuLog_logDebug(LogType::Force, "Friends::Notification {:02x} pid {:08x}", type, pid); - if(GetConfig().notification.friends) + if(!GetConfig().notification.friends) + return; + std::unique_lock lock(g_friend_notification_mutex); + std::string message; + if(type == NexFriends::NOTIFICATION_TYPE::NOTIFICATION_TYPE_ONLINE) { - std::unique_lock lock(g_friend_notification_mutex); - std::string message; - if(type == NexFriends::NOTIFICATION_TYPE::NOTIFICATION_TYPE_ONLINE) + g_friend_notifications.emplace_back("Connected to friend service", 5000); + if(g_fpd.nexFriendSession && g_fpd.nexFriendSession->getPendingFriendRequestCount() > 0) + g_friend_notifications.emplace_back(fmt::format("You have {} pending friend request(s)", g_fpd.nexFriendSession->getPendingFriendRequestCount()), 5000); + } + else + { + std::string msg_format; + switch(type) { - g_friend_notifications.emplace_back("Connected to friend service", 5000); - if(g_fpd.nexFriendSession && g_fpd.nexFriendSession->getPendingFriendRequestCount() > 0) - g_friend_notifications.emplace_back(fmt::format("You have {} pending friend request(s)", g_fpd.nexFriendSession->getPendingFriendRequestCount()), 5000); + case NexFriends::NOTIFICATION_TYPE_ONLINE: break; + case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGIN: msg_format = "{} is now online"; break; + case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGOFF: msg_format = "{} is now offline"; break; + case NexFriends::NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE: break; + case NexFriends::NOTIFICATION_TYPE_ADDED_FRIEND: msg_format = "{} has been added to your friend list"; break; + case NexFriends::NOTIFICATION_TYPE_REMOVED_FRIEND: msg_format = "{} has been removed from your friend list"; break; + case NexFriends::NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST: break; + case NexFriends::NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST: break; + case NexFriends::NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST: msg_format = "{} wants to add you to his friend list"; break; + case NexFriends::NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST: break; + default: ; } - else + if (!msg_format.empty()) { - std::string msg_format; - switch(type) + std::string name = fmt::format("{:#x}", pid); + if (g_fpd.nexFriendSession) { - case NexFriends::NOTIFICATION_TYPE_ONLINE: break; - case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGIN: msg_format = "{} is now online"; break; - case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGOFF: msg_format = "{} is now offline"; break; - case NexFriends::NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE: break; - case NexFriends::NOTIFICATION_TYPE_ADDED_FRIEND: msg_format = "{} has been added to your friend list"; break; - case NexFriends::NOTIFICATION_TYPE_REMOVED_FRIEND: msg_format = "{} has been removed from your friend list"; break; - case NexFriends::NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST: break; - case NexFriends::NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST: break; - case NexFriends::NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST: msg_format = "{} wants to add you to his friend list"; break; - case NexFriends::NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST: break; - default: ; - } - - if (!msg_format.empty()) - { - std::string name = fmt::format("{:#x}", pid); - if (g_fpd.nexFriendSession) - { - const std::string tmp = g_fpd.nexFriendSession->getAccountNameByPid(pid); - if (!tmp.empty()) - name = tmp; - } - - g_friend_notifications.emplace_back(fmt::format(fmt::runtime(msg_format), name), 5000); + const std::string tmp = g_fpd.nexFriendSession->getAccountNameByPid(pid); + if (!tmp.empty()) + name = tmp; } + g_friend_notifications.emplace_back(fmt::format(fmt::runtime(msg_format), name), 5000); } } + } - if (g_fpd.notificationFunc == MPTR_NULL) - return; - uint32 notificationFlag = 1 << (type - 1); - if ( (notificationFlag&g_fpd.notificationMask) == 0 ) - return; - coreinitAsyncCallback_add(g_fpd.notificationFunc, 3, type, pid, g_fpd.notificationCustomParam); + void NotificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) + { + OverlayNotificationHandler(type, pid); + g_NotificationQueue.TrackNotification(type, pid); } void convertMultiByteStringToBigEndianWidechar(const char* input, uint16be* output, sint32 maxOutputLength) @@ -107,7 +140,7 @@ namespace iosu output[beStr.size()] = '\0'; } - void convertFPDTimestampToDate(uint64 timestamp, fpdDate_t* fpdDate) + void convertFPDTimestampToDate(uint64 timestamp, FPDDate* fpdDate) { // if the timestamp is zero then still return a valid date if (timestamp == 0) @@ -128,7 +161,7 @@ namespace iosu fpdDate->year = (uint16)((timestamp >> 26)); } - uint64 convertDateToFPDTimestamp(fpdDate_t* fpdDate) + uint64 convertDateToFPDTimestamp(FPDDate* fpdDate) { uint64 t = 0; t |= (uint64)fpdDate->second; @@ -140,111 +173,33 @@ namespace iosu return t; } - void startFriendSession() + void NexPresenceToGameMode(const nexPresenceV2* presence, GameMode* gameMode) { - cemu_assert(!g_fpd.nexFriendSession); - - NAPI::AuthInfo authInfo; - NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); - NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, 0x0005001010001C00, 0x0000, 0x00003200); - if (nexTokenResult.isValid()) - { - // get values needed for friend session - uint32 myPid; - uint8 currentSlot = iosu::act::getCurrentAccountSlot(); - iosu::act::getPrincipalId(currentSlot, &myPid); - char accountId[256] = { 0 }; - iosu::act::getAccountId(currentSlot, accountId); - FFLData_t miiData; - act::getMii(currentSlot, &miiData); - uint16 screenName[ACT_NICKNAME_LENGTH + 1] = { 0 }; - act::getScreenname(currentSlot, screenName); - uint32 countryCode = 0; - 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(); - - // Resolve potential domain to IP address - struct addrinfo hints = {0}, *addrs; - hints.ai_family = AF_INET; - - const int status = getaddrinfo(nexTokenResult.nexToken.host, NULL, &hints, &addrs); - if (status != 0) { -#if BOOST_OS_WINDOWS - cemuLog_log(LogType::Force, "IOSU_FPD: Failed to resolve hostname {}, {}", nexTokenResult.nexToken.host, gai_strerrorA(status)); -#else - cemuLog_log(LogType::Force, "IOSU_FPD: Failed to resolve hostname {}, {}", nexTokenResult.nexToken.host, gai_strerror(status)); -#endif - return; - } - - char addrstr[NI_MAXHOST]; - getnameinfo(addrs->ai_addr, addrs->ai_addrlen, addrstr, sizeof addrstr, NULL, 0, NI_NUMERICHOST); - cemuLog_log(LogType::Force, "IOSU_FPD: Resolved IP for hostname {}, {}", nexTokenResult.nexToken.host, addrstr); - - // start session - const uint32_t hostIp = ((struct sockaddr_in*)addrs->ai_addr)->sin_addr.s_addr; - freeaddrinfo(addrs); - g_fpd.nexFriendSession = new NexFriends(hostIp, nexTokenResult.nexToken.port, "ridfebb9", myPid, nexTokenResult.nexToken.nexPassword, nexTokenResult.nexToken.token, accountId, (uint8*)&miiData, (wchar_t*)screenName, (uint8)countryCode, g_fpd.myPresence); - g_fpd.nexFriendSession->setNotificationHandler(notificationHandler); - cemuLog_log(LogType::Force, "IOSU_FPD: Created friend server session"); - } - else - { - cemuLog_logDebug(LogType::Force, "IOSU_FPD: Failed to acquire nex token for friend server"); - } + memset(gameMode, 0, sizeof(GameMode)); + gameMode->joinFlagMask = presence->joinFlagMask; + gameMode->matchmakeType = presence->joinAvailability; + gameMode->joinGameId = presence->gameId; + gameMode->joinGameMode = presence->gameMode; + gameMode->hostPid = presence->hostPid; + gameMode->groupId = presence->groupId; + memcpy(gameMode->appSpecificData, presence->appSpecificData, 0x14); } - void handleRequest_GetFriendList(iosuFpdCemuRequest_t* fpdCemuRequest, bool getAll) + void GameModeToNexPresence(const GameMode* gameMode, nexPresenceV2* presence) { - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0; - fpdCemuRequest->resultU32.u32 = 0; // zero entries returned - return; - } - - uint32 temporaryPidList[800]; - uint32 pidCount = 0; - g_fpd.nexFriendSession->getFriendPIDs(temporaryPidList, &pidCount, fpdCemuRequest->getFriendList.startIndex, std::min(sizeof(temporaryPidList) / sizeof(temporaryPidList[0]), fpdCemuRequest->getFriendList.maxCount), getAll); - uint32be* pidListOutput = fpdCemuRequest->getFriendList.pidList.GetPtr(); - if (pidListOutput) - { - for (uint32 i = 0; i < pidCount; i++) - pidListOutput[i] = temporaryPidList[i]; - } - fpdCemuRequest->returnCode = 0; - fpdCemuRequest->resultU32.u32 = pidCount; + *presence = {}; + presence->joinFlagMask = gameMode->joinFlagMask; + presence->joinAvailability = (uint8)(uint32)gameMode->matchmakeType; + presence->gameId = gameMode->joinGameId; + presence->gameMode = gameMode->joinGameMode; + presence->hostPid = gameMode->hostPid; + presence->groupId = gameMode->groupId; + memcpy(presence->appSpecificData, gameMode->appSpecificData, 0x14); } - void handleRequest_GetFriendRequestList(iosuFpdCemuRequest_t* fpdCemuRequest) + void NexFriendToFPDFriendData(const nexFriend* frd, FriendData* friendData) { - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0; - fpdCemuRequest->resultU32.u32 = 0; // zero entries returned - return; - } - - uint32 temporaryPidList[800]; - uint32 pidCount = 0; - // get only incoming friend requests - g_fpd.nexFriendSession->getFriendRequestPIDs(temporaryPidList, &pidCount, fpdCemuRequest->getFriendList.startIndex, std::min(sizeof(temporaryPidList) / sizeof(temporaryPidList[0]), fpdCemuRequest->getFriendList.maxCount), true, false); - uint32be* pidListOutput = fpdCemuRequest->getFriendList.pidList.GetPtr(); - if (pidListOutput) - { - for (uint32 i = 0; i < pidCount; i++) - pidListOutput[i] = temporaryPidList[i]; - } - fpdCemuRequest->returnCode = 0; - fpdCemuRequest->resultU32.u32 = pidCount; - } - - void setFriendDataFromNexFriend(friendData_t* friendData, nexFriend* frd) - { - memset(friendData, 0, sizeof(friendData_t)); + memset(friendData, 0, sizeof(FriendData)); // setup friend data friendData->type = 1; // friend friendData->pid = frd->nnaInfo.principalInfo.principalId; @@ -253,13 +208,11 @@ namespace iosu // screenname convertMultiByteStringToBigEndianWidechar(frd->nnaInfo.principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be)); - //friendData->friendExtraData.ukn0E4 = 0; friendData->friendExtraData.isOnline = frd->presence.isOnline != 0 ? 1 : 0; - friendData->friendExtraData.gameKeyTitleId = _swapEndianU64(frd->presence.gameKey.titleId); - friendData->friendExtraData.gameKeyUkn = frd->presence.gameKey.ukn; - - friendData->friendExtraData.statusMessage[0] = '\0'; + friendData->friendExtraData.gameKey.titleId = frd->presence.gameKey.titleId; + friendData->friendExtraData.gameKey.ukn08 = frd->presence.gameKey.ukn; + NexPresenceToGameMode(&frd->presence, &friendData->friendExtraData.gameMode); // set valid dates friendData->uknDate.year = 2018; @@ -269,19 +222,19 @@ namespace iosu friendData->uknDate.minute = 1; friendData->uknDate.second = 1; - friendData->friendExtraData.uknDate218.year = 2018; - friendData->friendExtraData.uknDate218.day = 1; - friendData->friendExtraData.uknDate218.month = 1; - friendData->friendExtraData.uknDate218.hour = 1; - friendData->friendExtraData.uknDate218.minute = 1; - friendData->friendExtraData.uknDate218.second = 1; + friendData->friendExtraData.approvalTime.year = 2018; + friendData->friendExtraData.approvalTime.day = 1; + friendData->friendExtraData.approvalTime.month = 1; + friendData->friendExtraData.approvalTime.hour = 1; + friendData->friendExtraData.approvalTime.minute = 1; + friendData->friendExtraData.approvalTime.second = 1; convertFPDTimestampToDate(frd->lastOnlineTimestamp, &friendData->friendExtraData.lastOnline); } - void setFriendDataFromNexFriendRequest(friendData_t* friendData, nexFriendRequest* frdReq, bool isIncoming) + void NexFriendRequestToFPDFriendData(const nexFriendRequest* frdReq, bool isIncoming, FriendData* friendData) { - memset(friendData, 0, sizeof(friendData_t)); + memset(friendData, 0, sizeof(FriendData)); // setup friend data friendData->type = 0; // friend request friendData->pid = frdReq->principalInfo.principalId; @@ -292,7 +245,7 @@ namespace iosu convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendData->requestExtraData.comment, sizeof(friendData->requestExtraData.comment) / sizeof(uint16be)); - fpdDate_t expireDate; + FPDDate expireDate; convertFPDTimestampToDate(frdReq->message.expireTimestamp, &expireDate); bool isProvisional = frdReq->message.expireTimestamp == 0; @@ -301,10 +254,7 @@ namespace iosu //friendData->requestExtraData.ukn0A0 = 0; // if not set -> provisional friend request //friendData->requestExtraData.ukn0A4 = isProvisional ? 0 : 123; // no change? - friendData->requestExtraData.messageId = _swapEndianU64(frdReq->message.messageId); - - - //find the value for 'markedAsReceived' + friendData->requestExtraData.messageId = frdReq->message.messageId; ///* +0x0A8 */ uint8 ukn0A8; ///* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed) @@ -332,9 +282,9 @@ namespace iosu convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendData->requestExtraData.uknData1); } - void setFriendRequestFromNexFriendRequest(friendRequest_t* friendRequest, nexFriendRequest* frdReq, bool isIncoming) + void NexFriendRequestToFPDFriendRequest(const nexFriendRequest* frdReq, bool isIncoming, FriendRequest* friendRequest) { - memset(friendRequest, 0, sizeof(friendRequest_t)); + memset(friendRequest, 0, sizeof(FriendRequest)); friendRequest->pid = frdReq->principalInfo.principalId; @@ -355,715 +305,1171 @@ namespace iosu convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendRequest->expireDate); } - void handleRequest_GetFriendListEx(iosuFpdCemuRequest_t* fpdCemuRequest) + struct FPProfile { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - for (uint32 i = 0; i < fpdCemuRequest->getFriendListEx.count; i++) - { - uint32 pid = fpdCemuRequest->getFriendListEx.pidList[i]; - friendData_t* friendData = fpdCemuRequest->getFriendListEx.friendData.GetPtr() + i; - nexFriend frd; - nexFriendRequest frdReq; - if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) - { - setFriendDataFromNexFriend(friendData, &frd); - continue; - } - bool incoming = false; - if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) - { - setFriendDataFromNexFriendRequest(friendData, &frdReq, incoming); - continue; - } - fpdCemuRequest->returnCode = 0x80000000; - return; - } - } + uint8be country; + uint8be area; + uint16be unused; + }; + static_assert(sizeof(FPProfile) == 4); - void handleRequest_GetFriendRequestListEx(iosuFpdCemuRequest_t* fpdCemuRequest) + struct SelfPresence { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + uint8be ukn[0x130]; // todo + }; + static_assert(sizeof(SelfPresence) == 0x130); + + struct SelfPlayingGame + { + uint8be ukn0[0x10]; + }; + static_assert(sizeof(SelfPlayingGame) == 0x10); + + static const auto FPResult_Ok = 0; + static const auto FPResult_InvalidIPCParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_FP, 0x680); + static const auto FPResult_RequestFailed = BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); // figure out proper error code + static const auto FPResult_Aborted = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_FP, 0x3480); + + class FPDService : public iosu::nn::IPCSimpleService + { + + struct NotificationAsyncRequest { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - for (uint32 i = 0; i < fpdCemuRequest->getFriendRequestListEx.count; i++) - { - uint32 pid = fpdCemuRequest->getFriendListEx.pidList[i]; - friendRequest_t* friendRequest = fpdCemuRequest->getFriendRequestListEx.friendRequest.GetPtr() + i; - nexFriendRequest frdReq; - bool incoming = false; - if (!g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + NotificationAsyncRequest(IPCCommandBody* cmd, uint32 maxNumEntries, FPDNotification* notificationsOut, uint32be* countOut) + : cmd(cmd), maxNumEntries(maxNumEntries), notificationsOut(notificationsOut), countOut(countOut) { - cemuLog_log(LogType::Force, "Failed to get friend request"); - fpdCemuRequest->returnCode = 0x80000000; + } + + IPCCommandBody* cmd; + uint32 maxNumEntries; + FPDNotification* notificationsOut; + uint32be* countOut; + }; + + struct FPDClient + { + bool hasLoggedIn{false}; + uint32 notificationMask{0}; + NotificationRunningId prevRunningId{0}; + std::vector notificationRequests; + }; + + // storage for async IPC requests + std::vector m_asyncLoginRequests; + std::vector m_clients; + + public: + FPDService() : iosu::nn::IPCSimpleService("/dev/fpd") {} + + std::string GetThreadName() override + { + return "IOSUModule::FPD"; + } + + void StartService() override + { + cemu_assert_debug(m_asyncLoginRequests.empty()); + } + + void StopService() override + { + m_asyncLoginRequests.clear(); + for(auto& it : m_clients) + delete it; + m_clients.clear(); + } + + void* CreateClientObject() override + { + FPDClient* client = new FPDClient(); + m_clients.push_back(client); + return client; + } + + void DestroyClientObject(void* clientObject) override + { + FPDClient* client = (FPDClient*)clientObject; + std::erase(m_clients, client); + delete client; + } + + void SendQueuedNotifications(FPDClient* client) + { + if (client->notificationRequests.empty()) return; + if (client->notificationRequests.size() > 1) + cemuLog_log(LogType::Force, "FPD: More than one simultanous notification query not supported"); + NotificationAsyncRequest& request = client->notificationRequests[0]; + uint32 numNotifications = 0; + while(numNotifications < request.maxNumEntries) + { + auto notification = g_NotificationQueue.GetNextNotification(client->prevRunningId); + if (!notification) + break; + uint32 flag = 1 << static_cast(notification->type); + if((client->notificationMask & flag) == 0) + continue; + request.notificationsOut[numNotifications].type = static_cast(notification->type); + request.notificationsOut[numNotifications].pid = notification->pid; + numNotifications++; } - setFriendRequestFromNexFriendRequest(friendRequest, &frdReq, incoming); - } - } - - typedef struct - { - MPTR funcMPTR; - MPTR customParam; - }fpAsyncCallback_t; - - typedef struct - { - nexPrincipalBasicInfo* principalBasicInfo; - uint32* pidList; - sint32 count; - friendBasicInfo_t* friendBasicInfo; - fpAsyncCallback_t fpCallback; - }getBasicInfoAsyncParams_t; - - SysAllocator _fpCallbackResultArray; // use a ring buffer of results to avoid overwriting the result when multiple callbacks are queued at the same time - sint32 fpCallbackResultIndex = 0; - - void handleFPCallback(fpAsyncCallback_t* fpCallback, uint32 resultCode) - { - fpCallbackResultIndex = (fpCallbackResultIndex + 1) % 32; - uint32* resultPtr = _fpCallbackResultArray.GetPtr() + fpCallbackResultIndex; - - *resultPtr = resultCode; - - coreinitAsyncCallback_add(fpCallback->funcMPTR, 2, memory_getVirtualOffsetFromPointer(resultPtr), fpCallback->customParam); - } - - void handleFPCallback2(MPTR funcMPTR, MPTR custom, uint32 resultCode) - { - fpCallbackResultIndex = (fpCallbackResultIndex + 1) % 32; - uint32* resultPtr = _fpCallbackResultArray.GetPtr() + fpCallbackResultIndex; - - *resultPtr = resultCode; - - coreinitAsyncCallback_add(funcMPTR, 2, memory_getVirtualOffsetFromPointer(resultPtr), custom); - } - - void handleResultCB_GetBasicInfoAsync(NexFriends* nexFriends, uint32 result, void* custom) - { - getBasicInfoAsyncParams_t* cbInfo = (getBasicInfoAsyncParams_t*)custom; - if (result != 0) - { - handleFPCallback(&cbInfo->fpCallback, 0x80000000); // todo - properly translate internal error to nn::fp error code - free(cbInfo->principalBasicInfo); - free(cbInfo->pidList); - free(cbInfo); - return; + if (numNotifications == 0) + return; + *request.countOut = numNotifications; + ServiceCallAsyncRespond(request.cmd, FPResult_Ok); + client->notificationRequests.erase(client->notificationRequests.begin()); } - // convert PrincipalBasicInfo into friendBasicInfo - for (sint32 i = 0; i < cbInfo->count; i++) + void TimerUpdate() override { - friendBasicInfo_t* basicInfo = cbInfo->friendBasicInfo + i; - nexPrincipalBasicInfo* principalBasicInfo = cbInfo->principalBasicInfo + i; + // called once a second while service is running + std::unique_lock _l(g_fpd.mtxFriendSession); + if (!g_fpd.nexFriendSession) + return; + g_fpd.nexFriendSession->update(); + while(!m_asyncLoginRequests.empty()) + { + if(g_fpd.nexFriendSession->isOnline()) + { + ServiceCallAsyncRespond(m_asyncLoginRequests.front(), FPResult_Ok); + m_asyncLoginRequests.erase(m_asyncLoginRequests.begin()); + } + else + break; + } + // handle notification responses + g_NotificationQueue.RemoveExpired(); + for(auto& client : m_clients) + SendQueuedNotifications(client); + } - memset(basicInfo, 0, sizeof(friendBasicInfo_t)); - basicInfo->pid = principalBasicInfo->principalId; - strcpy(basicInfo->nnid, principalBasicInfo->nnid); + uint32 ServiceCall(void* clientObject, uint32 requestId, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) override + { + // for /dev/fpd input and output vectors are swapped + std::swap(vecIn, vecOut); + std::swap(numVecIn, numVecOut); - convertMultiByteStringToBigEndianWidechar(principalBasicInfo->mii.miiNickname, basicInfo->screenname, sizeof(basicInfo->screenname) / sizeof(uint16be)); - memcpy(basicInfo->miiData, principalBasicInfo->mii.miiData, FFL_SIZE); + FPDClient* fpdClient = (FPDClient*)clientObject; + switch(static_cast(requestId)) + { + case FPD_REQUEST_ID::SetNotificationMask: + return CallHandler_SetNotificationMask(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetNotificationAsync: + return CallHandler_GetNotificationAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::SetLedEventMask: + cemuLog_logDebug(LogType::Force, "[/dev/fpd] SetLedEventMask is todo"); + return FPResult_Ok; + case FPD_REQUEST_ID::LoginAsync: + return CallHandler_LoginAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::HasLoggedIn: + return CallHandler_HasLoggedIn(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::IsOnline: + return CallHandler_IsOnline(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyPrincipalId: + return CallHandler_GetMyPrincipalId(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyAccountId: + return CallHandler_GetMyAccountId(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyScreenName: + return CallHandler_GetMyScreenName(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyMii: + return CallHandler_GetMyMii(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyProfile: + return CallHandler_GetMyProfile(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyPresence: + return CallHandler_GetMyPresence(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyComment: + return CallHandler_GetMyComment(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyPreference: + return CallHandler_GetMyPreference(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetMyPlayingGame: + return CallHandler_GetMyPlayingGame(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendAccountId: + return CallHandler_GetFriendAccountId(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendScreenName: + return CallHandler_GetFriendScreenName(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendMii: + return CallHandler_GetFriendMii(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendPresence: + return CallHandler_GetFriendPresence(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendRelationship: + return CallHandler_GetFriendRelationship(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendList: + return CallHandler_GetFriendList_GetFriendListAll(fpdClient, vecIn, numVecIn, vecOut, numVecOut, false); + case FPD_REQUEST_ID::GetFriendListAll: + return CallHandler_GetFriendList_GetFriendListAll(fpdClient, vecIn, numVecIn, vecOut, numVecOut, true); + case FPD_REQUEST_ID::GetFriendRequestList: + return CallHandler_GetFriendRequestList(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendRequestListEx: + return CallHandler_GetFriendRequestListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetBlackList: + return CallHandler_GetBlackList(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetFriendListEx: + return CallHandler_GetFriendListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::UpdatePreferenceAsync: + return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync: + return CallHandler_AddFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::AcceptFriendRequestAsync: + return CallHandler_AcceptFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::DeleteFriendRequestAsync: + return CallHandler_DeleteFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::CancelFriendRequestAsync: + return CallHandler_CancelFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::MarkFriendRequestsAsReceivedAsync: + return CallHandler_MarkFriendRequestsAsReceivedAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::RemoveFriendAsync: + return CallHandler_RemoveFriendAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::DeleteFriendFlagsAsync: + return CallHandler_DeleteFriendFlagsAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetBasicInfoAsync: + return CallHandler_GetBasicInfoAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::CheckSettingStatusAsync: + return CallHandler_CheckSettingStatusAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::IsPreferenceValid: + return CallHandler_IsPreferenceValid(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::GetRequestBlockSettingAsync: + return CallHandler_GetRequestBlockSettingAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::AddFriendAsyncByPid: + return CallHandler_AddFriendAsyncByPid(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::UpdateGameModeVariation1: + case FPD_REQUEST_ID::UpdateGameModeVariation2: + return CallHandler_UpdateGameMode(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + default: + cemuLog_log(LogType::Force, "Unsupported service call {} to /dev/fpd", requestId); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); + } + } - basicInfo->uknDate90.day = 1; - basicInfo->uknDate90.month = 1; - basicInfo->uknDate90.hour = 1; - basicInfo->uknDate90.minute = 1; - basicInfo->uknDate90.second = 1; + #define DeclareInputPtr(__Name, __T, __count, __vecIndex) if(sizeof(__T)*(__count) != vecIn[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T* __Name = ((__T*)vecIn[__vecIndex].basePhys.GetPtr()) + #define DeclareInput(__Name, __T, __vecIndex) if(sizeof(__T) != vecIn[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T __Name = *((__T*)vecIn[__vecIndex].basePhys.GetPtr()) + #define DeclareOutputPtr(__Name, __T, __count, __vecIndex) if(sizeof(__T)*(__count) != vecOut[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T* __Name = ((__T*)vecOut[__vecIndex].basePhys.GetPtr()) + + template + static nnResult WriteValueOutput(IPCIoctlVector* vec, const T& value) + { + if(vec->size != sizeof(T)) + return FPResult_InvalidIPCParam; + *(T*)vec->basePhys.GetPtr() = value; + return FPResult_Ok; + } + + nnResult CallHandler_SetNotificationMask(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + DeclareInput(notificationMask, uint32be, 0); + fpdClient->notificationMask = notificationMask; + return FPResult_Ok; + } + + nnResult CallHandler_GetNotificationAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 2) + return FPResult_InvalidIPCParam; + if((vecOut[0].size % sizeof(FPDNotification)) != 0 || vecOut[0].size < sizeof(FPDNotification)) + { + cemuLog_log(LogType::Force, "FPD GetNotificationAsync: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + DeclareOutputPtr(countOut, uint32be, 1, 1); + uint32 maxCount = vecOut[0].size / sizeof(FPDNotification); + DeclareOutputPtr(notificationList, FPDNotification, maxCount, 0); + fpdClient->notificationRequests.emplace_back(cmd, maxCount, notificationList, countOut); + SendQueuedNotifications(fpdClient); // if any notifications are queued, send them immediately + return FPResult_Ok; + } + + nnResult CallHandler_LoginAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!ActiveSettings::IsOnlineEnabled()) + { + // not online, fail immediately + return FPResult_Ok; // Splatoon expects this to always return success otherwise it will softlock. This should be FPResult_Aborted? + } + StartFriendSession(); + fpdClient->hasLoggedIn = true; + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + m_asyncLoginRequests.emplace_back(cmd); + return FPResult_Ok; + } + + nnResult CallHandler_HasLoggedIn(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + return WriteValueOutput(vecOut, fpdClient->hasLoggedIn ? 1 : 0); + } + + nnResult CallHandler_IsOnline(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + bool isOnline = g_fpd.nexFriendSession ? g_fpd.nexFriendSession->isOnline() : false; + return WriteValueOutput(vecOut, isOnline?1:0); + } + + nnResult CallHandler_GetMyPrincipalId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + uint8 slot = iosu::act::getCurrentAccountSlot(); + uint32 pid = 0; + iosu::act::getPrincipalId(slot, &pid); + return WriteValueOutput(vecOut, pid); + } + + nnResult CallHandler_GetMyAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + uint8 slot = iosu::act::getCurrentAccountSlot(); + std::string accountId = iosu::act::getAccountId2(slot); + if(vecOut->size != ACT_ACCOUNTID_LENGTH) + { + cemuLog_log(LogType::Force, "GetMyAccountId: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + if(accountId.length() > ACT_ACCOUNTID_LENGTH-1) + { + cemuLog_log(LogType::Force, "GetMyAccountId: AccountID is too long"); + return FPResult_InvalidIPCParam; + } + if(accountId.empty()) + { + cemuLog_log(LogType::Force, "GetMyAccountId: AccountID is empty"); + return FPResult_InvalidIPCParam; // should return 0xC0C00800 ? + } + char* outputStr = (char*)vecOut->basePhys.GetPtr(); + memset(outputStr, 0, ACT_ACCOUNTID_LENGTH); + memcpy(outputStr, accountId.data(), accountId.length()); + return FPResult_Ok; + } + + nnResult CallHandler_GetMyScreenName(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + uint8 slot = iosu::act::getCurrentAccountSlot(); + if(vecOut->size != ACT_NICKNAME_SIZE*sizeof(uint16be)) + { + cemuLog_log(LogType::Force, "GetMyScreenName: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + uint16 screenname[ACT_NICKNAME_SIZE]{0}; + bool r = iosu::act::getScreenname(slot, screenname); + if (!r) + { + cemuLog_log(LogType::Force, "GetMyScreenName: Screenname is empty"); + return FPResult_InvalidIPCParam; // should return 0xC0C00800 ? + } + uint16be* outputStr = (uint16be*)vecOut->basePhys.GetPtr(); + for(sint32 i = 0; i < ACT_NICKNAME_SIZE; i++) + outputStr[i] = screenname[i]; + return FPResult_Ok; + } + + nnResult CallHandler_GetMyMii(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + uint8 slot = iosu::act::getCurrentAccountSlot(); + if(vecOut->size != FFL_SIZE) + { + cemuLog_log(LogType::Force, "GetMyMii: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + bool r = iosu::act::getMii(slot, (FFLData_t*)vecOut->basePhys.GetPtr()); + if (!r) + { + cemuLog_log(LogType::Force, "GetMyMii: Mii is empty"); + return FPResult_InvalidIPCParam; // should return 0xC0C00800 ? + } + return FPResult_Ok; + } + + nnResult CallHandler_GetMyProfile(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + uint8 slot = iosu::act::getCurrentAccountSlot(); + FPProfile profile{0}; + // todo + cemuLog_log(LogType::Force, "GetMyProfile is todo"); + return WriteValueOutput(vecOut, profile); + } + + nnResult CallHandler_GetMyPresence(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + uint8 slot = iosu::act::getCurrentAccountSlot(); + SelfPresence selfPresence{0}; + cemuLog_log(LogType::Force, "GetMyPresence is todo"); + return WriteValueOutput(vecOut, selfPresence); + } + + nnResult CallHandler_GetMyComment(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + static constexpr uint32 MY_COMMENT_LENGTH = 0x12; // are comments utf16? Buffer length is 0x24 + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + if(vecOut->size != MY_COMMENT_LENGTH*sizeof(uint16be)) + { + cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + std::basic_string myComment; + myComment.resize(MY_COMMENT_LENGTH); + memcpy(vecOut->basePhys.GetPtr(), myComment.data(), MY_COMMENT_LENGTH*sizeof(uint16be)); + return 0; + } + + nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + FPDPreference selfPreference{0}; + if(g_fpd.nexFriendSession) + { + nexPrincipalPreference nexPreference; + g_fpd.nexFriendSession->getMyPreference(nexPreference); + selfPreference.showOnline = nexPreference.showOnline; + selfPreference.showGame = nexPreference.showGame; + selfPreference.blockFriendRequests = nexPreference.blockFriendRequests; + selfPreference.ukn = 0; + } + else + memset(&selfPreference, 0, sizeof(FPDPreference)); + return WriteValueOutput(vecOut, selfPreference); + } + + nnResult CallHandler_GetMyPlayingGame(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + SelfPlayingGame selfPlayingGame{0}; + cemuLog_log(LogType::Force, "GetMyPlayingGame is todo"); + return WriteValueOutput(vecOut, selfPlayingGame); + } + + nnResult CallHandler_GetFriendAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + // todo - online check + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, uint32be, count, 0); + DeclareOutputPtr(accountId, CafeString, count, 0); + memset(accountId, 0, ACT_ACCOUNTID_LENGTH * count); + if (g_fpd.nexFriendSession) + { + for (uint32 i = 0; i < count; i++) + { + const uint32 pid = pidList[i]; + auto& nnidOutput = accountId[i]; + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + nnidOutput.assign(frd.nnaInfo.principalInfo.nnid); + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + nnidOutput.assign(frdReq.principalInfo.nnid); + continue; + } + cemuLog_log(LogType::Force, "GetFriendAccountId: PID {} not found", pid); + } + } + return FPResult_Ok; + } + + nnResult CallHandler_GetFriendScreenName(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + static_assert(sizeof(CafeWideString) == 11*2); + if(numVecIn != 3 || numVecOut != 2) + return FPResult_InvalidIPCParam; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, uint32be, count, 0); + DeclareInput(replaceNonAscii, uint8be, 2); + DeclareOutputPtr(nameList, CafeWideString, count, 0); + uint8be* languageList = nullptr; + if(vecOut[1].size > 0) // languageList is optional + { + DeclareOutputPtr(_languageList, uint8be, count, 1); + languageList = _languageList; + } + memset(nameList, 0, ACT_NICKNAME_SIZE * sizeof(CafeWideString)); + if (g_fpd.nexFriendSession) + { + for (uint32 i = 0; i < count; i++) + { + const uint32 pid = pidList[i]; + CafeWideString& screennameOutput = nameList[i]; + if (languageList) + languageList[i] = 0; // unknown + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + screennameOutput.assignFromUTF8(frd.nnaInfo.principalInfo.mii.miiNickname); + if (languageList) + languageList[i] = frd.nnaInfo.principalInfo.regionGuessed; + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + screennameOutput.assignFromUTF8(frdReq.principalInfo.mii.miiNickname); + if (languageList) + languageList[i] = frdReq.principalInfo.regionGuessed; + continue; + } + cemuLog_log(LogType::Force, "GetFriendScreenName: PID {} not found", pid); + } + } + return FPResult_Ok; + } + + nnResult CallHandler_GetFriendMii(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, uint32be, count, 0); + DeclareOutputPtr(miiList, FFLData_t, count, 0); + memset(miiList, 0, sizeof(FFLData_t) * count); + if (g_fpd.nexFriendSession) + { + for (uint32 i = 0; i < count; i++) + { + const uint32 pid = pidList[i]; + FFLData_t& miiOutput = miiList[i]; + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + memcpy(&miiOutput, frd.nnaInfo.principalInfo.mii.miiData, FFL_SIZE); + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + memcpy(&miiOutput, frdReq.principalInfo.mii.miiData, FFL_SIZE); + continue; + } + } + } + return FPResult_Ok; + } + + nnResult CallHandler_GetFriendPresence(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, uint32be, count, 0); + DeclareOutputPtr(presenceList, FriendPresence, count, 0); + memset(presenceList, 0, sizeof(FriendPresence) * count); + if (g_fpd.nexFriendSession) + { + for (uint32 i = 0; i < count; i++) + { + FriendPresence& presenceOutput = presenceList[i]; + const uint32 pid = pidList[i]; + nexFriend frd; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + presenceOutput.isOnline = frd.presence.isOnline ? 1 : 0; + presenceOutput.isValid = 1; + // todo - region and subregion + presenceOutput.gameMode.joinFlagMask = frd.presence.joinFlagMask; + presenceOutput.gameMode.matchmakeType = frd.presence.joinAvailability; + presenceOutput.gameMode.joinGameId = frd.presence.gameId; + presenceOutput.gameMode.joinGameMode = frd.presence.gameMode; + presenceOutput.gameMode.hostPid = frd.presence.hostPid; + presenceOutput.gameMode.groupId = frd.presence.groupId; + + memcpy(presenceOutput.gameMode.appSpecificData, frd.presence.appSpecificData, 0x14); + } + else + { + cemuLog_log(LogType::Force, "GetFriendPresence: PID {} not found", pid); + } + } + } + return FPResult_Ok; + } + + nnResult CallHandler_GetFriendRelationship(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if(numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + // todo - check for valid session (same for all GetFriend* functions) + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, uint32be, count, 0); + DeclareOutputPtr(relationshipList, uint8be, count, 0); // correct? + for(uint32 i=0; igetFriendByPID(frd, pid)) + { + relationshipOutput = RELATIONSHIP_FRIEND; + continue; + } + else if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + if (incoming) + relationshipOutput = RELATIONSHIP_FRIENDREQUEST_IN; + else + relationshipOutput = RELATIONSHIP_FRIENDREQUEST_OUT; + } + } + } + return FPResult_Ok; + } + + nnResult CallHandler_GetFriendList_GetFriendListAll(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut, bool isAll) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if(numVecIn != 2 || numVecOut != 2) + return FPResult_InvalidIPCParam; + DeclareInput(startIndex, uint32be, 0); + DeclareInput(maxCount, uint32be, 1); + if (maxCount * sizeof(FriendPID) != vecOut[0].size || vecOut[0].basePhys.IsNull()) + { + cemuLog_log(LogType::Force, "GetFriendListAll: pid list buffer size is incorrect"); + return FPResult_InvalidIPCParam; + } + if (!g_fpd.nexFriendSession) + return WriteValueOutput(vecOut+1, 0); + betype* pidList = (betype*)vecOut[0].basePhys.GetPtr(); + std::vector temporaryPidList; + temporaryPidList.resize(std::min(maxCount, 500)); + uint32 pidCount = 0; + g_fpd.nexFriendSession->getFriendPIDs(temporaryPidList.data(), &pidCount, startIndex, temporaryPidList.size(), isAll); + std::copy(temporaryPidList.begin(), temporaryPidList.begin() + pidCount, pidList); + return WriteValueOutput(vecOut+1, pidCount); + } + + nnResult CallHandler_GetFriendRequestList(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if(numVecIn != 2 || numVecOut != 2) + return FPResult_InvalidIPCParam; + DeclareInput(startIndex, uint32be, 0); + DeclareInput(maxCount, uint32be, 1); + if(maxCount * sizeof(FriendPID) != vecOut[0].size || vecOut[0].basePhys.IsNull()) + { + cemuLog_log(LogType::Force, "GetFriendRequestList: pid list buffer size is incorrect"); + return FPResult_InvalidIPCParam; + } + if (!g_fpd.nexFriendSession) + return WriteValueOutput(vecOut+1, 0); + betype* pidList = (betype*)vecOut[0].basePhys.GetPtr(); + std::vector temporaryPidList; + temporaryPidList.resize(std::min(maxCount, 500)); + uint32 pidCount = 0; + g_fpd.nexFriendSession->getFriendRequestPIDs(temporaryPidList.data(), &pidCount, startIndex, temporaryPidList.size(), true, false); + std::copy(temporaryPidList.begin(), temporaryPidList.begin() + pidCount, pidList); + return WriteValueOutput(vecOut+1, pidCount); + } + + nnResult CallHandler_GetFriendRequestListEx(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if(numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, uint32be, count, 0); + DeclareOutputPtr(friendRequests, FriendRequest, count, 0); + memset(friendRequests, 0, sizeof(FriendRequest) * count); + if (!g_fpd.nexFriendSession) + return FPResult_Ok; + for(uint32 i=0; igetFriendRequestByPID(frdReq, &incoming, pidList[i])) + { + cemuLog_log(LogType::Force, "GetFriendRequestListEx: Failed to get friend request"); + return FPResult_RequestFailed; + } + NexFriendRequestToFPDFriendRequest(&frdReq, incoming, friendRequests + i); + } + return FPResult_Ok; + } + + nnResult CallHandler_GetBlackList(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if(numVecIn != 2 || numVecOut != 2) + return FPResult_InvalidIPCParam; + DeclareInput(startIndex, uint32be, 0); + DeclareInput(maxCount, uint32be, 1); + if(maxCount * sizeof(FriendPID) != vecOut[0].size) + { + cemuLog_log(LogType::Force, "GetBlackList: pid list buffer size is incorrect"); + return FPResult_InvalidIPCParam; + } + if (!g_fpd.nexFriendSession) + return WriteValueOutput(vecOut+1, 0); + betype* pidList = (betype*)vecOut[0].basePhys.GetPtr(); + // todo! + cemuLog_logDebug(LogType::Force, "GetBlackList is todo"); + uint32 countOut = 0; + + return WriteValueOutput(vecOut+1, countOut); + } + + nnResult CallHandler_GetFriendListEx(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if(numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, betype, count, 0); + if(count * sizeof(FriendPID) != vecIn[0].size) + { + cemuLog_log(LogType::Force, "GetFriendListEx: pid input list buffer size is incorrect"); + return FPResult_InvalidIPCParam; + } + if(count * sizeof(FriendData) != vecOut[0].size) + { + cemuLog_log(LogType::Force, "GetFriendListEx: Friend output list buffer size is incorrect"); + return FPResult_InvalidIPCParam; + } + FriendData* friendOutput = (FriendData*)vecOut[0].basePhys.GetPtr(); + memset(friendOutput, 0, sizeof(FriendData) * count); + if (g_fpd.nexFriendSession) + { + for (uint32 i = 0; i < count; i++) + { + uint32 pid = pidList[i]; + FriendData* friendData = friendOutput + i; + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + NexFriendToFPDFriendData(&frd, friendData); + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + NexFriendRequestToFPDFriendData(&frdReq, incoming, friendData); + continue; + } + cemuLog_logDebug(LogType::Force, "GetFriendListEx: Failed to find friend or request with pid {}", pid); + memset(friendData, 0, sizeof(FriendData)); + } + } + return FPResult_Ok; + } + + static void NexBasicInfoToBasicInfo(const nexPrincipalBasicInfo& nexBasicInfo, FriendBasicInfo& basicInfo) + { + memset(&basicInfo, 0, sizeof(FriendBasicInfo)); + basicInfo.pid = nexBasicInfo.principalId; + strcpy(basicInfo.nnid, nexBasicInfo.nnid); + + convertMultiByteStringToBigEndianWidechar(nexBasicInfo.mii.miiNickname, basicInfo.screenname, sizeof(basicInfo.screenname) / sizeof(uint16be)); + memcpy(basicInfo.miiData, nexBasicInfo.mii.miiData, FFL_SIZE); + + basicInfo.uknDate90.day = 1; + basicInfo.uknDate90.month = 1; + basicInfo.uknDate90.hour = 1; + basicInfo.uknDate90.minute = 1; + basicInfo.uknDate90.second = 1; // unknown values not set: // ukn15 // ukn2E // ukn2F } - // success - handleFPCallback(&cbInfo->fpCallback, 0x00000000); - free(cbInfo->principalBasicInfo); - free(cbInfo->pidList); - free(cbInfo); - } - - void handleRequest_GetBasicInfoAsync(iosuFpdCemuRequest_t* fpdCemuRequest) - { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + nnResult CallHandler_GetBasicInfoAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - sint32 count = fpdCemuRequest->getBasicInfo.count; - - nexPrincipalBasicInfo* principalBasicInfo = new nexPrincipalBasicInfo[count]; - uint32* pidList = (uint32*)malloc(sizeof(uint32)*count); - for (sint32 i = 0; i < count; i++) - pidList[i] = fpdCemuRequest->getBasicInfo.pidList[i]; - getBasicInfoAsyncParams_t* cbInfo = (getBasicInfoAsyncParams_t*)malloc(sizeof(getBasicInfoAsyncParams_t)); - cbInfo->principalBasicInfo = principalBasicInfo; - cbInfo->pidList = pidList; - cbInfo->count = count; - cbInfo->friendBasicInfo = fpdCemuRequest->getBasicInfo.basicInfo.GetPtr(); - cbInfo->fpCallback.funcMPTR = fpdCemuRequest->getBasicInfo.funcPtr; - cbInfo->fpCallback.customParam = fpdCemuRequest->getBasicInfo.custom; - g_fpd.nexFriendSession->requestPrincipleBaseInfoByPID(principalBasicInfo, pidList, count, handleResultCB_GetBasicInfoAsync, cbInfo); - } - - void handleResponse_addOrRemoveFriend(uint32 errorCode, MPTR funcMPTR, MPTR custom) - { - if (errorCode == 0) - { - handleFPCallback2(funcMPTR, custom, 0); - g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list - } - else - handleFPCallback2(funcMPTR, custom, 0x80000000); - } - - void handleRequest_RemoveFriendAsync(iosuFpdCemuRequest_t* fpdCemuRequest) - { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - g_fpd.nexFriendSession->removeFriend(fpdCemuRequest->addOrRemoveFriend.pid, std::bind(handleResponse_addOrRemoveFriend, std::placeholders::_1, fpdCemuRequest->addOrRemoveFriend.funcPtr, fpdCemuRequest->addOrRemoveFriend.custom)); - } - - void handleResponse_MarkFriendRequestAsReceivedAsync(uint32 errorCode, MPTR funcMPTR, MPTR custom) - { - if (errorCode == 0) - handleFPCallback2(funcMPTR, custom, 0); - else - handleFPCallback2(funcMPTR, custom, 0x80000000); - } - - void handleRequest_MarkFriendRequestAsReceivedAsync(iosuFpdCemuRequest_t* fpdCemuRequest) - { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - // convert messageId list to little endian - uint64 messageIds[100]; - sint32 count = 0; - for (uint32 i = 0; i < fpdCemuRequest->markFriendRequest.count; i++) - { - uint64 mid = _swapEndianU64(fpdCemuRequest->markFriendRequest.messageIdList.GetPtr()[i]); - if (mid == 0) + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidListBE, betype, count, 0); + DeclareOutputPtr(basicInfoList, FriendBasicInfo, count, 0); + if (!g_fpd.nexFriendSession) { - cemuLog_logDebug(LogType::Force, "MarkFriendRequestAsReceivedAsync - Invalid messageId"); - continue; + memset(basicInfoList, 0, sizeof(FriendBasicInfo) * sizeof(count)); + return FPResult_Ok; } - messageIds[count] = mid; - count++; - if (count >= sizeof(messageIds)/sizeof(messageIds[0])) - break; - } - // skipped for now - g_fpd.nexFriendSession->markFriendRequestsAsReceived(messageIds, count, std::bind(handleResponse_MarkFriendRequestAsReceivedAsync, std::placeholders::_1, fpdCemuRequest->markFriendRequest.funcPtr, fpdCemuRequest->markFriendRequest.custom)); - } - - void handleResponse_cancelMyFriendRequest(uint32 errorCode, uint32 pid, MPTR funcMPTR, MPTR custom) - { - if (errorCode == 0) - { - handleFPCallback2(funcMPTR, custom, 0); - } - else - handleFPCallback2(funcMPTR, custom, 0x80000000); - } - - void handleRequest_CancelFriendRequestAsync(iosuFpdCemuRequest_t* fpdCemuRequest) - { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - // find friend request with matching pid - nexFriendRequest frq; - bool isIncoming; - if (g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, fpdCemuRequest->cancelOrAcceptFriendRequest.messageId)) - { - g_fpd.nexFriendSession->removeFriend(frq.principalInfo.principalId, std::bind(handleResponse_cancelMyFriendRequest, std::placeholders::_1, frq.principalInfo.principalId, fpdCemuRequest->cancelOrAcceptFriendRequest.funcPtr, fpdCemuRequest->cancelOrAcceptFriendRequest.custom)); - } - else - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - } - - void handleResponse_acceptFriendRequest(uint32 errorCode, uint32 pid, MPTR funcMPTR, MPTR custom) - { - if (errorCode == 0) - { - handleFPCallback2(funcMPTR, custom, 0); - g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list - } - else - handleFPCallback2(funcMPTR, custom, 0x80000000); - } - - void handleRequest_AcceptFriendRequestAsync(iosuFpdCemuRequest_t* fpdCemuRequest) - { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - // find friend request with matching pid - nexFriendRequest frq; - bool isIncoming; - if (g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, fpdCemuRequest->cancelOrAcceptFriendRequest.messageId)) - { - g_fpd.nexFriendSession->acceptFriendRequest(fpdCemuRequest->cancelOrAcceptFriendRequest.messageId, std::bind(handleResponse_acceptFriendRequest, std::placeholders::_1, frq.principalInfo.principalId, fpdCemuRequest->cancelOrAcceptFriendRequest.funcPtr, fpdCemuRequest->cancelOrAcceptFriendRequest.custom)); - } - else - { - fpdCemuRequest->returnCode = 0x80000000; - return; - } - } - - void handleResponse_addFriendRequest(uint32 errorCode, MPTR funcMPTR, MPTR custom) - { - if (errorCode == 0) - { - handleFPCallback2(funcMPTR, custom, 0); - g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list - } - else - handleFPCallback2(funcMPTR, custom, 0x80000000); - } - - void handleRequest_AddFriendRequest(iosuFpdCemuRequest_t* fpdCemuRequest) - { - fpdCemuRequest->returnCode = 0; - if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) - { - fpdCemuRequest->returnCode = 0x80000000; - return; + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + std::vector pidList; + std::copy(pidListBE, pidListBE + count, std::back_inserter(pidList)); + g_fpd.nexFriendSession->requestPrincipleBaseInfoByPID(pidList.data(), count, [cmd, basicInfoList, count](NexFriends::RpcErrorCode result, std::span basicInfo) -> void { + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + cemu_assert_debug(basicInfo.size() == count); + for(uint32 i = 0; i < count; i++) + NexBasicInfoToBasicInfo(basicInfo[i], basicInfoList[i]); + ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; } - uint16be* input = fpdCemuRequest->addFriendRequest.message.GetPtr(); - size_t inputLen = 0; - while (input[inputLen] != 0) - inputLen++; - std::string msg = StringHelpers::ToUtf8({ input, inputLen }); - - g_fpd.nexFriendSession->addFriendRequest(fpdCemuRequest->addFriendRequest.pid, msg.data(), std::bind(handleResponse_addFriendRequest, std::placeholders::_1, fpdCemuRequest->addFriendRequest.funcPtr, fpdCemuRequest->addFriendRequest.custom)); - } - - // called once a second to handle state checking and updates of the friends service - void iosuFpd_updateFriendsService() - { - if (g_fpd.nexFriendSession) + nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { - g_fpd.nexFriendSession->update(); + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInputPtr(newPreference, FPDPreference, 1, 0); + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + g_fpd.nexFriendSession->updatePreferencesAsync(nexPrincipalPreference(newPreference->showOnline != 0 ? 1 : 0, newPreference->showGame != 0 ? 1 : 0, newPreference->blockFriendRequests != 0 ? 1 : 0), [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } - if (g_fpd.asyncLoginCallback.func != MPTR_NULL) + nnResult CallHandler_AddFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 2 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInputPtr(playRecord, RecentPlayRecordEx, 1, 0); + uint32 msgLength = vecIn[1].size/sizeof(uint16be); + DeclareInputPtr(msgBE, uint16be, msgLength, 1); + if(msgLength == 0 || msgBE[msgLength-1] != 0) { - if (g_fpd.nexFriendSession->isOnline()) - { - *_fpdAsyncLoginRetCode.GetPtr() = 0x00000000; - coreinitAsyncCallback_add(g_fpd.asyncLoginCallback.func, 2, _fpdAsyncLoginRetCode.GetMPTR(), g_fpd.asyncLoginCallback.customParam); - g_fpd.asyncLoginCallback.func = MPTR_NULL; - g_fpd.asyncLoginCallback.customParam = MPTR_NULL; - } + cemuLog_log(LogType::Force, "AddFriendRequestAsync: Message must contain at least a null-termination character and end with one"); + return FPResult_InvalidIPCParam; } + std::string msg = StringHelpers::ToUtf8({ msgBE, msgLength-1 }); + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + g_fpd.nexFriendSession->addFriendRequest(playRecord->pid, msg.data(), [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; } - } - void iosuFpd_thread() - { - SetThreadName("iosuFpd_thread"); - while (true) + nnResult CallHandler_AcceptFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { - uint32 returnValue = 0; // Ioctl return value - ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithTimeout(IOS_DEVICE_FPD, 1000); - if (ioQueueEntry == nullptr) + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(requestId, uint64be, 0); + nexFriendRequest frq; + bool isIncoming; + if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId)) + return FPResult_RequestFailed; + if(!isIncoming) { - iosuFpd_updateFriendsService(); - continue; + cemuLog_log(LogType::Force, "AcceptFriendRequestAsync: Trying to accept outgoing friend request"); + return FPResult_RequestFailed; } - if (ioQueueEntry->request == IOSU_FPD_REQUEST_CEMU) - { - iosuFpdCemuRequest_t* fpdCemuRequest = (iosuFpdCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); - if (fpdCemuRequest->requestCode == IOSU_FPD_INITIALIZE) - { - if (g_fpd.isInitialized2 == false) - { - if(ActiveSettings::IsOnlineEnabled()) - startFriendSession(); - - g_fpd.isInitialized2 = true; - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_SET_NOTIFICATION_HANDLER) - { - g_fpd.notificationFunc = fpdCemuRequest->setNotificationHandler.funcPtr; - g_fpd.notificationCustomParam = fpdCemuRequest->setNotificationHandler.custom; - g_fpd.notificationMask = fpdCemuRequest->setNotificationHandler.notificationMask; - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_LOGIN_ASYNC) - { - if (g_fpd.nexFriendSession) - { - g_fpd.asyncLoginCallback.func = fpdCemuRequest->loginAsync.funcPtr; - g_fpd.asyncLoginCallback.customParam = fpdCemuRequest->loginAsync.custom; - } - else - { - // offline mode - *_fpdAsyncLoginRetCode.GetPtr() = 0; // if we return 0x80000000 here then Splatoon softlocks during boot - coreinitAsyncCallback_add(fpdCemuRequest->loginAsync.funcPtr, 2, _fpdAsyncLoginRetCode.GetMPTR(), fpdCemuRequest->loginAsync.custom); - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_IS_ONLINE) - { - fpdCemuRequest->resultU32.u32 = g_fpd.nexFriendSession ? g_fpd.nexFriendSession->isOnline() : 0; - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_IS_PREFERENCE_VALID) - { - fpdCemuRequest->resultU32.u32 = 1; // todo (if this returns 0, the friend app will show the first-time-setup screen and ask the user to configure preferences) - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_PRINCIPAL_ID) - { - uint8 slot = iosu::act::getCurrentAccountSlot(); - iosu::act::getPrincipalId(slot, &fpdCemuRequest->resultU32.u32); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_ACCOUNT_ID) - { - char* accountId = (char*)fpdCemuRequest->common.ptr.GetPtr(); - uint8 slot = iosu::act::getCurrentAccountSlot(); - if (iosu::act::getAccountId(slot, accountId) == false) - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - else - fpdCemuRequest->returnCode = 0; - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_MII) - { - FFLData_t* fflData = (FFLData_t*)fpdCemuRequest->common.ptr.GetPtr(); - uint8 slot = iosu::act::getCurrentAccountSlot(); - if (iosu::act::getMii(slot, fflData) == false) - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - else - fpdCemuRequest->returnCode = 0; - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_SCREENNAME) - { - uint16be* screennameOutput = (uint16be*)fpdCemuRequest->common.ptr.GetPtr(); - uint8 slot = iosu::act::getCurrentAccountSlot(); - uint16 screennameTemp[ACT_NICKNAME_LENGTH]; - if (iosu::act::getScreenname(slot, screennameTemp)) - { - for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) - { - screennameOutput[i] = screennameTemp[i]; - } - screennameOutput[ACT_NICKNAME_LENGTH] = '\0'; // length is ACT_NICKNAME_LENGTH+1 - fpdCemuRequest->returnCode = 0; - } - else - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - screennameOutput[0] = '\0'; - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_ACCOUNT_ID) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - fpdCemuRequest->returnCode = 0; - for (sint32 i = 0; i < fpdCemuRequest->getFriendAccountId.count; i++) - { - char* nnidOutput = fpdCemuRequest->getFriendAccountId.accountIds.GetPtr()+i*17; - uint32 pid = fpdCemuRequest->getFriendAccountId.pidList[i]; - if (g_fpd.nexFriendSession == nullptr) - { - nnidOutput[0] = '\0'; - continue; - } - nexFriend frd; - nexFriendRequest frdReq; - if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) - { - strcpy(nnidOutput, frd.nnaInfo.principalInfo.nnid); - continue; - } - bool incoming = false; - if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) - { - strcpy(nnidOutput, frdReq.principalInfo.nnid); - continue; - } - nnidOutput[0] = '\0'; - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_SCREENNAME) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - fpdCemuRequest->returnCode = 0; - for (sint32 i = 0; i < fpdCemuRequest->getFriendScreenname.count; i++) - { - uint16be* screennameOutput = fpdCemuRequest->getFriendScreenname.nameList.GetPtr()+i*11; - uint32 pid = fpdCemuRequest->getFriendScreenname.pidList[i]; - if(fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) - fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = 0; - if (g_fpd.nexFriendSession == nullptr) - { - screennameOutput[0] = '\0'; - continue; - } - nexFriend frd; - nexFriendRequest frdReq; - if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) - { - convertMultiByteStringToBigEndianWidechar(frd.nnaInfo.principalInfo.mii.miiNickname, screennameOutput, 11); - if (fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) - fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = frd.nnaInfo.principalInfo.regionGuessed; - continue; - } - bool incoming = false; - if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) - { - convertMultiByteStringToBigEndianWidechar(frdReq.principalInfo.mii.miiNickname, screennameOutput, 11); - if (fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) - fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = frdReq.principalInfo.regionGuessed; - continue; - } - screennameOutput[0] = '\0'; - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_MII) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - fpdCemuRequest->returnCode = 0; - for (sint32 i = 0; i < fpdCemuRequest->getFriendMii.count; i++) - { - uint8* miiOutput = fpdCemuRequest->getFriendMii.miiList + i * FFL_SIZE; - uint32 pid = fpdCemuRequest->getFriendMii.pidList[i]; - if (g_fpd.nexFriendSession == nullptr) - { - memset(miiOutput, 0, FFL_SIZE); - continue; - } - nexFriend frd; - nexFriendRequest frdReq; - if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) - { - memcpy(miiOutput, frd.nnaInfo.principalInfo.mii.miiData, FFL_SIZE); - continue; - } - bool incoming = false; - if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) - { - memcpy(miiOutput, frdReq.principalInfo.mii.miiData, FFL_SIZE); - continue; - } - memset(miiOutput, 0, FFL_SIZE); - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_PRESENCE) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - fpdCemuRequest->returnCode = 0; - for (sint32 i = 0; i < fpdCemuRequest->getFriendPresence.count; i++) - { - friendPresence_t* presenceOutput = (friendPresence_t*)(fpdCemuRequest->getFriendPresence.presenceList + i * sizeof(friendPresence_t)); - memset(presenceOutput, 0, sizeof(friendPresence_t)); - uint32 pid = fpdCemuRequest->getFriendPresence.pidList[i]; - if (g_fpd.nexFriendSession == nullptr) - { - continue; - } - nexFriend frd; - nexFriendRequest frdReq; - if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) - { - presenceOutput->isOnline = frd.presence.isOnline ? 1 : 0; - presenceOutput->isValid = 1; - - presenceOutput->gameMode.joinFlagMask = frd.presence.joinFlagMask; - presenceOutput->gameMode.matchmakeType = frd.presence.joinAvailability; - presenceOutput->gameMode.joinGameId = frd.presence.gameId; - presenceOutput->gameMode.joinGameMode = frd.presence.gameMode; - presenceOutput->gameMode.hostPid = frd.presence.hostPid; - presenceOutput->gameMode.groupId = frd.presence.groupId; - - memcpy(presenceOutput->gameMode.appSpecificData, frd.presence.appSpecificData, 0x14); - - continue; - } - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_RELATIONSHIP) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - fpdCemuRequest->returnCode = 0; - for (sint32 i = 0; i < fpdCemuRequest->getFriendRelationship.count; i++) - { - uint8* relationshipOutput = (fpdCemuRequest->getFriendRelationship.relationshipList + i); - uint32 pid = fpdCemuRequest->getFriendRelationship.pidList[i]; - *relationshipOutput = RELATIONSHIP_INVALID; - if (g_fpd.nexFriendSession == nullptr) - { - continue; - } - nexFriend frd; - nexFriendRequest frdReq; - bool incoming; - if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) - { - *relationshipOutput = RELATIONSHIP_FRIEND; - continue; - } - else if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) - { - if(incoming) - *relationshipOutput = RELATIONSHIP_FRIENDREQUEST_IN; - else - *relationshipOutput = RELATIONSHIP_FRIENDREQUEST_OUT; - } - } - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST) - { - handleRequest_GetFriendList(fpdCemuRequest, false); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIENDREQUEST_LIST) - { - handleRequest_GetFriendRequestList(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST_ALL) - { - handleRequest_GetFriendList(fpdCemuRequest, true); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST_EX) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - handleRequest_GetFriendListEx(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIENDREQUEST_LIST_EX) - { - if (g_fpd.nexFriendSession == nullptr) - { - fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code - iosuIoctl_completeRequest(ioQueueEntry, returnValue); - return; - } - handleRequest_GetFriendRequestListEx(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_ADD_FRIEND) - { - // todo - figure out how this works - *_fpdAsyncAddFriendRetCode.GetPtr() = 0; - coreinitAsyncCallback_add(fpdCemuRequest->addOrRemoveFriend.funcPtr, 2, _fpdAsyncAddFriendRetCode.GetMPTR(), fpdCemuRequest->addOrRemoveFriend.custom); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_ADD_FRIEND_REQUEST) - { - handleRequest_AddFriendRequest(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_REMOVE_FRIEND_ASYNC) - { - handleRequest_RemoveFriendAsync(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC) - { - handleRequest_MarkFriendRequestAsReceivedAsync(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC) - { - handleRequest_CancelFriendRequestAsync(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC) - { - handleRequest_AcceptFriendRequestAsync(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_BASIC_INFO_ASYNC) - { - handleRequest_GetBasicInfoAsync(fpdCemuRequest); - } - else if (fpdCemuRequest->requestCode == IOSU_FPD_UPDATE_GAMEMODE) - { - gameMode_t* gameMode = fpdCemuRequest->updateGameMode.gameMode.GetPtr(); - uint16be* gameModeMessage = fpdCemuRequest->updateGameMode.gameModeMessage.GetPtr(); - - g_fpd.myPresence.joinFlagMask = gameMode->joinFlagMask; - - g_fpd.myPresence.joinAvailability = (uint8)(uint32)gameMode->matchmakeType; - g_fpd.myPresence.gameId = gameMode->joinGameId; - g_fpd.myPresence.gameMode = gameMode->joinGameMode; - g_fpd.myPresence.hostPid = gameMode->hostPid; - g_fpd.myPresence.groupId = gameMode->groupId; - memcpy(g_fpd.myPresence.appSpecificData, gameMode->appSpecificData, 0x14); - - if (g_fpd.nexFriendSession) - { - g_fpd.nexFriendSession->updateMyPresence(g_fpd.myPresence); - } - - fpdCemuRequest->returnCode = 0; - } - else - cemu_assert_unimplemented(); - } - else - assert_dbg(); - iosuIoctl_completeRequest(ioQueueEntry, returnValue); + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + g_fpd.nexFriendSession->acceptFriendRequest(requestId, [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + return ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; } - return; + + nnResult CallHandler_DeleteFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + // reject incoming friend request + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(requestId, uint64be, 0); + nexFriendRequest frq; + bool isIncoming; + if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId)) + return FPResult_RequestFailed; + if(!isIncoming) + { + cemuLog_log(LogType::Force, "CancelFriendRequestAsync: Trying to block outgoing friend request"); + return FPResult_RequestFailed; + } + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + g_fpd.nexFriendSession->deleteFriendRequest(requestId, [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + return ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } + + nnResult CallHandler_CancelFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + // retract outgoing friend request + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(requestId, uint64be, 0); + nexFriendRequest frq; + bool isIncoming; + if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId)) + return FPResult_RequestFailed; + if(isIncoming) + { + cemuLog_log(LogType::Force, "CancelFriendRequestAsync: Trying to cancel incoming friend request"); + return FPResult_RequestFailed; + } + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + g_fpd.nexFriendSession->removeFriend(frq.principalInfo.principalId, [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + return ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } + + nnResult CallHandler_MarkFriendRequestsAsReceivedAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 2 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(requestIdsBE, uint64be, count, 0); + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + // endian convert + std::vector requestIds; + std::copy(requestIdsBE, requestIdsBE + count, std::back_inserter(requestIds)); + g_fpd.nexFriendSession->markFriendRequestsAsReceived(requestIds.data(), requestIds.size(), [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + return ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } + + nnResult CallHandler_RemoveFriendAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(pid, uint32be, 0); + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + g_fpd.nexFriendSession->removeFriend(pid, [cmd](NexFriends::RpcErrorCode result){ + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + return ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } + + nnResult CallHandler_DeleteFriendFlagsAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 3 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(pid, uint32be, 0); + cemuLog_logDebug(LogType::Force, "DeleteFriendFlagsAsync is todo"); + return FPResult_Ok; + } + + nnResult CallHandler_CheckSettingStatusAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if (numVecIn != 0 || numVecOut != 1) + return FPResult_InvalidIPCParam; + if (vecOut[0].size != sizeof(uint8be)) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + + // for now we respond immediately + uint8 settingsStatus = 1; // todo - figure out what this status means + auto r = WriteValueOutput(vecOut, settingsStatus); + ServiceCallAsyncRespond(cmd, r); + cemuLog_log(LogType::Force, "CheckSettingStatusAsync is todo"); + + return FPResult_Ok; + } + + nnResult CallHandler_IsPreferenceValid(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if (numVecIn != 0 || numVecOut != 1) + return 0; + if (!g_fpd.nexFriendSession) + return 0; + // we currently automatically put the preferences into a valid state on session creation if they are not set yet + return WriteValueOutput(vecOut, 1); // if we return 0, the friend app will show the first time setup screen + } + + nnResult CallHandler_GetRequestBlockSettingAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) // todo + { + if (numVecIn != 2 || numVecOut != 1) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(count, uint32be, 1); + DeclareInputPtr(pidList, betype, count, 0); + DeclareOutputPtr(settingList, uint8be, count, 0); + cemuLog_log(LogType::Force, "GetRequestBlockSettingAsync is todo"); + + for (uint32 i = 0; i < count; i++) + settingList[i] = 0; + // implementation is todo. Used by friend list app when adding a friend + // 0 means not blocked. Friend app will continue with GetBasicInformation() + // 1 means blocked. Friend app will continue with AddFriendAsync to add the user as a provisional friend + + return FPResult_Ok; + } + + nnResult CallHandler_AddFriendAsyncByPid(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInput(pid, uint32be, 0); + cemuLog_log(LogType::Force, "AddFriendAsyncByPid is todo"); + return FPResult_Ok; + } + + nnResult CallHandler_UpdateGameMode(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + if (numVecIn != 2 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + DeclareInputPtr(gameMode, iosu::fpd::GameMode, 1, 0); + uint32 messageLength = vecIn[1].size / sizeof(uint16be); + if(messageLength == 0 || (vecIn[1].size%sizeof(uint16be)) != 0) + { + cemuLog_log(LogType::Force, "UpdateGameMode: Message must contain at least a null-termination character"); + return FPResult_InvalidIPCParam; + } + DeclareInputPtr(gameModeMessage, uint16be, messageLength, 1); + messageLength--; + GameModeToNexPresence(gameMode, &g_fpd.myPresence); + g_fpd.nexFriendSession->updateMyPresence(g_fpd.myPresence); + // todo - message + return FPResult_Ok; + } + + void StartFriendSession() + { + bool expected = false; + if (!g_fpd.sessionStarted.compare_exchange_strong(expected, true)) + return; + cemu_assert(!g_fpd.nexFriendSession); + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, 0x0005001010001C00, 0x0000, 0x00003200); + if (!nexTokenResult.isValid()) + { + cemuLog_logDebug(LogType::Force, "IOSU_FPD: Failed to acquire nex token for friend server"); + g_fpd.myPresence.isOnline = 0; + return; + } + // get values needed for friend session + uint32 myPid; + uint8 currentSlot = iosu::act::getCurrentAccountSlot(); + iosu::act::getPrincipalId(currentSlot, &myPid); + char accountId[256] = { 0 }; + iosu::act::getAccountId(currentSlot, accountId); + FFLData_t miiData; + act::getMii(currentSlot, &miiData); + uint16 screenName[ACT_NICKNAME_LENGTH + 1] = { 0 }; + act::getScreenname(currentSlot, screenName); + uint32 countryCode = 0; + 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(); + // resolve potential domain to IP address + struct addrinfo hints = {0}, *addrs; + hints.ai_family = AF_INET; + const int status = getaddrinfo(nexTokenResult.nexToken.host, NULL, &hints, &addrs); + if (status != 0) + { + cemuLog_log(LogType::Force, "IOSU_FPD: Failed to resolve hostname {}", nexTokenResult.nexToken.host); + return; + } + char addrstr[NI_MAXHOST]; + getnameinfo(addrs->ai_addr, addrs->ai_addrlen, addrstr, sizeof addrstr, NULL, 0, NI_NUMERICHOST); + cemuLog_log(LogType::Force, "IOSU_FPD: Resolved IP for hostname {}, {}", nexTokenResult.nexToken.host, addrstr); + // start session + const uint32_t hostIp = ((struct sockaddr_in*)addrs->ai_addr)->sin_addr.s_addr; + freeaddrinfo(addrs); + g_fpd.mtxFriendSession.lock(); + g_fpd.nexFriendSession = new NexFriends(hostIp, nexTokenResult.nexToken.port, "ridfebb9", myPid, nexTokenResult.nexToken.nexPassword, nexTokenResult.nexToken.token, accountId, (uint8*)&miiData, (wchar_t*)screenName, (uint8)countryCode, g_fpd.myPresence); + g_fpd.nexFriendSession->setNotificationHandler(NotificationHandler); + g_fpd.mtxFriendSession.unlock(); + cemuLog_log(LogType::Force, "IOSU_FPD: Created friend server session"); + } + + void StopFriendSession() + { + std::unique_lock _l(g_fpd.mtxFriendSession); + bool expected = true; + if (!g_fpd.sessionStarted.compare_exchange_strong(expected, false) ) + return; + delete g_fpd.nexFriendSession; + g_fpd.nexFriendSession = nullptr; + } + + private: + + + }; + + FPDService gFPDService; + + class : public ::IOSUModule + { + void TitleStart() override + { + gFPDService.Start(); + gFPDService.SetTimerUpdate(1000); // call TimerUpdate() once a second + } + void TitleStop() override + { + gFPDService.StopFriendSession(); + gFPDService.Stop(); + } + }sIOSUModuleNNFPD; + + IOSUModule* GetModule() + { + return static_cast(&sIOSUModuleNNFPD); } - void Initialize() - { - if (g_fpd.isThreadStarted) - return; - std::thread t1(iosuFpd_thread); - t1.detach(); - g_fpd.isThreadStarted = true; - } } } + diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.h b/src/Cafe/IOSU/legacy/iosu_fpd.h index bd52035c..79f524d6 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.h +++ b/src/Cafe/IOSU/legacy/iosu_fpd.h @@ -1,10 +1,12 @@ #pragma once +#include "Cafe/IOSU/iosu_types_common.h" +#include "Common/CafeString.h" namespace iosu { namespace fpd { - typedef struct + struct FPDDate { /* +0x0 */ uint16be year; /* +0x2 */ uint8 month; @@ -13,13 +15,61 @@ namespace iosu /* +0x5 */ uint8 minute; /* +0x6 */ uint8 second; /* +0x7 */ uint8 padding; - }fpdDate_t; + }; - static_assert(sizeof(fpdDate_t) == 8); + static_assert(sizeof(FPDDate) == 8); - typedef struct + struct RecentPlayRecordEx { - /* +0x000 */ uint8 type; // type(Non-Zero -> Friend, 0 -> Friend request ? ) + /* +0x00 */ uint32be pid; + /* +0x04 */ uint8 ukn04; + /* +0x05 */ uint8 ukn05; + /* +0x06 */ uint8 ukn06[0x22]; + /* +0x28 */ uint8 ukn28[0x22]; + /* +0x4A */ uint8 _uknOrPadding4A[6]; + /* +0x50 */ uint32be ukn50; + /* +0x54 */ uint32be ukn54; + /* +0x58 */ uint16be ukn58; + /* +0x5C */ uint8 _padding5C[4]; + /* +0x60 */ iosu::fpd::FPDDate date; + }; + + static_assert(sizeof(RecentPlayRecordEx) == 0x68, ""); + static_assert(offsetof(RecentPlayRecordEx, ukn06) == 0x06, ""); + static_assert(offsetof(RecentPlayRecordEx, ukn50) == 0x50, ""); + + struct GameKey + { + /* +0x00 */ uint64be titleId; + /* +0x08 */ uint16be ukn08; + /* +0x0A */ uint8 _padding0A[6]; + }; + static_assert(sizeof(GameKey) == 0x10); + + struct Profile + { + uint8be region; + uint8be regionSubcode; + uint8be platform; + uint8be ukn3; + }; + static_assert(sizeof(Profile) == 0x4); + + struct GameMode + { + /* +0x00 */ uint32be joinFlagMask; + /* +0x04 */ uint32be matchmakeType; + /* +0x08 */ uint32be joinGameId; + /* +0x0C */ uint32be joinGameMode; + /* +0x10 */ uint32be hostPid; + /* +0x14 */ uint32be groupId; + /* +0x18 */ uint8 appSpecificData[0x14]; + }; + static_assert(sizeof(GameMode) == 0x2C); + + struct FriendData + { + /* +0x000 */ uint8 type; // type (Non-Zero -> Friend, 0 -> Friend request ? ) /* +0x001 */ uint8 _padding1[7]; /* +0x008 */ uint32be pid; /* +0x00C */ char nnid[0x10 + 1]; @@ -29,43 +79,29 @@ namespace iosu /* +0x036 */ uint8 ukn036; /* +0x037 */ uint8 _padding037; /* +0x038 */ uint8 mii[0x60]; - /* +0x098 */ fpdDate_t uknDate; + /* +0x098 */ FPDDate uknDate; // sub struct (the part above seems to be shared with friend requests) union { struct { - /* +0x0A0 */ uint8 ukn0A0; // country code? - /* +0x0A1 */ uint8 ukn0A1; // country subcode? - /* +0x0A2 */ uint8 _paddingA2[2]; + /* +0x0A0 */ Profile profile; // this is returned for nn_fp.GetFriendProfile /* +0x0A4 */ uint32be ukn0A4; - /* +0x0A8 */ uint64 gameKeyTitleId; - /* +0x0B0 */ uint16be gameKeyUkn; - /* +0x0B2 */ uint8 _paddingB2[6]; - /* +0x0B8 */ uint32 ukn0B8; - /* +0x0BC */ uint32 ukn0BC; - /* +0x0C0 */ uint32 ukn0C0; - /* +0x0C4 */ uint32 ukn0C4; - /* +0x0C8 */ uint32 ukn0C8; - /* +0x0CC */ uint32 ukn0CC; - /* +0x0D0 */ uint8 appSpecificData[0x14]; - /* +0x0E4 */ uint8 ukn0E4; - /* +0x0E5 */ uint8 _paddingE5; - /* +0x0E6 */ uint16 uknStr[0x80]; // game mode description (could be larger) - /* +0x1E6 */ uint8 _padding1E6[0x1EC - 0x1E6]; + /* +0x0A8 */ GameKey gameKey; + /* +0x0B8 */ GameMode gameMode; + /* +0x0E4 */ CafeWideString<0x82> gameModeDescription; + /* +0x1E8 */ Profile profile1E8; // how does it differ from the one at 0xA0? Returned by GetFriendPresence /* +0x1EC */ uint8 isOnline; /* +0x1ED */ uint8 _padding1ED[3]; // some other sub struct? - /* +0x1F0 */ uint8 ukn1F0; - /* +0x1F1 */ uint8 _padding1F1; - /* +0x1F2 */ uint16be statusMessage[16 + 1]; // pops up every few seconds in friend list (ingame character name?) - /* +0x214 */ uint8 _padding214[4]; - /* +0x218 */ fpdDate_t uknDate218; - /* +0x220 */ fpdDate_t lastOnline; + /* +0x1F0 */ char comment[36]; // pops up every few seconds in friend list + /* +0x214 */ uint32be _padding214; + /* +0x218 */ FPDDate approvalTime; + /* +0x220 */ FPDDate lastOnline; }friendExtraData; struct { - /* +0x0A0 */ uint64 messageId; // guessed + /* +0x0A0 */ uint64be messageId; // guessed. If 0, then relationship is FRIENDSHIP_REQUEST_OUT, otherwise FRIENDSHIP_REQUEST_IN /* +0x0A8 */ uint8 ukn0A8; /* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed) /* +0x0AA */ uint16be comment[0x40]; @@ -75,26 +111,23 @@ namespace iosu /* +0x150 */ uint64 gameKeyTitleId; /* +0x158 */ uint16be gameKeyUkn; /* +0x15A */ uint8 _padding[6]; - /* +0x160 */ fpdDate_t uknData0; - /* +0x168 */ fpdDate_t uknData1; + /* +0x160 */ FPDDate uknData0; + /* +0x168 */ FPDDate uknData1; }requestExtraData; }; - }friendData_t; - - static_assert(sizeof(friendData_t) == 0x228, ""); - static_assert(offsetof(friendData_t, nnid) == 0x00C, ""); - static_assert(offsetof(friendData_t, friendExtraData.gameKeyTitleId) == 0x0A8, ""); - static_assert(offsetof(friendData_t, friendExtraData.appSpecificData) == 0x0D0, ""); - static_assert(offsetof(friendData_t, friendExtraData.uknStr) == 0x0E6, ""); - static_assert(offsetof(friendData_t, friendExtraData.ukn1F0) == 0x1F0, ""); + }; + static_assert(sizeof(FriendData) == 0x228); + static_assert(offsetof(FriendData, friendExtraData.gameKey) == 0x0A8); + static_assert(offsetof(FriendData, friendExtraData.gameModeDescription) == 0x0E4); + static_assert(offsetof(FriendData, friendExtraData.comment) == 0x1F0); - static_assert(offsetof(friendData_t, requestExtraData.messageId) == 0x0A0, ""); - static_assert(offsetof(friendData_t, requestExtraData.comment) == 0x0AA, ""); - static_assert(offsetof(friendData_t, requestExtraData.uknMessage) == 0x12C, ""); - static_assert(offsetof(friendData_t, requestExtraData.gameKeyTitleId) == 0x150, ""); - static_assert(offsetof(friendData_t, requestExtraData.uknData1) == 0x168, ""); + static_assert(offsetof(FriendData, requestExtraData.messageId) == 0x0A0); + static_assert(offsetof(FriendData, requestExtraData.comment) == 0x0AA); + static_assert(offsetof(FriendData, requestExtraData.uknMessage) == 0x12C); + static_assert(offsetof(FriendData, requestExtraData.gameKeyTitleId) == 0x150); + static_assert(offsetof(FriendData, requestExtraData.uknData1) == 0x168); - typedef struct + struct FriendBasicInfo { /* +0x00 */ uint32be pid; /* +0x04 */ char nnid[0x11]; @@ -104,17 +137,17 @@ namespace iosu /* +0x2E */ uint8 ukn2E; // bool option /* +0x2F */ uint8 ukn2F; /* +0x30 */ uint8 miiData[0x60]; - /* +0x90 */ fpdDate_t uknDate90; - }friendBasicInfo_t; // size is 0x98 + /* +0x90 */ FPDDate uknDate90; + }; - static_assert(sizeof(friendBasicInfo_t) == 0x98, ""); - static_assert(offsetof(friendBasicInfo_t, nnid) == 0x04, ""); - static_assert(offsetof(friendBasicInfo_t, ukn15) == 0x15, ""); - static_assert(offsetof(friendBasicInfo_t, screenname) == 0x18, ""); - static_assert(offsetof(friendBasicInfo_t, ukn2E) == 0x2E, ""); - static_assert(offsetof(friendBasicInfo_t, miiData) == 0x30, ""); + static_assert(sizeof(FriendBasicInfo) == 0x98); + static_assert(offsetof(FriendBasicInfo, nnid) == 0x04); + static_assert(offsetof(FriendBasicInfo, ukn15) == 0x15); + static_assert(offsetof(FriendBasicInfo, screenname) == 0x18); + static_assert(offsetof(FriendBasicInfo, ukn2E) == 0x2E); + static_assert(offsetof(FriendBasicInfo, miiData) == 0x30); - typedef struct + struct FriendRequest { /* +0x000 */ uint32be pid; /* +0x004 */ uint8 nnid[17]; // guessed type @@ -125,7 +158,7 @@ namespace iosu /* +0x02E */ uint8 ukn2E; // bool option /* +0x02F */ uint8 ukn2F; // ukn /* +0x030 */ uint8 miiData[0x60]; - /* +0x090 */ fpdDate_t uknDate; + /* +0x090 */ FPDDate uknDate; /* +0x098 */ uint64 ukn98; /* +0x0A0 */ uint8 isMarkedAsReceived; /* +0x0A1 */ uint8 uknA1; @@ -136,216 +169,98 @@ namespace iosu /* +0x148 */ uint64 gameKeyTitleId; /* +0x150 */ uint16be gameKeyUkn; /* +0x152 */ uint8 _padding152[6]; - /* +0x158 */ fpdDate_t uknDate2; - /* +0x160 */ fpdDate_t expireDate; - }friendRequest_t; + /* +0x158 */ FPDDate uknDate2; + /* +0x160 */ FPDDate expireDate; + }; - static_assert(sizeof(friendRequest_t) == 0x168, ""); - static_assert(offsetof(friendRequest_t, uknDate) == 0x090, ""); - static_assert(offsetof(friendRequest_t, message) == 0x0A2, ""); - static_assert(offsetof(friendRequest_t, uknString2) == 0x124, ""); - static_assert(offsetof(friendRequest_t, gameKeyTitleId) == 0x148, ""); + static_assert(sizeof(FriendRequest) == 0x168); + static_assert(offsetof(FriendRequest, uknDate) == 0x090); + static_assert(offsetof(FriendRequest, message) == 0x0A2); + static_assert(offsetof(FriendRequest, uknString2) == 0x124); + static_assert(offsetof(FriendRequest, gameKeyTitleId) == 0x148); - typedef struct + struct FriendPresence { - /* +0x00 */ uint32be joinFlagMask; - /* +0x04 */ uint32be matchmakeType; - /* +0x08 */ uint32be joinGameId; - /* +0x0C */ uint32be joinGameMode; - /* +0x10 */ uint32be hostPid; - /* +0x14 */ uint32be groupId; - /* +0x18 */ uint8 appSpecificData[0x14]; - }gameMode_t; - - static_assert(sizeof(gameMode_t) == 0x2C, ""); - - typedef struct - { - gameMode_t gameMode; - /* +0x2C */ uint8 region; - /* +0x2D */ uint8 regionSubcode; - /* +0x2E */ uint8 platform; - /* +0x2F */ uint8 _padding2F; + GameMode gameMode; + /* +0x2C */ Profile profile; /* +0x30 */ uint8 isOnline; /* +0x31 */ uint8 isValid; /* +0x32 */ uint8 padding[2]; // guessed - }friendPresence_t; + }; + static_assert(sizeof(FriendPresence) == 0x34); + static_assert(offsetof(FriendPresence, isOnline) == 0x30); - static_assert(sizeof(friendPresence_t) == 0x34, ""); - static_assert(offsetof(friendPresence_t, region) == 0x2C, ""); - static_assert(offsetof(friendPresence_t, isOnline) == 0x30, ""); + struct FPDNotification + { + betype type; + betype pid; + }; + static_assert(sizeof(FPDNotification) == 8); + + struct FPDPreference + { + uint8be showOnline; // show online status to others + uint8be showGame; // show played game to others + uint8be blockFriendRequests; // block friend requests + uint8be ukn; // probably padding? + }; + static_assert(sizeof(FPDPreference) == 4); static const int RELATIONSHIP_INVALID = 0; static const int RELATIONSHIP_FRIENDREQUEST_OUT = 1; static const int RELATIONSHIP_FRIENDREQUEST_IN = 2; static const int RELATIONSHIP_FRIEND = 3; - typedef struct + static const int GAMEMODE_MAX_MESSAGE_LENGTH = 0x80; // limit includes null-terminator character, so only 0x7F actual characters can be used + + enum class FPD_REQUEST_ID { - uint32 requestCode; - union - { - struct - { - MEMPTR ptr; - }common; - struct - { - MPTR funcPtr; - MPTR custom; - }loginAsync; - struct - { - MEMPTR pidList; - uint32 startIndex; - uint32 maxCount; - }getFriendList; - struct - { - MEMPTR friendData; - MEMPTR pidList; - uint32 count; - }getFriendListEx; - struct - { - MEMPTR friendRequest; - MEMPTR pidList; - uint32 count; - }getFriendRequestListEx; - struct - { - uint32 pid; - MPTR funcPtr; - MPTR custom; - }addOrRemoveFriend; - struct - { - uint64 messageId; - MPTR funcPtr; - MPTR custom; - }cancelOrAcceptFriendRequest; - struct - { - uint32 pid; - MEMPTR message; - MPTR funcPtr; - MPTR custom; - }addFriendRequest; - struct - { - MEMPTR messageIdList; - uint32 count; - MPTR funcPtr; - MPTR custom; - }markFriendRequest; - struct - { - MEMPTR basicInfo; - MEMPTR pidList; - sint32 count; - MPTR funcPtr; - MPTR custom; - }getBasicInfo; - struct - { - uint32 notificationMask; - MPTR funcPtr; - MPTR custom; - }setNotificationHandler; - struct - { - MEMPTR accountIds; - MEMPTR pidList; - sint32 count; - }getFriendAccountId; - struct - { - MEMPTR nameList; - MEMPTR pidList; - sint32 count; - bool replaceNonAscii; - MEMPTR languageList; - }getFriendScreenname; - struct - { - uint8* miiList; - MEMPTR pidList; - sint32 count; - }getFriendMii; - struct - { - uint8* presenceList; - MEMPTR pidList; - sint32 count; - }getFriendPresence; - struct - { - uint8* relationshipList; - MEMPTR pidList; - sint32 count; - }getFriendRelationship; - struct - { - MEMPTR gameMode; - MEMPTR gameModeMessage; - }updateGameMode; - }; - - // output - uint32 returnCode; // return value - union - { - struct - { - uint32 numReturnedCount; - }resultGetFriendList; - struct - { - uint32 u32; - }resultU32; - }; - }iosuFpdCemuRequest_t; - - // custom dev/fpd protocol (Cemu only) - #define IOSU_FPD_REQUEST_CEMU (0xEE) - - // FPD request Cemu subcodes - enum - { - _IOSU_FPD_NONE, - IOSU_FPD_INITIALIZE, - IOSU_FPD_SET_NOTIFICATION_HANDLER, - IOSU_FPD_LOGIN_ASYNC, - IOSU_FPD_IS_ONLINE, - IOSU_FPD_IS_PREFERENCE_VALID, - - IOSU_FPD_UPDATE_GAMEMODE, - - IOSU_FPD_GET_MY_PRINCIPAL_ID, - IOSU_FPD_GET_MY_ACCOUNT_ID, - IOSU_FPD_GET_MY_MII, - IOSU_FPD_GET_MY_SCREENNAME, - - IOSU_FPD_GET_FRIEND_ACCOUNT_ID, - IOSU_FPD_GET_FRIEND_SCREENNAME, - IOSU_FPD_GET_FRIEND_MII, - IOSU_FPD_GET_FRIEND_PRESENCE, - IOSU_FPD_GET_FRIEND_RELATIONSHIP, - - IOSU_FPD_GET_FRIEND_LIST, - IOSU_FPD_GET_FRIENDREQUEST_LIST, - IOSU_FPD_GET_FRIEND_LIST_ALL, - IOSU_FPD_GET_FRIEND_LIST_EX, - IOSU_FPD_GET_FRIENDREQUEST_LIST_EX, - IOSU_FPD_ADD_FRIEND, - IOSU_FPD_ADD_FRIEND_REQUEST, - IOSU_FPD_REMOVE_FRIEND_ASYNC, - IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC, - IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC, - IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC, - IOSU_FPD_GET_BASIC_INFO_ASYNC, + LoginAsync = 0x2775, + HasLoggedIn = 0x2777, + IsOnline = 0x2778, + GetMyPrincipalId = 0x27D9, + GetMyAccountId = 0x27DA, + GetMyScreenName = 0x27DB, + GetMyMii = 0x27DC, + GetMyProfile = 0x27DD, + GetMyPreference = 0x27DE, + GetMyPresence = 0x27DF, + IsPreferenceValid = 0x27E0, + GetFriendList = 0x283D, + GetFriendListAll = 0x283E, + GetFriendAccountId = 0x283F, + GetFriendScreenName = 0x2840, + GetFriendPresence = 0x2845, + GetFriendRelationship = 0x2846, + GetFriendMii = 0x2841, + GetBlackList = 0x28A1, + GetFriendRequestList = 0x2905, + UpdateGameModeVariation1 = 0x2969, // there seem to be two different requestIds for the 2-param and 3-param version of UpdateGameMode, + UpdateGameModeVariation2 = 0x296A, // but the third parameter is never used and the same handler is used for both + AddFriendAsyncByPid = 0x29CD, + AddFriendAsyncByXXX = 0x29CE, // probably by name? + GetRequestBlockSettingAsync = 0x2B5D, + GetMyComment = 0x4EE9, + GetMyPlayingGame = 0x4EEA, + CheckSettingStatusAsync = 0x7596, + GetFriendListEx = 0x75F9, + GetFriendRequestListEx = 0x76C1, + UpdatePreferenceAsync = 0x7727, + RemoveFriendAsync = 0x7789, + DeleteFriendFlagsAsync = 0x778A, + AddFriendRequestByPlayRecordAsync = 0x778B, + CancelFriendRequestAsync = 0x778C, + AcceptFriendRequestAsync = 0x7851, + DeleteFriendRequestAsync = 0x7852, + MarkFriendRequestsAsReceivedAsync = 0x7854, + GetBasicInfoAsync = 0x7919, + SetLedEventMask = 0x9D0B, + SetNotificationMask = 0x15ff5, + GetNotificationAsync = 0x15FF6, }; - void Initialize(); + using FriendPID = uint32; + + IOSUModule* GetModule(); } } \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_ioctl.cpp b/src/Cafe/IOSU/legacy/iosu_ioctl.cpp index 00be31b0..22e5a55d 100644 --- a/src/Cafe/IOSU/legacy/iosu_ioctl.cpp +++ b/src/Cafe/IOSU/legacy/iosu_ioctl.cpp @@ -1,6 +1,5 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" -#include "Cafe/OS/libs/coreinit/coreinit.h" #include "iosu_ioctl.h" #include "util/helpers/ringbuffer.h" @@ -23,7 +22,7 @@ sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry) } __OSLockScheduler(); ioctlMutex.lock(); - ioQueueEntry->ppcThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance); + ioQueueEntry->ppcThread = coreinit::OSGetCurrentThread(); _ioctlRingbuffer[ioctlHandle].Push(ioQueueEntry); ioctlMutex.unlock(); diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index ade1fa2b..b3b2d4c9 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -1,5 +1,6 @@ #include "iosu_nn_service.h" #include "../kernel/iosu_kernel.h" +#include "util/helpers/helpers.h" using namespace iosu::kernel; @@ -7,6 +8,132 @@ namespace iosu { namespace nn { + /* IPCSimpleService */ + void IPCSimpleService::Start() + { + if (m_isRunning.exchange(true)) + return; + m_threadInitialized = false; + m_requestStop = false; + m_serviceThread = std::thread(&IPCSimpleService::ServiceThread, this); + while (!m_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10)); + StartService(); + } + + void IPCSimpleService::Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_requestStop = true; + StopService(); + if(m_timerId != IOSInvalidTimerId) + IOS_DestroyTimer(m_timerId); + m_timerId = IOSInvalidTimerId; + IOS_SendMessage(m_msgQueueId, 0, 0); // wake up thread + m_serviceThread.join(); + } + + void IPCSimpleService::ServiceThread() + { + if(!GetThreadName().empty()) + SetThreadName(GetThreadName().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); + cemu_assert(!IOS_ResultIsError(r)); + m_threadInitialized = true; + while (true) + { + IOSMessage msg; + r = IOS_ReceiveMessage(m_msgQueueId, &msg, 0); + cemu_assert(!IOS_ResultIsError(r)); + if (msg == 0) + { + cemu_assert_debug(m_requestStop); + break; + } + else if(msg == 1) + { + TimerUpdate(); + continue; + } + IPCCommandBody* cmd = MEMPTR(msg).GetPtr(); + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + void* clientObject = CreateClientObject(); + if(clientObject == nullptr) + { + cemuLog_log(LogType::Force, "IPCSimpleService[{}]: Maximum handle count reached or handle rejected", m_devicePath); + IOS_ResourceReply(cmd, IOS_ERROR_MAXIMUM_REACHED); + continue; + } + IOSDevHandle newHandle = GetFreeHandle(); + m_clientObjects[newHandle] = clientObject; + IOS_ResourceReply(cmd, (IOS_ERROR)newHandle); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + void* clientObject = GetClientObjectByHandle(cmd->devHandle); + if (clientObject) + DestroyClientObject(clientObject); + IOS_ResourceReply(cmd, IOS_ERROR_OK); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) + { + void* clientObject = GetClientObjectByHandle(cmd->devHandle); + if (!clientObject) + { + cemuLog_log(LogType::Force, "IPCSimpleService[{}]: Invalid IPC handle", m_devicePath); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + continue; + } + uint32 requestId = cmd->args[0]; + uint32 numIn = cmd->args[1]; + uint32 numOut = cmd->args[2]; + IPCIoctlVector* vec = MEMPTR{ cmd->args[3] }.GetPtr(); + IPCIoctlVector* vecIn = vec + 0; // the ordering of vecIn/vecOut differs from IPCService + IPCIoctlVector* vecOut = vec + numIn; + m_delayResponse = false; + m_activeCmd = cmd; + uint32 result = ServiceCall(clientObject, requestId, vecIn, numIn, vecOut, numOut); + if (!m_delayResponse) + IOS_ResourceReply(cmd, (IOS_ERROR)result); + m_activeCmd = nullptr; + continue; + } + else + { + cemuLog_log(LogType::Force, "IPCSimpleService[{}]: Unsupported IPC cmdId {}", m_devicePath, (uint32)cmd->cmdId.value()); + cemu_assert_unimplemented(); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + IOS_DestroyMessageQueue(m_msgQueueId); + m_threadInitialized = false; + } + + void IPCSimpleService::SetTimerUpdate(uint32 milliseconds) + { + if(m_timerId != IOSInvalidTimerId) + IOS_DestroyTimer(m_timerId); + m_timerId = IOS_CreateTimer(milliseconds * 1000, milliseconds * 1000, m_msgQueueId, 1); + } + + IPCCommandBody* IPCSimpleService::ServiceCallDelayCurrentResponse() + { + cemu_assert_debug(m_activeCmd); + m_delayResponse = true; + return m_activeCmd; + } + + void IPCSimpleService::ServiceCallAsyncRespond(IPCCommandBody* response, uint32 r) + { + IOS_ResourceReply(response, (IOS_ERROR)r); + } + + /* IPCService */ void IPCService::Start() { if (m_isRunning.exchange(true)) @@ -83,4 +210,4 @@ namespace iosu m_threadInitialized = false; } }; -}; \ No newline at end of file +}; diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.h b/src/Cafe/IOSU/nn/iosu_nn_service.h index 7f06139f..d50a0794 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.h +++ b/src/Cafe/IOSU/nn/iosu_nn_service.h @@ -8,6 +8,71 @@ namespace iosu { namespace nn { + // a simple service interface which wraps handle management and Ioctlv/IoctlvAsync + class IPCSimpleService + { + public: + IPCSimpleService(std::string_view devicePath) : m_devicePath(devicePath) {}; + virtual ~IPCSimpleService() {}; + + virtual void StartService() {}; + virtual void StopService() {}; + + virtual std::string GetThreadName() = 0; + + virtual void* CreateClientObject() = 0; + virtual void DestroyClientObject(void* clientObject) = 0; + virtual uint32 ServiceCall(void* clientObject, uint32 requestId, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) = 0; + virtual void TimerUpdate() {}; + + IPCCommandBody* ServiceCallDelayCurrentResponse(); + static void ServiceCallAsyncRespond(IPCCommandBody* response, uint32 r); + + void Start(); + void Stop(); + + void SetTimerUpdate(uint32 milliseconds); + + private: + void ServiceThread(); + + IOSDevHandle GetFreeHandle() + { + while(m_clientObjects.find(m_nextHandle) != m_clientObjects.end() || m_nextHandle == 0) + { + m_nextHandle++; + m_nextHandle &= 0x7FFFFFFF; + } + IOSDevHandle newHandle = m_nextHandle; + m_nextHandle++; + m_nextHandle &= 0x7FFFFFFF; + return newHandle; + } + + void* GetClientObjectByHandle(IOSDevHandle handle) const + { + auto it = m_clientObjects.find(handle); + if(it == m_clientObjects.end()) + return nullptr; + return it->second; + } + + std::string m_devicePath; + std::thread m_serviceThread; + std::atomic_bool m_requestStop{false}; + std::atomic_bool m_isRunning{false}; + std::atomic_bool m_threadInitialized{ false }; + std::unordered_map m_clientObjects; + IOSDevHandle m_nextHandle{1}; + IOSTimerId m_timerId{IOSInvalidTimerId}; + + IPCCommandBody* m_activeCmd{nullptr}; + bool m_delayResponse{false}; + + IOSMsgQueueId m_msgQueueId; + SysAllocator _m_msgBuffer; + }; + struct IPCServiceRequest { uint32be ukn00; @@ -23,6 +88,7 @@ namespace iosu uint32be nnResultCode; }; + // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, ? class IPCService { public: @@ -60,5 +126,6 @@ namespace iosu IOSMsgQueueId m_msgQueueId; SysAllocator _m_msgBuffer; }; + }; }; \ No newline at end of file diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp index d32acb2a..7f9d1948 100644 --- a/src/Cafe/OS/RPL/rpl.cpp +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -38,20 +38,22 @@ VHeap rplLoaderHeap_workarea(nullptr, MEMORY_RPLLOADER_AREA_SIZE); PPCCodeHeap rplLoaderHeap_lowerAreaCodeMem2(nullptr, MEMORY_CODE_TRAMPOLINE_AREA_SIZE); PPCCodeHeap rplLoaderHeap_codeArea2(nullptr, MEMORY_CODEAREA_SIZE); -bool rplLoader_applicationHasMemoryControl = false; -uint32 rplLoader_maxCodeAddress = 0; // highest used code address - ChunkedFlatAllocator<64 * 1024> g_heapTrampolineArea; -std::vector rplDependencyList = std::vector(); +std::vector rplDependencyList; RPLModule* rplModuleList[256]; sint32 rplModuleCount = 0; -uint32 _currentTLSModuleIndex = 1; // value 0 is reserved - +bool rplLoader_applicationHasMemoryControl = false; +uint32 rplLoader_maxCodeAddress = 0; // highest used code address +uint32 rplLoader_currentTLSModuleIndex = 1; // value 0 is reserved +uint32 rplLoader_currentHandleCounter = 0x00001000; +sint16 rplLoader_currentTlsModuleIndex = 0x0001; +RPLModule* rplLoader_mainModule = nullptr; uint32 rplLoader_sdataAddr = MPTR_NULL; // r13 uint32 rplLoader_sdata2Addr = MPTR_NULL; // r2 +uint32 rplLoader_currentDataAllocatorAddr = 0x10000000; std::map g_map_callableExports; @@ -75,13 +77,6 @@ struct RPLRegionMappingTable void RPLLoader_UnloadModule(RPLModule* rpl); void RPLLoader_RemoveDependency(const char* name); -char _ansiToLower(char c) -{ - if (c >= 'A' && c <= 'Z') - c -= ('A' - 'a'); - return c; -} - uint8* RPLLoader_AllocateTrampolineCodeSpace(RPLModule* rplLoaderContext, sint32 size) { if (rplLoaderContext) @@ -109,8 +104,6 @@ MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment) return codeAddr; } -uint32 rpl3_currentDataAllocatorAddr = 0x10000000; - uint32 RPLLoader_AllocateDataSpace(RPLModule* rpl, uint32 size, uint32 alignment) { if (rplLoader_applicationHasMemoryControl) @@ -120,9 +113,9 @@ uint32 RPLLoader_AllocateDataSpace(RPLModule* rpl, uint32 size, uint32 alignment PPCCoreCallback(rpl->funcAlloc.value(), size, alignment, memPtr.GetPointer()); return (uint32)*(memPtr.GetPointer()); } - rpl3_currentDataAllocatorAddr = (rpl3_currentDataAllocatorAddr + alignment - 1)&~(alignment-1); - uint32 mem = rpl3_currentDataAllocatorAddr; - rpl3_currentDataAllocatorAddr += size; + rplLoader_currentDataAllocatorAddr = (rplLoader_currentDataAllocatorAddr + alignment - 1) & ~(alignment - 1); + uint32 mem = rplLoader_currentDataAllocatorAddr; + rplLoader_currentDataAllocatorAddr += size; return mem; } @@ -133,7 +126,7 @@ void RPLLoader_FreeData(RPLModule* rpl, void* ptr) uint32 RPLLoader_GetDataAllocatorAddr() { - return (rpl3_currentDataAllocatorAddr + 0xFFF)&(~0xFFF); + return (rplLoader_currentDataAllocatorAddr + 0xFFF) & (~0xFFF); } uint32 RPLLoader_GetMaxCodeOffset() @@ -730,7 +723,7 @@ uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU if (it != g_map_callableExports.end()) return it->second; // get HLE function index - sint32 functionIndex = PPCInterpreter_registerHLECall(ppcCallableExport); + sint32 functionIndex = PPCInterpreter_registerHLECall(ppcCallableExport, fmt::format("PPCCallback{:x}", (uintptr_t)ppcCallableExport)); MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); uint32 opcode = (1 << 26) | functionIndex; memory_write(codeAddr, opcode); @@ -1384,12 +1377,11 @@ bool RPLLoader_HandleRelocs(RPLModule* rplLoaderContext, std::span 0); - sint32 startIndex = inputLen - 1; + cemu_assert(!input.empty()); + size_t startIndex = input.size() - 1; while (startIndex > 0) { if (input[startIndex] == '/') @@ -1400,23 +1392,20 @@ void _RPLLoader_ExtractModuleNameFromPath(char* output, const char* input) startIndex--; } // cut off after '.' - sint32 endIndex = startIndex; - while (endIndex <= inputLen) + size_t endIndex = startIndex; + while (endIndex < input.size()) { if (input[endIndex] == '.') break; endIndex++; } - sint32 nameLen = endIndex - startIndex; + size_t nameLen = endIndex - startIndex; cemu_assert(nameLen != 0); - nameLen = std::min(nameLen, RPL_MODULE_NAME_LENGTH-1); - memcpy(output, input + startIndex, nameLen); + nameLen = std::min(nameLen, RPL_MODULE_NAME_LENGTH-1); + memcpy(output, input.data() + startIndex, nameLen); output[nameLen] = '\0'; // convert to lower case - for (sint32 i = 0; i < nameLen; i++) - { - output[i] = _ansiToLower(output[i]); - } + std::for_each(output, output + nameLen, [](char& c) {c = _ansiToLower(c);}); } void RPLLoader_InitState() @@ -1431,27 +1420,6 @@ void RPLLoader_InitState() RPLLoader_ResetState(); } -void RPLLoader_ResetState() -{ - // unload all RPL modules - while (rplModuleCount > 0) - RPLLoader_UnloadModule(rplModuleList[0]); - rplDependencyList.clear(); - // unload all remaining symbols - rplSymbolStorage_unloadAll(); - // free all code imports - g_heapTrampolineArea.releaseAll(); - list_mappedFunctionImports.clear(); - g_map_callableExports.clear(); - - rplLoader_applicationHasMemoryControl = false; - rplLoader_maxCodeAddress = 0; - rpl3_currentDataAllocatorAddr = 0x10000000; - _currentTLSModuleIndex = 1; - rplLoader_sdataAddr = MPTR_NULL; - rplLoader_sdata2Addr = MPTR_NULL; -} - void RPLLoader_BeginCemuhookCRC(RPLModule* rpl) { // calculate some values required for CRC @@ -1609,7 +1577,7 @@ void RPLLoader_InitModuleAllocator(RPLModule* rpl) } // map rpl into memory, but do not resolve relocs and imports yet -RPLModule* rpl_loadFromMem(uint8* rplData, sint32 size, char* name) +RPLModule* RPLLoader_LoadFromMemory(uint8* rplData, sint32 size, char* name) { char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); @@ -1698,7 +1666,7 @@ RPLModule* rpl_loadFromMem(uint8* rplData, sint32 size, char* name) return rpl; } -void RPLLoader_flushMemory(RPLModule* rpl) +void RPLLoader_FlushMemory(RPLModule* rpl) { // invalidate recompiler cache PPCRecompiler_invalidateRange(rpl->regionMappingBase_text.GetMPTR(), rpl->regionMappingBase_text.GetMPTR() + rpl->regionSize_text); @@ -1754,7 +1722,7 @@ void RPLLoader_LinkSingleModule(RPLModule* rplLoaderContext, bool resolveOnlyExp else RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 0); - RPLLoader_flushMemory(rplLoaderContext); + RPLLoader_FlushMemory(rplLoaderContext); } void RPLLoader_LoadSectionDebugSymbols(RPLModule* rplLoaderContext, rplSectionEntryNew_t* section, int symtabSectionIndex) @@ -1920,8 +1888,24 @@ uint32 RPLLoader_GetModuleEntrypoint(RPLModule* rplLoaderContext) return rplLoaderContext->entrypoint; } -uint32 rplLoader_currentHandleCounter = 0x00001000; -sint16 rplLoader_currentTlsModuleIndex = 0x0001; +// takes a module name without extension, returns true if the RPL module is a known Cafe OS module +bool RPLLoader_IsKnownCafeOSModule(std::string_view name) +{ + static std::unordered_set s_systemModules556 = { + "avm","camera","coreinit","dc","dmae","drmapp","erreula", + "gx2","h264","lzma920","mic","nfc","nio_prof","nlibcurl", + "nlibnss","nlibnss2","nn_ac","nn_acp","nn_act","nn_aoc","nn_boss", + "nn_ccr","nn_cmpt","nn_dlp","nn_ec","nn_fp","nn_hai","nn_hpad", + "nn_idbe","nn_ndm","nn_nets2","nn_nfp","nn_nim","nn_olv","nn_pdm", + "nn_save","nn_sl","nn_spm","nn_temp","nn_uds","nn_vctl","nsysccr", + "nsyshid","nsyskbd","nsysnet","nsysuhs","nsysuvd","ntag","padscore", + "proc_ui","sndcore2","snduser2","snd_core","snd_user","swkbd","sysapp", + "tcl","tve","uac","uac_rpl","usb_mic","uvc","uvd","vpad","vpadbase", + "zlib125"}; + std::string nameLower{name}; + std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), _ansiToLower); + return s_systemModules556.contains(nameLower); +} // increment reference counter for module void RPLLoader_AddDependency(const char* name) @@ -1947,18 +1931,18 @@ void RPLLoader_AddDependency(const char* name) { if (strcmp(moduleName, dep->modulename) == 0) { - // entry already exists, increment reference counter dep->referenceCount++; return; } } // add new entry - rplDependency_t* newDependency = new rplDependency_t(); + RPLDependency* newDependency = new RPLDependency(); strcpy(newDependency->modulename, moduleName); newDependency->referenceCount = 1; newDependency->coreinitHandle = rplLoader_currentHandleCounter; newDependency->tlsModuleIndex = rplLoader_currentTlsModuleIndex; - rplLoader_currentTlsModuleIndex++; + newDependency->isCafeOSModule = RPLLoader_IsKnownCafeOSModule(moduleName); + rplLoader_currentTlsModuleIndex++; // todo - delay handle and tls allocation until the module is actually loaded. It may not exist rplLoader_currentHandleCounter++; if (rplLoader_currentTlsModuleIndex == 0x7FFF) cemuLog_log(LogType::Force, "RPLLoader: Exhausted TLS module indices pool"); @@ -2006,6 +1990,18 @@ void RPLLoader_RemoveDependency(const char* name) } } +bool RPLLoader_HasDependency(std::string_view name) +{ + char moduleName[RPL_MODULE_NAME_LENGTH]; + _RPLLoader_ExtractModuleNameFromPath(moduleName, name); + for (const auto& dep : rplDependencyList) + { + if (strcmp(moduleName, dep->modulename) == 0) + return true; + } + return false; +} + // decrement reference counter for dependency by module handle void RPLLoader_RemoveDependency(uint32 handle) { @@ -2031,6 +2027,9 @@ uint32 RPLLoader_GetHandleByModuleName(const char* name) { if (strcmp(moduleName, dep->modulename) == 0) { + cemu_assert_debug(dep->loadAttempted); + if (!dep->isCafeOSModule && !dep->rplLoaderContext) + return RPL_INVALID_HANDLE; // module not found return dep->coreinitHandle; } } @@ -2063,21 +2062,21 @@ bool RPLLoader_GetTLSDataByTLSIndex(sint16 tlsModuleIndex, uint8** tlsData, sint return true; } -bool RPLLoader_LoadFromVirtualPath(rplDependency_t* dependency, char* filePath) +bool RPLLoader_LoadFromVirtualPath(RPLDependency* dependency, char* filePath) { uint32 rplSize = 0; uint8* rplData = fsc_extractFile(filePath, &rplSize); if (rplData) { cemuLog_logDebug(LogType::Force, "Loading: {}", filePath); - dependency->rplLoaderContext = rpl_loadFromMem(rplData, rplSize, filePath); + dependency->rplLoaderContext = RPLLoader_LoadFromMemory(rplData, rplSize, filePath); free(rplData); return true; } return false; } -void RPLLoader_LoadDependency(rplDependency_t* dependency) +void RPLLoader_LoadDependency(RPLDependency* dependency) { dependency->loadAttempted = true; // check if module is already loaded @@ -2085,11 +2084,9 @@ void RPLLoader_LoadDependency(rplDependency_t* dependency) { if(!boost::iequals(rplModuleList[i]->moduleName2, dependency->modulename)) continue; - // already loaded dependency->rplLoaderContext = rplModuleList[i]; return; } - // attempt to load rpl from various locations char filePath[RPL_MODULE_PATH_LENGTH]; // check if path is absolute if (dependency->filepath[0] == '/') @@ -2098,7 +2095,7 @@ void RPLLoader_LoadDependency(rplDependency_t* dependency) RPLLoader_LoadFromVirtualPath(dependency, filePath); return; } - // attempt to load rpl from internal folder + // attempt to load rpl from code directory of current title strcpy_s(filePath, "/internal/current_title/code/"); strcat_s(filePath, dependency->filepath); // except if it is blacklisted @@ -2120,7 +2117,8 @@ void RPLLoader_LoadDependency(rplDependency_t* dependency) if (fileData) { cemuLog_log(LogType::Force, "Loading RPL: /cafeLibs/{}", dependency->filepath); - dependency->rplLoaderContext = rpl_loadFromMem(fileData->data(), fileData->size(), dependency->filepath); + dependency->rplLoaderContext = RPLLoader_LoadFromMemory(fileData->data(), fileData->size(), + dependency->filepath); return; } } @@ -2169,8 +2167,6 @@ void RPLLoader_UpdateDependencies() RPLLoader_Link(); } -RPLModule* rplLoader_mainModule = nullptr; - void RPLLoader_SetMainModule(RPLModule* rplLoaderContext) { rplLoaderContext->entrypointCalled = true; @@ -2251,7 +2247,7 @@ uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const c { // find dependency from handle RPLModule* rplLoaderContext = nullptr; - rplDependency_t* dependency = nullptr; + RPLDependency* dependency = nullptr; for (auto& dep : rplDependencyList) { if (dep->coreinitHandle == moduleHandle) @@ -2380,3 +2376,24 @@ void RPLLoader_ReleaseCodeCaveMem(MEMPTR addr) { heapCodeCaveArea.free(addr.GetMPTR()); } + +void RPLLoader_ResetState() +{ + // unload all RPL modules + while (rplModuleCount > 0) + RPLLoader_UnloadModule(rplModuleList[0]); + rplDependencyList.clear(); + // unload all remaining symbols + rplSymbolStorage_unloadAll(); + // free all code imports + g_heapTrampolineArea.releaseAll(); + list_mappedFunctionImports.clear(); + g_map_callableExports.clear(); + rplLoader_applicationHasMemoryControl = false; + rplLoader_maxCodeAddress = 0; + rplLoader_currentDataAllocatorAddr = 0x10000000; + rplLoader_currentTLSModuleIndex = 1; + rplLoader_sdataAddr = MPTR_NULL; + rplLoader_sdata2Addr = MPTR_NULL; + rplLoader_mainModule = nullptr; +} diff --git a/src/Cafe/OS/RPL/rpl.h b/src/Cafe/OS/RPL/rpl.h index ced7e83c..1075ee58 100644 --- a/src/Cafe/OS/RPL/rpl.h +++ b/src/Cafe/OS/RPL/rpl.h @@ -2,7 +2,7 @@ struct RPLModule; -#define RPL_INVALID_HANDLE (0xFFFFFFFF) +#define RPL_INVALID_HANDLE 0xFFFFFFFF void RPLLoader_InitState(); void RPLLoader_ResetState(); @@ -14,7 +14,7 @@ MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment); uint32 RPLLoader_GetMaxCodeOffset(); uint32 RPLLoader_GetDataAllocatorAddr(); -RPLModule* rpl_loadFromMem(uint8* rplData, sint32 size, char* name); +RPLModule* RPLLoader_LoadFromMemory(uint8* rplData, sint32 size, char* name); uint32 rpl_mapHLEImport(RPLModule* rplLoaderContext, const char* rplName, const char* funcName, bool functionMustExist); void RPLLoader_Link(); @@ -29,6 +29,7 @@ void RPLLoader_NotifyControlPassedToApplication(); void RPLLoader_AddDependency(const char* name); void RPLLoader_RemoveDependency(uint32 handle); +bool RPLLoader_HasDependency(std::string_view name); void RPLLoader_UpdateDependencies(); uint32 RPLLoader_GetHandleByModuleName(const char* name); diff --git a/src/Cafe/OS/RPL/rpl_structs.h b/src/Cafe/OS/RPL/rpl_structs.h index 71be960c..998ec8d7 100644 --- a/src/Cafe/OS/RPL/rpl_structs.h +++ b/src/Cafe/OS/RPL/rpl_structs.h @@ -225,17 +225,17 @@ struct RPLModule }; -typedef struct +struct RPLDependency { char modulename[RPL_MODULE_NAME_LENGTH]; char filepath[RPL_MODULE_PATH_LENGTH]; bool loadAttempted; - //bool isHLEModule; // determined to be a HLE module - RPLModule* rplLoaderContext; // context of loaded module + bool isCafeOSModule; // name is a known Cafe OS RPL + RPLModule* rplLoaderContext; // context of loaded module, can be nullptr for HLE COS modules sint32 referenceCount; uint32 coreinitHandle; // fake handle for coreinit sint16 tlsModuleIndex; // tls module index assigned to this dependency -}rplDependency_t; +}; RPLModule* RPLLoader_FindModuleByCodeAddr(uint32 addr); RPLModule* RPLLoader_FindModuleByDataAddr(uint32 addr); diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp index 7e11ea13..5aedd197 100644 --- a/src/Cafe/OS/common/OSCommon.cpp +++ b/src/Cafe/OS/common/OSCommon.cpp @@ -85,6 +85,7 @@ void osLib_addFunctionInternal(const char* libraryName, const char* functionName uint32 funcHashA, funcHashB; osLib_generateHashFromName(libraryName, &libHashA, &libHashB); osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); + std::string hleName = fmt::format("{}.{}", libraryName, functionName); // if entry already exists, update it for (auto& it : *s_osFunctionTable) { @@ -93,11 +94,11 @@ void osLib_addFunctionInternal(const char* libraryName, const char* functionName it.funcHashA == funcHashA && it.funcHashB == funcHashB) { - it.hleFunc = PPCInterpreter_registerHLECall(osFunction); + it.hleFunc = PPCInterpreter_registerHLECall(osFunction, hleName); return; } } - s_osFunctionTable->emplace_back(libHashA, libHashB, funcHashA, funcHashB, fmt::format("{}.{}", libraryName, functionName), PPCInterpreter_registerHLECall(osFunction)); + s_osFunctionTable->emplace_back(libHashA, libHashB, funcHashA, funcHashB, hleName, PPCInterpreter_registerHLECall(osFunction, hleName)); } extern "C" DLLEXPORT void osLib_registerHLEFunction(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t * hCPU)) diff --git a/src/Cafe/OS/common/OSUtil.h b/src/Cafe/OS/common/OSUtil.h index 9bf8480b..6801f6af 100644 --- a/src/Cafe/OS/common/OSUtil.h +++ b/src/Cafe/OS/common/OSUtil.h @@ -65,7 +65,7 @@ public: } else if constexpr (std::is_floating_point_v) { - v = (T)ppcInterpreterCurrentInstance->fpr[1 + fprIndex].fpr; + v = (T)hCPU->fpr[1 + fprIndex].fpr; fprIndex++; } else diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index e8e4ce1f..660f874f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -35,7 +35,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" -coreinitData_t* gCoreinitData = NULL; +CoreinitSharedData* gCoreinitData = NULL; sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) { @@ -204,7 +204,7 @@ namespace coreinit { sint32 OSGetCoreId() { - return PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + return PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); } uint32 OSGetCoreCount() @@ -239,7 +239,7 @@ namespace coreinit uint32 OSGetStackPointer() { - return ppcInterpreterCurrentInstance->gpr[1]; + return PPCInterpreter_getCurrentInstance()->gpr[1]; } void coreinitExport_ENVGetEnvironmentVariable(PPCInterpreter_t* hCPU) @@ -323,8 +323,8 @@ void coreinit_load() coreinit::InitializeSysHeap(); // allocate coreinit global data - gCoreinitData = (coreinitData_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(coreinitData_t), 32)); - memset(gCoreinitData, 0x00, sizeof(coreinitData_t)); + gCoreinitData = (CoreinitSharedData*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(CoreinitSharedData), 32)); + memset(gCoreinitData, 0x00, sizeof(CoreinitSharedData)); // coreinit weak links osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeap)); diff --git a/src/Cafe/OS/libs/coreinit/coreinit.h b/src/Cafe/OS/libs/coreinit/coreinit.h index 046ffe1e..74aab9b2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.h +++ b/src/Cafe/OS/libs/coreinit/coreinit.h @@ -16,8 +16,7 @@ void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, void coreinit_load(); // coreinit shared memory - -typedef struct +struct CoreinitSharedData { MEMPTR MEMAllocFromDefaultHeap; MEMPTR MEMAllocFromDefaultHeapEx; @@ -26,11 +25,9 @@ typedef struct MPTR __cpp_exception_init_ptr; MPTR __cpp_exception_cleanup_ptr; MPTR __stdio_cleanup; -}coreinitData_t; +}; -extern coreinitData_t* gCoreinitData; - -#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" +extern CoreinitSharedData* gCoreinitData; // coreinit init void coreinit_start(PPCInterpreter_t* hCPU); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp index c8b05124..546501b6 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp @@ -86,8 +86,7 @@ namespace coreinit } // search for loaded modules with matching name uint32 rplHandle = RPLLoader_GetHandleByModuleName(libName); - - if (rplHandle == RPL_INVALID_HANDLE) + if (rplHandle == RPL_INVALID_HANDLE && !RPLLoader_HasDependency(libName)) { RPLLoader_AddDependency(libName); RPLLoader_UpdateDependencies(); @@ -100,7 +99,10 @@ namespace coreinit else *moduleHandleOut = rplHandle; if (rplHandle == RPL_INVALID_HANDLE) + { + cemuLog_logDebug(LogType::Force, "OSDynLoad_Acquire() failed to load module '{}'", libName); return 0xFFFCFFE9; // module not found + } return 0; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 26636eae..1e6eb92b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -42,17 +42,16 @@ bool strcpy_whole(char* dst, size_t dstLength, const char* src) namespace coreinit { - std::mutex sFSClientLock; - std::recursive_mutex sFSGlobalMutex; + SysAllocator s_fsGlobalMutex; inline void FSLockMutex() { - sFSGlobalMutex.lock(); + OSLockMutex(&s_fsGlobalMutex); } inline void FSUnlockMutex() { - sFSGlobalMutex.unlock(); + OSUnlockMutex(&s_fsGlobalMutex); } void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody); @@ -251,7 +250,7 @@ namespace coreinit fsCmdQueueBE->dequeueHandlerFuncMPTR = _swapEndianU32(dequeueHandlerFuncMPTR); fsCmdQueueBE->numCommandsInFlight = 0; fsCmdQueueBE->numMaxCommandsInFlight = numMaxCommandsInFlight; - coreinit::OSInitMutexEx(&fsCmdQueueBE->mutex, nullptr); + coreinit::OSFastMutex_Init(&fsCmdQueueBE->fastMutex, nullptr); fsCmdQueueBE->firstMPTR = _swapEndianU32(0); fsCmdQueueBE->lastMPTR = _swapEndianU32(0); } @@ -672,12 +671,12 @@ namespace coreinit _debugVerifyCommand("FSCmdSubmitResult", fsCmdBlockBody); FSClientBody_t* fsClientBody = fsCmdBlockBody->fsClientBody.GetPtr(); - sFSClientLock.lock(); // OSFastMutex_Lock(&fsClientBody->fsCmdQueue.mutex) + OSFastMutex_Lock(&fsClientBody->fsCmdQueue.fastMutex); fsCmdBlockBody->cancelState &= ~(1 << 0); // clear cancel bit if (fsClientBody->currentCmdBlockBody.GetPtr() == fsCmdBlockBody) fsClientBody->currentCmdBlockBody = nullptr; fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A24); - sFSClientLock.unlock(); + OSFastMutex_Unlock(&fsClientBody->fsCmdQueue.fastMutex); // send result via msg queue or callback cemu_assert_debug(!fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue != !fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userCallback); // either must be set fsCmdBlockBody->ukn09EA = 0; @@ -692,7 +691,7 @@ namespace coreinit while (OSSendMessage(ioMsgQueue, &fsCmdBlockBody->asyncResult.msgUnion.osMsg, 0) == 0) { cemuLog_log(LogType::Force, "FS driver: Failed to add message to result queue. Retrying..."); - if (ppcInterpreterCurrentInstance) + if (PPCInterpreter_getCurrentInstance()) PPCCore_switchToScheduler(); else std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -913,8 +912,8 @@ namespace coreinit sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, fileHandle, errHandling, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } @@ -942,8 +941,8 @@ namespace coreinit sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* fileHandle, uint32 errHandling) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, fileHandle, errHandling, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } @@ -976,8 +975,8 @@ namespace coreinit sint32 FSCloseFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSCloseFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSCloseFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } @@ -1010,8 +1009,8 @@ namespace coreinit sint32 FSFlushFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSFlushFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSFlushFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } @@ -1091,7 +1090,7 @@ namespace coreinit sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileAsync(fsClient, fsCmdBlock, dst, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1106,7 +1105,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 asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, 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); } @@ -1184,7 +1183,7 @@ namespace coreinit sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileAsync(fsClient, fsCmdBlock, src, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1197,7 +1196,7 @@ namespace coreinit sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, 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); } @@ -1229,7 +1228,7 @@ namespace coreinit { // used by games: Mario Kart 8 StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSSetPosFileAsync(fsClient, fsCmdBlock, fileHandle, filePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1260,7 +1259,7 @@ namespace coreinit sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetPosFileAsync(fsClient, fsCmdBlock, fileHandle, returnedFilePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1308,8 +1307,8 @@ namespace coreinit sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSOpenDirAsync(fsClient, fsCmdBlock, path, dirHandleOut, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSOpenDirAsync(fsClient, fsCmdBlock, path, dirHandleOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1338,8 +1337,8 @@ namespace coreinit sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSReadDirAsync(fsClient, fsCmdBlock, dirHandle, dirEntryOut, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSReadDirAsync(fsClient, fsCmdBlock, dirHandle, dirEntryOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1368,8 +1367,8 @@ namespace coreinit sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSCloseDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSCloseDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1401,8 +1400,8 @@ namespace coreinit sint32 FSRewindDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSRewindDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSRewindDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1433,10 +1432,10 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask) + sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, size, count, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1468,7 +1467,7 @@ namespace coreinit sint32 FSTruncateFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSTruncateFileAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1532,7 +1531,7 @@ namespace coreinit sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRenameAsync(fsClient, fsCmdBlock, srcPath, dstPath, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1583,8 +1582,8 @@ namespace coreinit sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSRemoveAsync(fsClient, fsCmdBlock, filePath, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSRemoveAsync(fsClient, fsCmdBlock, filePath, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1635,8 +1634,8 @@ namespace coreinit sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSMakeDirAsync(fsClient, fsCmdBlock, path, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSMakeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1684,8 +1683,8 @@ namespace coreinit sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSChangeDirAsync(fsClient, fsCmdBlock, path, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSChangeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1719,8 +1718,8 @@ namespace coreinit sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSGetCwdAsync(fsClient, fsCmdBlock, dirPathOut, dirPathMaxLen, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSGetCwdAsync(fsClient, fsCmdBlock, dirPathOut, dirPathMaxLen, errorMask, &asyncParams); auto r = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); return r; } @@ -1764,8 +1763,8 @@ namespace coreinit sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSFlushQuotaAsync(fsClient, fsCmdBlock, path, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSFlushQuotaAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1822,8 +1821,8 @@ namespace coreinit sint32 FSGetStat(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSGetStatAsync(fsClient, fsCmdBlock, path, statOut, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSGetStatAsync(fsClient, fsCmdBlock, path, statOut, errorMask, &asyncParams); sint32 ret = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); return ret; } @@ -1859,8 +1858,8 @@ namespace coreinit sint32 FSGetStatFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSGetStatFileAsync(fsClient, fsCmdBlock, fileHandle, statOut, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSGetStatFileAsync(fsClient, fsCmdBlock, fileHandle, statOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1874,8 +1873,8 @@ namespace coreinit sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSGetFreeSpaceSizeAsync(fsClient, fsCmdBlock, path, returnedFreeSize, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSGetFreeSpaceSizeAsync(fsClient, fsCmdBlock, path, returnedFreeSize, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -1909,8 +1908,8 @@ namespace coreinit sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask) { StackAllocator asyncParams; - __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); - sint32 fsAsyncRet = FSIsEofAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams); + __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); + sint32 fsAsyncRet = FSIsEofAsync(fsClient, fsCmdBlock, fileHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } @@ -2640,6 +2639,8 @@ namespace coreinit void InitializeFS() { + OSInitMutex(&s_fsGlobalMutex); + cafeExportRegister("coreinit", FSInit, LogType::CoreinitFile); cafeExportRegister("coreinit", FSShutdown, LogType::CoreinitFile); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.h b/src/Cafe/OS/libs/coreinit/coreinit_FS.h index 0355c9aa..2a57f7da 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.h @@ -42,7 +42,7 @@ namespace coreinit /* +0x00 */ MPTR firstMPTR; /* +0x04 */ MPTR lastMPTR; - /* +0x08 */ OSMutex mutex; + /* +0x08 */ OSFastMutex fastMutex; /* +0x34 */ MPTR dequeueHandlerFuncMPTR; /* +0x38 */ uint32be numCommandsInFlight; /* +0x3C */ uint32 numMaxCommandsInFlight; @@ -50,6 +50,8 @@ namespace coreinit }; DEFINE_ENUM_FLAG_OPERATORS(FSCmdQueue::QUEUE_FLAG); + static_assert(sizeof(FSCmdQueue) == 0x44); + #define FS_CLIENT_BUFFER_SIZE (5888) #define FS_CMD_BLOCK_SIZE (2688) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp index ef847f26..be3cb300 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp @@ -355,23 +355,6 @@ namespace coreinit IOS_ERROR _IPCDriver_SetupCmd_IOSIoctlv(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec) { IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody; - // verify input and output vectors - IPCIoctlVector* vecIn = vec; - IPCIoctlVector* vecOut = vec + numIn; - for (uint32 i = 0; i < numIn; i++) - { - if (vecIn[i].baseVirt == nullptr && vecIn[i].size != 0) - return IOS_ERROR_INVALID_ARG; - vecIn[i].basePhys = vecIn[i].baseVirt; - vecIn[i].baseVirt = nullptr; - } - for (uint32 i = 0; i < numOut; i++) - { - if (vecOut[i].baseVirt == nullptr && vecOut[i].size != 0) - return IOS_ERROR_INVALID_ARG; - vecOut[i].basePhys = vecOut[i].baseVirt; - vecOut[i].baseVirt = nullptr; - } // set args cmdBody.ppcVirt0 = MEMPTR(vec).GetMPTR(); cmdBody.args[0] = requestId; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp index 51a3f542..72f6ac11 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp @@ -142,10 +142,11 @@ void CafeInit() } } // setup UGQR - ppcInterpreterCurrentInstance->spr.UGQR[0 + 2] = 0x00040004; - ppcInterpreterCurrentInstance->spr.UGQR[0 + 3] = 0x00050005; - ppcInterpreterCurrentInstance->spr.UGQR[0 + 4] = 0x00060006; - ppcInterpreterCurrentInstance->spr.UGQR[0 + 5] = 0x00070007; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + hCPU->spr.UGQR[0 + 2] = 0x00040004; + hCPU->spr.UGQR[0 + 3] = 0x00050005; + hCPU->spr.UGQR[0 + 4] = 0x00060006; + hCPU->spr.UGQR[0 + 5] = 0x00070007; coreinit::InitForegroundBucket(); coreinit::InitSysHeap(); } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp index a784e593..14d7a645 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -71,7 +71,6 @@ sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSetti void coreinitExport_MCP_GetSysProdSettings(PPCInterpreter_t* hCPU) { - cemuLog_logDebug(LogType::Force, "MCP_GetSysProdSettings(0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); sint32 result = MCP_GetSysProdSettings(hCPU->gpr[3], (SysProdSettings*)memory_getPointerFromVirtualOffset(hCPU->gpr[4])); osLib_returnFromFunction(hCPU, result); } @@ -546,6 +545,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // get parental online control for online features // note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used + // a non-zero value means network access is restricted through parental access. 0 means allowed // account in slot 1 if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed @@ -561,7 +561,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // miiverse restrictions if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) - memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only?, 2 -> no access?) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only, 2 -> no access) } else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0) { diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index 05660c71..2d7468cf 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -195,7 +195,28 @@ namespace coreinit else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X'))) { formatStr += 3; - // number (64bit) + // double (64bit) + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + if (integerParamIndex & 1) + integerParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex)); + integerParamIndex += 2; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && formatStr[2] == 'd')) + { + formatStr += 3; + // signed integer (64bit) strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; @@ -235,7 +256,7 @@ namespace coreinit sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr) { - sint32 r = ppcSprintf(formatStr, outputStr, maxLength, ppcInterpreterCurrentInstance, 3); + sint32 r = ppcSprintf(formatStr, outputStr, maxLength, PPCInterpreter_getCurrentInstance(), 3); return r; } @@ -303,7 +324,7 @@ namespace coreinit void OSReport(const char* format) { char buffer[1024 * 2]; - sint32 len = ppcSprintf(format, buffer, sizeof(buffer), ppcInterpreterCurrentInstance, 1); + sint32 len = ppcSprintf(format, buffer, sizeof(buffer), PPCInterpreter_getCurrentInstance(), 1); WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len); } @@ -316,7 +337,7 @@ namespace coreinit { char buffer[1024 * 2]; int prefixLen = sprintf(buffer, "[COSWarn-%d] ", moduleId); - sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, ppcInterpreterCurrentInstance, 2); + sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 2); WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); } @@ -324,7 +345,7 @@ namespace coreinit { char buffer[1024 * 2]; int prefixLen = sprintf(buffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); - sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, ppcInterpreterCurrentInstance, 4); + sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 4); WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp index 92c90a9d..9e5de19e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp @@ -621,49 +621,49 @@ namespace coreinit OSInitEvent(g_rendezvousEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); // OSEvent - cafeExportRegister("coreinit", OSInitEvent, LogType::ThreadSync); - cafeExportRegister("coreinit", OSInitEventEx, LogType::ThreadSync); - cafeExportRegister("coreinit", OSResetEvent, LogType::ThreadSync); - cafeExportRegister("coreinit", OSWaitEvent, LogType::ThreadSync); - cafeExportRegister("coreinit", OSWaitEventWithTimeout, LogType::ThreadSync); - cafeExportRegister("coreinit", OSSignalEvent, LogType::ThreadSync); - cafeExportRegister("coreinit", OSSignalEventAll, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitEvent, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSInitEventEx, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSResetEvent, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSWaitEvent, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSWaitEventWithTimeout, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSSignalEvent, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSSignalEventAll, LogType::CoreinitThreadSync); // OSRendezvous - cafeExportRegister("coreinit", OSInitRendezvous, LogType::ThreadSync); - cafeExportRegister("coreinit", OSWaitRendezvous, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitRendezvous, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSWaitRendezvous, LogType::CoreinitThreadSync); // OSMutex - cafeExportRegister("coreinit", OSInitMutex, LogType::ThreadSync); - cafeExportRegister("coreinit", OSInitMutexEx, LogType::ThreadSync); - cafeExportRegister("coreinit", OSLockMutex, LogType::ThreadSync); - cafeExportRegister("coreinit", OSTryLockMutex, LogType::ThreadSync); - cafeExportRegister("coreinit", OSUnlockMutex, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitMutex, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSInitMutexEx, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSLockMutex, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSTryLockMutex, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSUnlockMutex, LogType::CoreinitThreadSync); // OSCond - cafeExportRegister("coreinit", OSInitCond, LogType::ThreadSync); - cafeExportRegister("coreinit", OSInitCondEx, LogType::ThreadSync); - cafeExportRegister("coreinit", OSSignalCond, LogType::ThreadSync); - cafeExportRegister("coreinit", OSWaitCond, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitCond, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSInitCondEx, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSSignalCond, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSWaitCond, LogType::CoreinitThreadSync); // OSSemaphore - cafeExportRegister("coreinit", OSInitSemaphore, LogType::ThreadSync); - cafeExportRegister("coreinit", OSInitSemaphoreEx, LogType::ThreadSync); - cafeExportRegister("coreinit", OSWaitSemaphore, LogType::ThreadSync); - cafeExportRegister("coreinit", OSTryWaitSemaphore, LogType::ThreadSync); - cafeExportRegister("coreinit", OSSignalSemaphore, LogType::ThreadSync); - cafeExportRegister("coreinit", OSGetSemaphoreCount, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitSemaphore, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSInitSemaphoreEx, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSWaitSemaphore, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSTryWaitSemaphore, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSSignalSemaphore, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSGetSemaphoreCount, LogType::CoreinitThreadSync); // OSFastMutex - cafeExportRegister("coreinit", OSFastMutex_Init, LogType::ThreadSync); - cafeExportRegister("coreinit", OSFastMutex_Lock, LogType::ThreadSync); - cafeExportRegister("coreinit", OSFastMutex_TryLock, LogType::ThreadSync); - cafeExportRegister("coreinit", OSFastMutex_Unlock, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastMutex_Init, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSFastMutex_Lock, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSFastMutex_TryLock, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSFastMutex_Unlock, LogType::CoreinitThreadSync); // OSFastCond - cafeExportRegister("coreinit", OSFastCond_Init, LogType::ThreadSync); - cafeExportRegister("coreinit", OSFastCond_Wait, LogType::ThreadSync); - cafeExportRegister("coreinit", OSFastCond_Signal, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastCond_Init, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSFastCond_Wait, LogType::CoreinitThreadSync); + cafeExportRegister("coreinit", OSFastCond_Signal, LogType::CoreinitThreadSync); } }; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 59bd034e..3701a4d7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -5,6 +5,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" @@ -198,7 +199,7 @@ namespace coreinit void threadEntry(PPCInterpreter_t* hCPU) { - OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(hCPU); + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); uint32 r3 = hCPU->gpr[3]; uint32 r4 = hCPU->gpr[4]; uint32 lr = hCPU->spr.LR; @@ -305,7 +306,7 @@ namespace coreinit affinityMask = attr & 0x7; // if no core is selected -> set current one if (affinityMask == 0) - affinityMask |= (1 << PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance)); + 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); @@ -325,7 +326,7 @@ namespace coreinit { __OSLockScheduler(); - cemu_assert_debug(ppcInterpreterCurrentInstance == nullptr || OSGetCurrentThread() != thread); // called on self, what should this function do? + cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr || OSGetCurrentThread() != thread); // called on self, what should this function do? if (thread->state != OSThread_t::THREAD_STATE::STATE_NONE && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) { @@ -368,39 +369,38 @@ namespace coreinit { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = exitValue; - OSThread_t* threadBE = coreinitThread_getCurrentThreadDepr(hCPU); - MPTR t = memory_getVirtualOffsetFromPointer(threadBE); + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); // thread cleanup callback - if (!threadBE->cleanupCallback2.IsNull()) + if (!currentThread->cleanupCallback2.IsNull()) { - threadBE->stateFlags = _swapEndianU32(_swapEndianU32(threadBE->stateFlags) | 0x00000001); - PPCCoreCallback(threadBE->cleanupCallback2.GetMPTR(), threadBE, _swapEndianU32(threadBE->stackEnd)); + currentThread->stateFlags = _swapEndianU32(_swapEndianU32(currentThread->stateFlags) | 0x00000001); + PPCCoreCallback(currentThread->cleanupCallback2.GetMPTR(), currentThread, _swapEndianU32(currentThread->stackEnd)); } // cpp exception cleanup - if (gCoreinitData->__cpp_exception_cleanup_ptr != 0 && threadBE->crt.eh_globals != nullptr) + if (gCoreinitData->__cpp_exception_cleanup_ptr != 0 && currentThread->crt.eh_globals != nullptr) { - PPCCoreCallback(_swapEndianU32(gCoreinitData->__cpp_exception_cleanup_ptr), &threadBE->crt.eh_globals); - threadBE->crt.eh_globals = nullptr; + PPCCoreCallback(_swapEndianU32(gCoreinitData->__cpp_exception_cleanup_ptr), ¤tThread->crt.eh_globals); + currentThread->crt.eh_globals = nullptr; } // set exit code - threadBE->exitValue = exitValue; + currentThread->exitValue = exitValue; __OSLockScheduler(); // release held synchronization primitives - if (!threadBE->mutexQueue.isEmpty()) + if (!currentThread->mutexQueue.isEmpty()) { cemuLog_log(LogType::Force, "OSExitThread: Thread is holding mutexes"); while (true) { - OSMutex* mutex = threadBE->mutexQueue.getFirst(); + OSMutex* mutex = currentThread->mutexQueue.getFirst(); if (!mutex) break; - if (mutex->owner != threadBE) + if (mutex->owner != currentThread) { cemuLog_log(LogType::Force, "OSExitThread: Thread is holding mutex which it doesn't own"); - threadBE->mutexQueue.removeMutex(mutex); + currentThread->mutexQueue.removeMutex(mutex); continue; } coreinit::OSUnlockMutexInternal(mutex); @@ -409,22 +409,22 @@ namespace coreinit // todo - release all fast mutexes // handle join queue - if (!threadBE->joinQueue.isEmpty()) - threadBE->joinQueue.wakeupEntireWaitQueue(false); + if (!currentThread->joinQueue.isEmpty()) + currentThread->joinQueue.wakeupEntireWaitQueue(false); - if ((threadBE->attr & 8) != 0) + if ((currentThread->attr & 8) != 0) { // deactivate thread since it is detached - threadBE->state = OSThread_t::THREAD_STATE::STATE_NONE; - coreinit::__OSDeactivateThread(threadBE); + currentThread->state = OSThread_t::THREAD_STATE::STATE_NONE; + coreinit::__OSDeactivateThread(currentThread); // queue call to thread deallocator if set - if (!threadBE->deallocatorFunc.IsNull()) - __OSQueueThreadDeallocation(threadBE); + if (!currentThread->deallocatorFunc.IsNull()) + __OSQueueThreadDeallocation(currentThread); } else { // non-detached threads remain active - threadBE->state = OSThread_t::THREAD_STATE::STATE_MORIBUND; + currentThread->state = OSThread_t::THREAD_STATE::STATE_MORIBUND; } PPCCore_switchToSchedulerWithLock(); } @@ -607,7 +607,7 @@ namespace coreinit // todo - only set this once? thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); // reschedule if thread has higher priority - if (ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) PPCCore_switchToSchedulerWithLock(); } return previousSuspendCount; @@ -930,17 +930,17 @@ namespace coreinit thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag // update total cycles - uint64 remainingCycles = std::min((uint64)ppcInterpreterCurrentInstance->remainingCycles, (uint64)thread->quantumTicks); + uint64 remainingCycles = std::min((uint64)hCPU->remainingCycles, (uint64)thread->quantumTicks); uint64 executedCycles = thread->quantumTicks - remainingCycles; - if (executedCycles < ppcInterpreterCurrentInstance->skippedCycles) + if (executedCycles < hCPU->skippedCycles) executedCycles = 0; else - executedCycles -= ppcInterpreterCurrentInstance->skippedCycles; + executedCycles -= hCPU->skippedCycles; thread->totalCycles += executedCycles; // store context and set current thread to null __OSThreadStoreContext(hCPU, thread); OSSetCurrentThread(OSGetCoreId(), nullptr); - ppcInterpreterCurrentInstance = nullptr; + PPCInterpreter_setCurrentInstance(nullptr); } void __OSLoadThread(OSThread_t* thread, PPCInterpreter_t* hCPU, uint32 coreIndex) @@ -951,7 +951,7 @@ namespace coreinit hCPU->reservedMemValue = 0; hCPU->spr.UPIR = coreIndex; hCPU->coreInterruptMask = 1; - ppcInterpreterCurrentInstance = hCPU; + PPCInterpreter_setCurrentInstance(hCPU); OSSetCurrentThread(OSGetCoreId(), thread); __OSThreadLoadContext(hCPU, thread); thread->context.upir = coreIndex; @@ -1076,7 +1076,7 @@ namespace coreinit // store context of current thread __OSStoreThread(OSGetCurrentThread(), &hostThread->ppcInstance); - cemu_assert_debug(ppcInterpreterCurrentInstance == nullptr); + cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr); if (!sSchedulerActive.load(std::memory_order::relaxed)) { @@ -1154,6 +1154,18 @@ namespace coreinit } } +#if BOOST_OS_LINUX + #include + #include + + std::vector g_schedulerThreadIds; + + std::vector& OSGetSchedulerThreadIds() + { + return g_schedulerThreadIds; + } +#endif + void OSSchedulerCoreEmulationThread(void* _assignedCoreIndex) { SetThreadName(fmt::format("OSSchedulerThread[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); @@ -1161,11 +1173,24 @@ namespace coreinit #if defined(ARCH_X86_64) _mm_setcsr(_mm_getcsr() | 0x8000); // flush denormals to zero #endif + +#if BOOST_OS_LINUX + if (g_gdbstub) + { + // need to allow the GDBStub to attach to our thread + prctl(PR_SET_DUMPABLE, (unsigned long)1); + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); + } + + pid_t tid = gettid(); + g_schedulerThreadIds.emplace_back(tid); +#endif + t_schedulerFiber = Fiber::PrepareCurrentThread(); - + // create scheduler idle fiber and switch to it g_idleLoopFiber[t_assignedCoreIndex] = new Fiber(__OSThreadCoreIdle, nullptr, nullptr); - cemu_assert_debug(ppcInterpreterCurrentInstance == nullptr); + cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr); __OSLockScheduler(); Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); // returned from scheduler loop, exit thread @@ -1212,6 +1237,9 @@ namespace coreinit threadItr.join(); sSchedulerThreads.clear(); g_schedulerThreadHandles.clear(); +#if BOOST_OS_LINUX + g_schedulerThreadIds.clear(); +#endif // clean up all fibers for (auto& it : g_idleLoopFiber) { @@ -1401,11 +1429,6 @@ void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count) __OSUnlockScheduler(); } -MPTR coreinitThread_getCurrentThreadMPTRDepr(PPCInterpreter_t* hCPU) -{ - return memory_getVirtualOffsetFromPointer(coreinit::__currentCoreThread[PPCInterpreter_getCoreIndex(hCPU)]); -} - 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 e2f5bef2..e619d5b6 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -486,8 +486,8 @@ struct OSThread_t /* +0x668 */ MPTR tlsBlocksMPTR; /* +0x66C */ MEMPTR waitingForFastMutex; - /* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; // link or queue? - /* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; // link or queue? + /* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; + /* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; /* +0x680 */ uint32 padding680[28 / 4]; }; @@ -615,7 +615,6 @@ namespace coreinit void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count = 1); void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count = 1); -MPTR coreinitThread_getCurrentThreadMPTRDepr(PPCInterpreter_t* hCPU); OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU); extern MPTR activeThread[256]; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp index 38302d5a..68cb22b3 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp @@ -139,7 +139,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) shouldReschedule = true; } if (shouldReschedule) @@ -159,7 +159,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) shouldReschedule = true; } if (shouldReschedule) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp index 465439ba..5a75b406 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp @@ -28,16 +28,6 @@ namespace coreinit osLib_returnFromFunction64(hCPU, osTime); } - uint64 coreinit_getTimeBase_dummy() - { - return __rdtsc(); - } - - void export_OSGetSystemTimeDummy(PPCInterpreter_t* hCPU) - { - osLib_returnFromFunction64(hCPU, coreinit_getTimeBase_dummy()); - } - void export_OSGetSystemTime(PPCInterpreter_t* hCPU) { osLib_returnFromFunction64(hCPU, coreinit_getTimerTick()); @@ -371,14 +361,13 @@ namespace coreinit void InitializeTimeAndCalendar() { osLib_addFunction("coreinit", "OSGetTime", export_OSGetTime); - osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTimeDummy); + osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime); osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick); osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick); cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder); cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder); - osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime); //timeTest(); } diff --git a/src/Cafe/OS/libs/gx2/GX2.cpp b/src/Cafe/OS/libs/gx2/GX2.cpp index 5a1f5520..82aef164 100644 --- a/src/Cafe/OS/libs/gx2/GX2.cpp +++ b/src/Cafe/OS/libs/gx2/GX2.cpp @@ -4,8 +4,8 @@ #include "GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/CafeSystem.h" - #include "Cafe/HW/Latte/Core/LattePM4.h" #include "GX2_Command.h" @@ -68,7 +68,7 @@ void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU) // Orochi Warriors seems to call GX2SwapScanBuffers on arbitrary threads/cores. The PM4 commands should go through to the GPU as long as there is no active display list and no other core is submitting commands simultaneously // right now, we work around this by avoiding the infinite loop below (request counter incremented, but PM4 not sent) - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = coreinit::OSGetCoreId(); if (GX2::sGX2MainCoreIndex == coreIndex) LatteGPUState.sharedArea->flipRequestCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) + 1); @@ -332,7 +332,7 @@ uint64 Latte_GetTime() void _GX2SubmitToTCL() { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); // do nothing if called from non-main GX2 core if (GX2::sGX2MainCoreIndex != coreIndex) { @@ -373,7 +373,7 @@ uint32 _GX2GetUnflushedBytes(uint32 coreIndex) */ void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32) { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = coreinit::OSGetCoreId(); // if we are in a display list then do nothing if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL ) return; @@ -396,12 +396,7 @@ void gx2_load() osLib_addFunction("gx2", "GX2GetCurrentScanBuffer", gx2Export_GX2GetCurrentScanBuffer); // shader stuff - osLib_addFunction("gx2", "GX2GetVertexShaderGPRs", gx2Export_GX2GetVertexShaderGPRs); - osLib_addFunction("gx2", "GX2GetVertexShaderStackEntries", gx2Export_GX2GetVertexShaderStackEntries); - osLib_addFunction("gx2", "GX2GetPixelShaderGPRs", gx2Export_GX2GetPixelShaderGPRs); - osLib_addFunction("gx2", "GX2GetPixelShaderStackEntries", gx2Export_GX2GetPixelShaderStackEntries); - osLib_addFunction("gx2", "GX2SetFetchShader", gx2Export_GX2SetFetchShader); - osLib_addFunction("gx2", "GX2SetVertexShader", gx2Export_GX2SetVertexShader); + //osLib_addFunction("gx2", "GX2SetVertexShader", gx2Export_GX2SetVertexShader); osLib_addFunction("gx2", "GX2SetPixelShader", gx2Export_GX2SetPixelShader); osLib_addFunction("gx2", "GX2SetGeometryShader", gx2Export_GX2SetGeometryShader); osLib_addFunction("gx2", "GX2SetComputeShader", gx2Export_GX2SetComputeShader); diff --git a/src/Cafe/OS/libs/gx2/GX2.h b/src/Cafe/OS/libs/gx2/GX2.h index c9607ee4..58d98191 100644 --- a/src/Cafe/OS/libs/gx2/GX2.h +++ b/src/Cafe/OS/libs/gx2/GX2.h @@ -7,11 +7,6 @@ #define GX2_ENABLE 1 #define GX2_DISABLE 0 -// tex unit base for render backends -#define CEMU_PS_TEX_UNIT_BASE 0 -#define CEMU_VS_TEX_UNIT_BASE 32 -#define CEMU_GS_TEX_UNIT_BASE 64 - #include "GX2_Surface.h" // general @@ -20,12 +15,6 @@ void gx2_load(); // shader -void gx2Export_GX2SetFetchShader(PPCInterpreter_t* hCPU); -void gx2Export_GX2GetVertexShaderGPRs(PPCInterpreter_t* hCPU); -void gx2Export_GX2GetVertexShaderStackEntries(PPCInterpreter_t* hCPU); -void gx2Export_GX2GetPixelShaderGPRs(PPCInterpreter_t* hCPU); -void gx2Export_GX2GetPixelShaderStackEntries(PPCInterpreter_t* hCPU); -void gx2Export_GX2SetVertexShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU); diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp index 8d584190..804e3da0 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -3,6 +3,7 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "GX2_Command.h" @@ -15,7 +16,7 @@ GX2WriteGatherPipeState gx2WriteGatherPipe = { 0 }; void gx2WriteGather_submitU32AsBE(uint32 v) { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) return; *(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = _swapEndianU32(v); @@ -24,7 +25,7 @@ void gx2WriteGather_submitU32AsBE(uint32 v) void gx2WriteGather_submitU32AsLE(uint32 v) { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) return; *(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = v; @@ -33,7 +34,7 @@ void gx2WriteGather_submitU32AsLE(uint32 v) void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues) { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) return; memcpy_dwords((*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]), v, numValues); @@ -134,7 +135,7 @@ namespace GX2 bool GX2GetCurrentDisplayList(betype* displayListAddr, uint32be* displayListSize) { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = coreinit::OSGetCoreId(); if (gx2WriteGatherPipe.displayListStart[coreIndex] == MPTR_NULL) return false; @@ -149,13 +150,13 @@ namespace GX2 bool GX2GetDisplayListWriteStatus() { // returns true if we are writing to a display list - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = coreinit::OSGetCoreId(); return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL; } bool GX2WriteGather_isDisplayListActive() { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = coreinit::OSGetCoreId(); if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) return true; return false; @@ -171,7 +172,7 @@ namespace GX2 void GX2WriteGather_checkAndInsertWrapAroundMark() { - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = coreinit::OSGetCoreId(); if (coreIndex != sGX2MainCoreIndex) // only if main gx2 core return; if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) @@ -187,18 +188,18 @@ namespace GX2 void GX2BeginDisplayList(MEMPTR displayListAddr, uint32 size) { - GX2WriteGather_beginDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR(), size); + GX2WriteGather_beginDisplayList(PPCInterpreter_getCurrentInstance(), displayListAddr.GetMPTR(), size); } void GX2BeginDisplayListEx(MEMPTR displayListAddr, uint32 size, bool profiling) { - GX2WriteGather_beginDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR(), size); + GX2WriteGather_beginDisplayList(PPCInterpreter_getCurrentInstance(), displayListAddr.GetMPTR(), size); } uint32 GX2EndDisplayList(MEMPTR displayListAddr) { cemu_assert_debug(displayListAddr != nullptr); - uint32 displayListSize = GX2WriteGather_endDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR()); + uint32 displayListSize = GX2WriteGather_endDisplayList(PPCInterpreter_getCurrentInstance(), displayListAddr.GetMPTR()); return displayListSize; } @@ -220,7 +221,7 @@ namespace GX2 // its basically a way to manually submit a command buffer to the GPU // as such it also affects the submission and retire timestamps - uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); cemu_assert_debug(coreIndex == sGX2MainCoreIndex); coreIndex = sGX2MainCoreIndex; // always submit to main queue which is owned by GX2 main core (TCLSubmitToRing does not need this workaround) @@ -263,7 +264,7 @@ namespace GX2 if (patchType == GX2_PATCH_TYPE::VERTEX_SHADER) { - GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)obj; + GX2VertexShader* vertexShader = (GX2VertexShader*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(vertexShader->GetProgramAddr()) >> 8; } else if (patchType == GX2_PATCH_TYPE::PIXEL_SHADER) @@ -273,7 +274,7 @@ namespace GX2 } else if (patchType == GX2_PATCH_TYPE::FETCH_SHADER) { - GX2FetchShader_t* fetchShader = (GX2FetchShader_t*)obj; + GX2FetchShader* fetchShader = (GX2FetchShader*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(fetchShader->GetProgramAddr()) >> 8; } else if (patchType == GX2_PATCH_TYPE::GEOMETRY_COPY_SHADER) diff --git a/src/Cafe/OS/libs/gx2/GX2_Event.cpp b/src/Cafe/OS/libs/gx2/GX2_Event.cpp index ba498477..9748e20b 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Event.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Event.cpp @@ -263,7 +263,7 @@ namespace GX2 gx2WriteGather_submitU32AsBE(0x00000000); // unused } // flush pipeline - if (_GX2GetUnflushedBytes(PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance)) > 0) + if (_GX2GetUnflushedBytes(coreinit::OSGetCoreId()) > 0) _GX2SubmitToTCL(); uint64 ts = GX2GetLastSubmittedTimeStamp(); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index c63688eb..ad17dc49 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -3,6 +3,7 @@ #include "GX2_Shader.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/ISA/LatteInstructions.h" uint32 memory_getVirtualOffsetFromPointer(void* ptr); // remove once we updated everything to MEMPTR @@ -70,9 +71,9 @@ namespace GX2 static_assert(sizeof(betype) == 0x4); // calculate size of CF program subpart, includes alignment padding for clause instructions - size_t _calcFetchShaderCFCodeSize(uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + size_t _calcFetchShaderCFCodeSize(uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { - cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); + cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); cemu_assert_debug(tessellationMode == 0); uint32 numCFInstructions = ((attributeCount + 15) / 16) + 1; // one VTX clause can have up to 16 instructions + final CF instruction is RETURN size_t cfSize = numCFInstructions * 8; @@ -80,16 +81,16 @@ namespace GX2 return cfSize; } - size_t _calcFetchShaderClauseCodeSize(uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + size_t _calcFetchShaderClauseCodeSize(uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { - cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); + cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); cemu_assert_debug(tessellationMode == 0); uint32 numClauseInstructions = attributeCount; size_t clauseSize = numClauseInstructions * 16; return clauseSize; } - void _writeFetchShaderCFCode(void* programBufferOut, uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + void _writeFetchShaderCFCode(void* programBufferOut, uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { LatteCFInstruction* cfInstructionWriter = (LatteCFInstruction*)programBufferOut; uint32 attributeIndex = 0; @@ -111,7 +112,7 @@ namespace GX2 memcpy(cfInstructionWriter, &returnInstr, sizeof(LatteCFInstruction)); } - void _writeFetchShaderVTXCode(GX2FetchShader_t* fetchShader, void* programOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + void _writeFetchShaderVTXCode(GX2FetchShader* fetchShader, void* programOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { uint8* writePtr = (uint8*)programOut; // one instruction per attribute (hardcoded into _writeFetchShaderCFCode) @@ -151,7 +152,7 @@ namespace GX2 bool divisorFound = false; for (uint32 i = 0; i < numDivisors; i++) { - if (_swapEndianU32(fetchShader->divisors[i]) == attrAluDivisor) + if (fetchShader->divisors[i] == attrAluDivisor) { srcSelX = i != 0 ? 2 : 1; divisorFound = true; @@ -168,7 +169,7 @@ namespace GX2 else { srcSelX = numDivisors != 0 ? 2 : 1; - fetchShader->divisors[numDivisors] = _swapEndianU32(attrAluDivisor); + fetchShader->divisors[numDivisors] = attrAluDivisor; numDivisors++; fetchShader->divisorCount = _swapEndianU32(numDivisors); } @@ -213,9 +214,9 @@ namespace GX2 } } - uint32 GX2CalcFetchShaderSizeEx(uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + uint32 GX2CalcFetchShaderSizeEx(uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { - cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); // other types are todo + cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); // other types are todo cemu_assert_debug(tessellationMode == 0); // other modes are todo uint32 finalSize = @@ -225,9 +226,9 @@ namespace GX2 return finalSize; } - void GX2InitFetchShaderEx(GX2FetchShader_t* fetchShader, void* programBufferOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + void GX2InitFetchShaderEx(GX2FetchShader* fetchShader, void* programBufferOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { - cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); + cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); cemu_assert_debug(tessellationMode == 0); /* @@ -238,7 +239,7 @@ namespace GX2 [CLAUSES] */ - memset(fetchShader, 0x00, sizeof(GX2FetchShader_t)); + memset(fetchShader, 0x00, sizeof(GX2FetchShader)); fetchShader->attribCount = _swapEndianU32(attributeCount); fetchShader->shaderPtr = (MPTR)_swapEndianU32(memory_getVirtualOffsetFromPointer(programBufferOut)); @@ -251,14 +252,181 @@ namespace GX2 shaderOutput += _calcFetchShaderClauseCodeSize(attributeCount, fetchShaderType, tessellationMode); uint32 shaderSize = (uint32)(shaderOutput - shaderStart); - cemu_assert_debug(shaderSize == GX2CalcFetchShaderSizeEx(attributeCount, GX2FetchShader_t::FetchShaderType::NO_TESSELATION, tessellationMode)); + cemu_assert_debug(shaderSize == GX2CalcFetchShaderSizeEx(attributeCount, GX2FetchShader::FetchShaderType::NO_TESSELATION, tessellationMode)); fetchShader->shaderSize = _swapEndianU32((uint32)(shaderOutput - shaderStart)); + + fetchShader->reg_SQ_PGM_RESOURCES_FS = Latte::LATTE_SQ_PGM_RESOURCES_FS().set_NUM_GPRS(2); // todo - affected by tesselation params? + } + + uint32 GX2GetVertexShaderGPRs(GX2VertexShader* vertexShader) + { + return vertexShader->regs.SQ_PGM_RESOURCES_VS.value().get_NUM_GPRS(); + } + + uint32 GX2GetVertexShaderStackEntries(GX2VertexShader* vertexShader) + { + return vertexShader->regs.SQ_PGM_RESOURCES_VS.value().get_NUM_STACK_ENTRIES(); + } + + uint32 GX2GetPixelShaderGPRs(GX2PixelShader_t* pixelShader) + { + return _swapEndianU32(pixelShader->regs[0])&0xFF; + } + + uint32 GX2GetPixelShaderStackEntries(GX2PixelShader_t* pixelShader) + { + return (_swapEndianU32(pixelShader->regs[0]>>8))&0xFF; + } + + void GX2SetFetchShader(GX2FetchShader* fetchShaderPtr) + { + GX2ReserveCmdSpace(11); + cemu_assert_debug((_swapEndianU32(fetchShaderPtr->shaderPtr) & 0xFF) == 0); + + gx2WriteGather_submit( + // setup fetch shader + pm4HeaderType3(IT_SET_CONTEXT_REG, 1+5), + Latte::REGADDR::SQ_PGM_START_FS-0xA000, + _swapEndianU32(fetchShaderPtr->shaderPtr)>>8, + _swapEndianU32(fetchShaderPtr->shaderSize)>>3, + 0x10000, // ukn (ring buffer size?) + 0x10000, // ukn (ring buffer size?) + fetchShaderPtr->reg_SQ_PGM_RESOURCES_FS, + + // write instance step + pm4HeaderType3(IT_SET_CONTEXT_REG, 1+2), + Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0-0xA000, + fetchShaderPtr->divisors[0], + fetchShaderPtr->divisors[1]); + } + + void GX2SetVertexShader(GX2VertexShader* vertexShader) + { + GX2ReserveCmdSpace(100); + + MPTR shaderProgramAddr; + uint32 shaderProgramSize; + if (vertexShader->shaderPtr) + { + // without R API + shaderProgramAddr = vertexShader->shaderPtr.GetMPTR(); + shaderProgramSize = vertexShader->shaderSize; + } + else + { + shaderProgramAddr = vertexShader->rBuffer.GetVirtualAddr(); + shaderProgramSize = vertexShader->rBuffer.GetSize(); + } + + cemu_assert_debug(shaderProgramAddr != 0); + cemu_assert_debug(shaderProgramSize != 0); + + if (vertexShader->shaderMode == GX2_SHADER_MODE::GEOMETRY_SHADER) + { + // in geometry shader mode the vertex shader is written to _ES register and almost all vs control registers are set by GX2SetGeometryShader + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 6), + Latte::REGADDR::SQ_PGM_START_ES-0xA000, + memory_virtualToPhysical(shaderProgramAddr)>>8, + shaderProgramSize>>3, + 0x100000, + 0x100000, + vertexShader->regs.SQ_PGM_RESOURCES_VS); // SQ_PGM_RESOURCES_VS/SQ_PGM_RESOURCES_ES + } + else + { + gx2WriteGather_submit( + /* vertex shader program */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 6), + Latte::REGADDR::SQ_PGM_START_VS-0xA000, + memory_virtualToPhysical(shaderProgramAddr)>>8, // physical address + shaderProgramSize>>3, + 0x100000, + 0x100000, + vertexShader->regs.SQ_PGM_RESOURCES_VS, // SQ_PGM_RESOURCES_VS/ES + /* primitive id enable */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::VGT_PRIMITIVEID_EN-0xA000, + vertexShader->regs.VGT_PRIMITIVEID_EN, + /* output config */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::SPI_VS_OUT_CONFIG-0xA000, + vertexShader->regs.SPI_VS_OUT_CONFIG, + /* PA_CL_VS_OUT_CNTL */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::PA_CL_VS_OUT_CNTL-0xA000, + vertexShader->regs.PA_CL_VS_OUT_CNTL + ); + + cemu_assert_debug(vertexShader->regs.SPI_VS_OUT_CONFIG.value().get_VS_PER_COMPONENT() == false); // not handled on the GPU side + + uint32 numOutputIds = vertexShader->regs.vsOutIdTableSize; + numOutputIds = std::min(numOutputIds, 0xA); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); + gx2WriteGather_submitU32AsBE(Latte::REGADDR::SPI_VS_OUT_ID_0-0xA000); + for(uint32 i=0; iregs.LATTE_SPI_VS_OUT_ID_N[i].value().getRawValue()); + + // todo: SQ_PGM_CF_OFFSET_VS + // todo: VGT_STRMOUT_BUFFER_EN + // stream out + if (vertexShader->usesStreamOut != 0) + { + // stride 0 + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_0-0xA000, + vertexShader->streamOutVertexStride[0]>>2, + // stride 1 + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_1-0xA000, + vertexShader->streamOutVertexStride[1]>>2, + // stride 2 + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_2-0xA000, + vertexShader->streamOutVertexStride[2]>>2, + // stride 3 + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_3-0xA000, + vertexShader->streamOutVertexStride[3]>>2); + } + } + // update semantic table + uint32 vsSemanticTableSize = vertexShader->regs.semanticTableSize; + if (vsSemanticTableSize > 0) + { + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1), + Latte::REGADDR::SQ_VTX_SEMANTIC_CLEAR-0xA000, + 0xFFFFFFFF); + if (vsSemanticTableSize == 0) + { + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1), + Latte::REGADDR::SQ_VTX_SEMANTIC_0-0xA000, + 0xFFFFFFFF); + } + else + { + uint32* vsSemanticTable = (uint32*)vertexShader->regs.SQ_VTX_SEMANTIC_N; + vsSemanticTableSize = std::min(vsSemanticTableSize, 32); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+vsSemanticTableSize)); + gx2WriteGather_submitU32AsBE(Latte::REGADDR::SQ_VTX_SEMANTIC_0-0xA000); + gx2WriteGather_submitU32AsLEArray(vsSemanticTable, vsSemanticTableSize); + } + } } void GX2ShaderInit() { cafeExportRegister("gx2", GX2CalcFetchShaderSizeEx, LogType::GX2); cafeExportRegister("gx2", GX2InitFetchShaderEx, LogType::GX2); + + cafeExportRegister("gx2", GX2GetVertexShaderGPRs, LogType::GX2); + cafeExportRegister("gx2", GX2GetVertexShaderStackEntries, LogType::GX2); + cafeExportRegister("gx2", GX2GetPixelShaderGPRs, LogType::GX2); + cafeExportRegister("gx2", GX2GetPixelShaderStackEntries, LogType::GX2); + cafeExportRegister("gx2", GX2SetFetchShader, LogType::GX2); + cafeExportRegister("gx2", GX2SetVertexShader, LogType::GX2); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.h b/src/Cafe/OS/libs/gx2/GX2_Shader.h index 1d1c79cc..960bdf95 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.h +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.h @@ -2,7 +2,7 @@ #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "GX2_Streamout.h" -struct GX2FetchShader_t +struct GX2FetchShader { enum class FetchShaderType : uint32 { @@ -10,12 +10,12 @@ struct GX2FetchShader_t }; /* +0x00 */ betype fetchShaderType; - /* +0x04 */ uint32 _regs[1]; + /* +0x04 */ betype reg_SQ_PGM_RESOURCES_FS; /* +0x08 */ uint32 shaderSize; /* +0x0C */ MPTR shaderPtr; /* +0x10 */ uint32 attribCount; /* +0x14 */ uint32 divisorCount; - /* +0x18 */ uint32 divisors[2]; + /* +0x18 */ uint32be divisors[2]; MPTR GetProgramAddr() const { @@ -23,8 +23,8 @@ struct GX2FetchShader_t } }; -static_assert(sizeof(GX2FetchShader_t) == 0x20); -static_assert(sizeof(betype) == 4); +static_assert(sizeof(GX2FetchShader) == 0x20); +static_assert(sizeof(betype) == 4); namespace GX2 { @@ -32,19 +32,43 @@ namespace GX2 void GX2ShaderInit(); } -// code below still needs to be modernized (use betype, enum classes) +// code below still needs to be modernized (use betype, enum classes, move to namespace) +// deprecated, use GX2_SHADER_MODE enum class instead #define GX2_SHADER_MODE_UNIFORM_REGISTER 0 #define GX2_SHADER_MODE_UNIFORM_BLOCK 1 #define GX2_SHADER_MODE_GEOMETRY_SHADER 2 #define GX2_SHADER_MODE_COMPUTE_SHADER 3 -struct GX2VertexShader_t +enum class GX2_SHADER_MODE : uint32 { - /* +0x000 */ uint32 regs[52]; - /* +0x0D0 */ uint32 shaderSize; - /* +0x0D4 */ MPTR shaderPtr; - /* +0x0D8 */ uint32 shaderMode; // GX2_SHADER_MODE_* + UNIFORM_REGISTER = 0, + UNIFORM_BLOCK = 1, + GEOMETRY_SHADER = 2, + COMPUTE_SHADER = 3, +}; + +struct GX2VertexShader +{ + /* +0x000 */ + struct + { + /* +0x00 */ betype SQ_PGM_RESOURCES_VS; // compatible with SQ_PGM_RESOURCES_ES + /* +0x04 */ betype VGT_PRIMITIVEID_EN; + /* +0x08 */ betype SPI_VS_OUT_CONFIG; + /* +0x0C */ uint32be vsOutIdTableSize; + /* +0x10 */ betype LATTE_SPI_VS_OUT_ID_N[10]; + /* +0x38 */ betype PA_CL_VS_OUT_CNTL; + /* +0x3C */ uint32be uknReg15; // ? + /* +0x40 */ uint32be semanticTableSize; + /* +0x44 */ betype SQ_VTX_SEMANTIC_N[32]; + /* +0xC4 */ uint32be uknReg49; // ? + /* +0xC8 */ uint32be uknReg50; // vgt_vertex_reuse_block_cntl + /* +0xCC */ uint32be uknReg51; // vgt_hos_reuse_depth + }regs; + /* +0x0D0 */ uint32be shaderSize; + /* +0x0D4 */ MEMPTR shaderPtr; + /* +0x0D8 */ betype shaderMode; /* +0x0DC */ uint32 uniformBlockCount; /* +0x0E0 */ MPTR uniformBlockInfo; /* +0x0E4 */ uint32 uniformVarCount; @@ -57,20 +81,20 @@ struct GX2VertexShader_t /* +0x100 */ MPTR samplerInfo; /* +0x104 */ uint32 attribCount; /* +0x108 */ MPTR attribInfo; - /* +0x10C */ uint32 ringItemsize; // for GS - /* +0x110 */ uint32 usesStreamOut; - /* +0x114 */ uint32 streamOutVertexStride[GX2_MAX_STREAMOUT_BUFFERS]; + /* +0x10C */ uint32be ringItemsize; // for GS + /* +0x110 */ uint32be usesStreamOut; + /* +0x114 */ uint32be streamOutVertexStride[GX2_MAX_STREAMOUT_BUFFERS]; /* +0x124 */ GX2RBuffer rBuffer; MPTR GetProgramAddr() const { - if (_swapEndianU32(this->shaderPtr) != MPTR_NULL) - return _swapEndianU32(this->shaderPtr); + if (this->shaderPtr) + return this->shaderPtr.GetMPTR(); return this->rBuffer.GetVirtualAddr(); } }; -static_assert(sizeof(GX2VertexShader_t) == 0x134); +static_assert(sizeof(GX2VertexShader) == 0x134); typedef struct _GX2PixelShader { diff --git a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp index 845292fe..1cb61a7e 100644 --- a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp @@ -8,204 +8,6 @@ #include "GX2.h" #include "GX2_Shader.h" -void gx2Export_GX2SetFetchShader(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2SetFetchShader(0x{:08x})", hCPU->gpr[3]); - GX2ReserveCmdSpace(11); - GX2FetchShader_t* fetchShaderPtr = (GX2FetchShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - cemu_assert_debug((_swapEndianU32(fetchShaderPtr->shaderPtr) & 0xFF) == 0); - - gx2WriteGather_submit( - // setup fetch shader - pm4HeaderType3(IT_SET_CONTEXT_REG, 1+5), - mmSQ_PGM_START_FS-0xA000, - _swapEndianU32(fetchShaderPtr->shaderPtr)>>8, // pointer divided by 256 - _swapEndianU32(fetchShaderPtr->shaderSize)>>3, // size divided by 8 - 0x10000, // ukn (ring buffer size?) - 0x10000, // ukn (ring buffer size?) - *(uint32be*)&(fetchShaderPtr->_regs[0]), - - // write instance step - pm4HeaderType3(IT_SET_CONTEXT_REG, 1+2), - mmVGT_INSTANCE_STEP_RATE_0-0xA000, - *(uint32be*)&(fetchShaderPtr->divisors[0]), - *(uint32be*)&(fetchShaderPtr->divisors[1])); - - osLib_returnFromFunction(hCPU, 0); -} - -void gx2Export_GX2GetVertexShaderGPRs(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2GetVertexShaderGPRs(0x{:08x})", hCPU->gpr[3]); - GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - uint8 numGPRs = _swapEndianU32(vertexShader->regs[0])&0xFF; - osLib_returnFromFunction(hCPU, numGPRs); -} - -void gx2Export_GX2GetVertexShaderStackEntries(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2GetVertexShaderStackEntries(0x{:08x})", hCPU->gpr[3]); - GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - uint8 stackEntries = (_swapEndianU32(vertexShader->regs[0])>>8)&0xFF; - osLib_returnFromFunction(hCPU, stackEntries); -} - -void gx2Export_GX2GetPixelShaderGPRs(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2GetPixelShaderGPRs(0x{:08x})", hCPU->gpr[3]); - GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - uint8 stackEntries = (_swapEndianU32(pixelShader->regs[0]))&0xFF; - osLib_returnFromFunction(hCPU, stackEntries); -} - -void gx2Export_GX2GetPixelShaderStackEntries(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2GetPixelShaderStackEntries(0x{:08x})", hCPU->gpr[3]); - GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - uint8 numGPRs = (_swapEndianU32(pixelShader->regs[0]>>8))&0xFF; - osLib_returnFromFunction(hCPU, numGPRs); -} - -void gx2Export_GX2SetVertexShader(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2SetVertexShader(0x{:08x})", hCPU->gpr[3]); - GX2ReserveCmdSpace(100); - - GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - - MPTR shaderProgramAddr; - uint32 shaderProgramSize; - - if( _swapEndianU32(vertexShader->shaderPtr) != MPTR_NULL ) - { - // without R API - shaderProgramAddr = _swapEndianU32(vertexShader->shaderPtr); - shaderProgramSize = _swapEndianU32(vertexShader->shaderSize); - } - else - { - shaderProgramAddr = vertexShader->rBuffer.GetVirtualAddr(); - shaderProgramSize = vertexShader->rBuffer.GetSize(); - } - - cemu_assert_debug(shaderProgramAddr != 0); - cemu_assert_debug(shaderProgramSize != 0); - - if( _swapEndianU32(vertexShader->shaderMode) == GX2_SHADER_MODE_GEOMETRY_SHADER ) - { - // in geometry shader mode the vertex shader is written to _ES register and almost all vs control registers are set by GX2SetGeometryShader - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 6)); - gx2WriteGather_submitU32AsBE(mmSQ_PGM_START_ES-0xA000); - gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(shaderProgramAddr)>>8); - gx2WriteGather_submitU32AsBE(shaderProgramSize>>3); - gx2WriteGather_submitU32AsBE(0x100000); - gx2WriteGather_submitU32AsBE(0x100000); - gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->regs[0])); // unknown - } - else - { - gx2WriteGather_submit( - /* vertex shader program */ - pm4HeaderType3(IT_SET_CONTEXT_REG, 6), - mmSQ_PGM_START_VS-0xA000, - memory_virtualToPhysical(shaderProgramAddr)>>8, // physical address - shaderProgramSize>>3, // size - 0x100000, - 0x100000, - _swapEndianU32(vertexShader->regs[0]), // unknown - /* primitive id enable */ - pm4HeaderType3(IT_SET_CONTEXT_REG, 2), - mmVGT_PRIMITIVEID_EN-0xA000, - _swapEndianU32(vertexShader->regs[1]), - /* output config */ - pm4HeaderType3(IT_SET_CONTEXT_REG, 2), - mmSPI_VS_OUT_CONFIG-0xA000, - _swapEndianU32(vertexShader->regs[2])); - - if( (_swapEndianU32(vertexShader->regs[2]) & 1) != 0 ) - debugBreakpoint(); // per-component flag? - - // ukn - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); - gx2WriteGather_submitU32AsBE(mmPA_CL_VS_OUT_CNTL-0xA000); - gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->regs[14])); - - uint32 numOutputIds = _swapEndianU32(vertexShader->regs[3]); - numOutputIds = std::min(numOutputIds, 0xA); - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); - gx2WriteGather_submitU32AsBE(mmSPI_VS_OUT_ID_0-0xA000); - for(uint32 i=0; iregs[4+i])); - } - - /* - VS _regs[]: - 0 ? - 1 mmVGT_PRIMITIVEID_EN (?) - 2 mmSPI_VS_OUT_CONFIG - 3 Number of used SPI_VS_OUT_ID_* entries - 4 - 13 SPI_VS_OUT_ID_0 - SPI_VS_OUT_ID_9 - 14 pa_cl_vs_out_cntl - ... - 17 - ?? semantic table entry (input) - - ... - 50 vgt_vertex_reuse_block_cntl - 51 vgt_hos_reuse_depth - */ - - // todo: mmSQ_PGM_CF_OFFSET_VS - // todo: mmVGT_STRMOUT_BUFFER_EN - // stream out - if( _swapEndianU32(vertexShader->usesStreamOut) != 0 ) - { - // stride 0 - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); - gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_0-0xA000); - gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[0])>>2); - // stride 1 - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); - gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_1-0xA000); - gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[1])>>2); - // stride 2 - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); - gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_2-0xA000); - gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[2])>>2); - // stride 3 - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); - gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_3-0xA000); - gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[3])>>2); - } - } - // update semantic table - uint32 vsSemanticTableSize = _swapEndianU32(vertexShader->regs[0x40/4]); - if( vsSemanticTableSize > 0 ) - { - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); - gx2WriteGather_submitU32AsBE(mmSQ_VTX_SEMANTIC_CLEAR-0xA000); - gx2WriteGather_submitU32AsBE(0xFFFFFFFF); - if( vsSemanticTableSize == 0 ) - { - // todo: Figure out how this is done on real SW/HW (some vertex shaders don't have a semantic table) - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); - gx2WriteGather_submitU32AsBE(mmSQ_VTX_SEMANTIC_0-0xA000); - gx2WriteGather_submitU32AsBE(0xFFFFFFFF); - } - else - { - uint32* vsSemanticTable = vertexShader->regs+(0x44/4); - vsSemanticTableSize = std::min(vsSemanticTableSize, 0x20); - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+vsSemanticTableSize)); - gx2WriteGather_submitU32AsBE(mmSQ_VTX_SEMANTIC_0-0xA000); - for(uint32 i=0; igpr[3]); @@ -415,14 +217,14 @@ void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -struct GX2ComputeShader_t +struct GX2ComputeShader { /* +0x00 */ uint32be regs[12]; /* +0x30 */ uint32be programSize; /* +0x34 */ uint32be programPtr; - /* +0x38 */ uint32 ukn38; - /* +0x3C */ uint32 ukn3C; - /* +0x40 */ uint32 ukn40[8]; + /* +0x38 */ uint32be ukn38; + /* +0x3C */ uint32be ukn3C; + /* +0x40 */ uint32be ukn40[8]; /* +0x60 */ uint32be workgroupSizeX; /* +0x64 */ uint32be workgroupSizeY; /* +0x68 */ uint32be workgroupSizeZ; @@ -431,13 +233,13 @@ struct GX2ComputeShader_t /* +0x74 */ GX2RBuffer rBuffer; }; -static_assert(offsetof(GX2ComputeShader_t, programSize) == 0x30); -static_assert(offsetof(GX2ComputeShader_t, workgroupSizeX) == 0x60); -static_assert(offsetof(GX2ComputeShader_t, rBuffer) == 0x74); +static_assert(offsetof(GX2ComputeShader, programSize) == 0x30); +static_assert(offsetof(GX2ComputeShader, workgroupSizeX) == 0x60); +static_assert(offsetof(GX2ComputeShader, rBuffer) == 0x74); void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) { - ppcDefineParamTypePtr(computeShader, GX2ComputeShader_t, 0); + ppcDefineParamTypePtr(computeShader, GX2ComputeShader, 0); cemuLog_log(LogType::GX2, "GX2SetComputeShader(0x{:08x})", hCPU->gpr[3]); MPTR shaderPtr; diff --git a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp index 88ce272a..024965fd 100644 --- a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp +++ b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp @@ -254,6 +254,8 @@ namespace H264 m_codecCtx->pv_fxns = (void*)&ih264d_api_function; m_codecCtx->u4_size = sizeof(iv_obj_t); + SetDecoderCoreCount(1); + m_isBufferedMode = isBufferedMode; UpdateParameters(false); @@ -278,6 +280,19 @@ namespace H264 m_codecCtx = nullptr; } + void SetDecoderCoreCount(uint32 coreCount) + { + ih264d_ctl_set_num_cores_ip_t s_set_cores_ip; + ih264d_ctl_set_num_cores_op_t s_set_cores_op; + s_set_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_cores_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES; + s_set_cores_ip.u4_num_cores = coreCount; // valid numbers are 1-4 + s_set_cores_ip.u4_size = sizeof(ih264d_ctl_set_num_cores_ip_t); + s_set_cores_op.u4_size = sizeof(ih264d_ctl_set_num_cores_op_t); + IV_API_CALL_STATUS_T status = ih264d_api_function(m_codecCtx, (void *)&s_set_cores_ip, (void *)&s_set_cores_op); + cemu_assert(status == IV_SUCCESS); + } + static bool GetImageInfo(uint8* stream, uint32 length, uint32& imageWidth, uint32& imageHeight) { // create temporary decoder @@ -702,7 +717,6 @@ namespace H264 decodeResult = m_bufferedResults.front(); m_bufferedResults.erase(m_bufferedResults.begin()); } - private: iv_obj_t* m_codecCtx{nullptr}; bool m_hasBufferSizeInfo{ false }; @@ -845,10 +859,10 @@ namespace H264 return H264DEC_STATUS::SUCCESS; } StackAllocator executeDoneEvent; - coreinit::OSInitEvent(executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + coreinit::OSInitEvent(&executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::vector results; auto asyncTask = std::async(std::launch::async, _async_H264DECEnd, executeDoneEvent.GetPointer(), session, ctx, &results); - coreinit::OSWaitEvent(executeDoneEvent); + coreinit::OSWaitEvent(&executeDoneEvent); _ReleaseDecoderSession(session); if (!results.empty()) { @@ -963,9 +977,9 @@ namespace H264 StackAllocator stack_decodedFrameResult; for (sint32 i = 0; i < outputFrameCount; i++) - stack_resultPtrArray[i] = stack_decodedFrameResult + i; + stack_resultPtrArray[i] = &stack_decodedFrameResult + i; - H264DECFrameOutput* frameOutput = stack_decodedFrameResult + 0; + H264DECFrameOutput* frameOutput = &stack_decodedFrameResult + 0; memset(frameOutput, 0x00, sizeof(H264DECFrameOutput)); frameOutput->imagePtr = (uint8*)decodeResult.imageOutput; frameOutput->result = 100; @@ -1008,10 +1022,10 @@ namespace H264 return 0; } StackAllocator executeDoneEvent; - coreinit::OSInitEvent(executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + coreinit::OSInitEvent(&executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); H264AVCDecoder::DecodeResult decodeResult; - auto asyncTask = std::async(std::launch::async, _async_H264DECExecute, executeDoneEvent.GetPointer(), session, ctx, imageOutput , &decodeResult); - coreinit::OSWaitEvent(executeDoneEvent); + auto asyncTask = std::async(std::launch::async, _async_H264DECExecute, &executeDoneEvent, session, ctx, imageOutput , &decodeResult); + coreinit::OSWaitEvent(&executeDoneEvent); _ReleaseDecoderSession(session); if(decodeResult.frameReady) H264DoFrameOutputCallback(ctx, decodeResult); diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 9afb9f85..53981a5a 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -225,7 +225,7 @@ void CurlWorkerThread(CURL_t* curl, PPCConcurrentQueue* callerQueue, uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) { - OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance); + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); curl->curlThread = currentThread; // cemuLog_logDebug(LogType::Force, "CURRENTTHREAD: 0x{} -> {}",currentThread, order) @@ -707,7 +707,7 @@ void export_curl_easy_init(PPCInterpreter_t* hCPU) memset(result.GetPtr(), 0, sizeof(CURL_t)); *result = {}; result->curl = curl_easy_init(); - result->curlThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance); + result->curlThread = coreinit::OSGetCurrentThread(); result->info_contentType = nullptr; result->info_redirectUrl = nullptr; diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index fb1d4d14..0fd9df5a 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -283,7 +283,6 @@ void nnActExport_GetSimpleAddressIdEx(PPCInterpreter_t* hCPU) void nnActExport_GetPrincipalId(PPCInterpreter_t* hCPU) { // return error for non-nnid accounts? - cemuLog_logDebug(LogType::Force, "nn_act.GetPrincipalId()"); uint32be principalId; GetPrincipalIdEx(&principalId, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, (uint32)principalId); @@ -392,7 +391,7 @@ void nnActExport_GetMiiName(PPCInterpreter_t* hCPU) StackAllocator miiData; - uint32 r = nn::act::GetMiiEx(miiData, iosu::act::ACT_SLOT_CURRENT); + uint32 r = nn::act::GetMiiEx(&miiData, iosu::act::ACT_SLOT_CURRENT); // extract name sint32 miiNameLength = 0; for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) @@ -415,7 +414,7 @@ void nnActExport_GetMiiNameEx(PPCInterpreter_t* hCPU) StackAllocator miiData; - uint32 r = nn::act::GetMiiEx(miiData, slot); + uint32 r = nn::act::GetMiiEx(&miiData, slot); // extract name sint32 miiNameLength = 0; for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) diff --git a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp index e33b6369..fc757ea9 100644 --- a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp +++ b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp @@ -1,426 +1,555 @@ #include "Cafe/OS/common/OSCommon.h" -#include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/IOSU/legacy/iosu_fpd.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" // deprecated +#include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" - -#define fpdPrepareRequest() \ -StackAllocator _buf_fpdRequest; \ -StackAllocator _buf_bufferVector; \ -iosu::fpd::iosuFpdCemuRequest_t* fpdRequest = _buf_fpdRequest.GetPointer(); \ -ioBufferVector_t* fpdBufferVector = _buf_bufferVector.GetPointer(); \ -memset(fpdRequest, 0, sizeof(iosu::fpd::iosuFpdCemuRequest_t)); \ -memset(fpdBufferVector, 0, sizeof(ioBufferVector_t)); \ -fpdBufferVector->buffer = (uint8*)fpdRequest; +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "Cafe/OS/libs/nn_common.h" +#include "util/ChunkedHeap/ChunkedHeap.h" +#include "Common/CafeString.h" namespace nn { namespace fp { + static const auto FPResult_OkZero = 0; + static const auto FPResult_Ok = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_FP, 0); + static const auto FPResult_InvalidIPCParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_FP, 0x680); + static const auto FPResult_RequestFailed = BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); // figure out proper error code - struct + struct { - bool isInitialized; + uint32 initCounter; bool isAdminMode; + bool isLoggedIn; + IOSDevHandle fpdHandle; + SysAllocator fpMutex; + SysAllocator g_fpdAllocatorSpace; + VHeap* fpBufferHeap{nullptr}; + // PPC buffers for async notification query + SysAllocator notificationCount; + SysAllocator notificationBuffer; + bool getNotificationCalled{false}; + // notification handler + MEMPTR notificationHandler{nullptr}; + MEMPTR notificationHandlerParam{nullptr}; }g_fp = { }; - void Initialize() + class { - if (g_fp.isInitialized == false) + public: + void Init() { - g_fp.isInitialized = true; - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_INITIALIZE; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + std::unique_lock _l(m_mtx); + g_fp.fpBufferHeap = new VHeap(g_fp.g_fpdAllocatorSpace.GetPtr(), g_fp.g_fpdAllocatorSpace.GetByteSize()); } - } - - - - void export_IsInitialized(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "Called nn_fp.IsInitialized"); - osLib_returnFromFunction(hCPU, g_fp.isInitialized ? 1 : 0); - } - - void export_Initialize(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "Called nn_fp.Initialize"); - - Initialize(); - - osLib_returnFromFunction(hCPU, 0); - } - - void export_InitializeAdmin(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "Called nn_fp.InitializeAdmin"); - Initialize(); - g_fp.isAdminMode = true; - osLib_returnFromFunction(hCPU, 0); - } - - void export_IsInitializedAdmin(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "nn_fp.IsInitializedAdmin()"); - osLib_returnFromFunction(hCPU, g_fp.isInitialized ? 1 : 0); - } - - void export_SetNotificationHandler(PPCInterpreter_t* hCPU) - { - ppcDefineParamU32(notificationMask, 0); - ppcDefineParamMPTR(funcMPTR, 1); - ppcDefineParamMPTR(customParam, 2); - - cemuLog_logDebug(LogType::Force, "nn_fp.SetNotificationHandler(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_SET_NOTIFICATION_HANDLER; - fpdRequest->setNotificationHandler.notificationMask = notificationMask; - fpdRequest->setNotificationHandler.funcPtr = funcMPTR; - fpdRequest->setNotificationHandler.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, 0); - } - - void export_LoginAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMPTR(funcPtr, 0); - ppcDefineParamMPTR(custom, 1); - cemuLog_logDebug(LogType::Force, "nn_fp.LoginAsync(0x{:08x},0x{:08x})", funcPtr, custom); - if (g_fp.isInitialized == false) + void Destroy() { - osLib_returnFromFunction(hCPU, 0xC0C00580); + std::unique_lock _l(m_mtx); + delete g_fp.fpBufferHeap; + } + + void* Allocate(uint32 size, uint32 alignment) + { + std::unique_lock _l(m_mtx); + void* p = g_fp.fpBufferHeap->alloc(size, 32); + if (!p) + cemuLog_log(LogType::Force, "nn_fp: Internal heap is full"); + return p; + } + + void Free(void* ptr) + { + std::unique_lock _l(m_mtx); + g_fp.fpBufferHeap->free(ptr); + } + + private: + std::mutex m_mtx; + }FPIpcBufferAllocator; + + class FPIpcContext { + static inline constexpr uint32 MAX_VEC_COUNT = 8; + public: + // use FP heap for this class + static void* operator new(size_t size) + { + return FPIpcBufferAllocator.Allocate(size, (uint32)alignof(FPIpcContext)); + } + + static void operator delete(void* ptr) + { + FPIpcBufferAllocator.Free(ptr); + } + + FPIpcContext(iosu::fpd::FPD_REQUEST_ID requestId) : m_requestId(requestId) + { + } + + ~FPIpcContext() + { + if(m_dataBuffer) + FPIpcBufferAllocator.Free(m_dataBuffer); + } + + void AddInput(void* ptr, uint32 size) + { + size_t vecIndex = GetVecInIndex(m_numVecIn); + m_vec[vecIndex].baseVirt = ptr; + m_vec[vecIndex].size = size; + m_numVecIn = m_numVecIn + 1; + } + + void AddOutput(void* ptr, uint32 size) + { + cemu_assert_debug(m_numVecIn == 0); // all outputs need to be added before any inputs + size_t vecIndex = GetVecOutIndex(m_numVecOut); + m_vec[vecIndex].baseVirt = ptr; + m_vec[vecIndex].size = size; + m_numVecOut = m_numVecOut + 1; + } + + uint32 Submit(std::unique_ptr owner) + { + InitSubmissionBuffer(); + // note: While generally, Ioctlv() usage has the order as input (app->IOSU) followed by output (IOSU->app), FP uses it the other way around + nnResult r = coreinit::IOS_Ioctlv(g_fp.fpdHandle, (uint32)m_requestId.value(), m_numVecOut, m_numVecIn, m_vec); + CopyBackOutputs(); + owner.reset(); + return r; + } + + nnResult SubmitAsync(std::unique_ptr owner, MEMPTR callbackFunc, MEMPTR callbackParam) + { + InitSubmissionBuffer(); + this->m_callbackFunc = callbackFunc; + this->m_callbackParam = callbackParam; + nnResult r = coreinit::IOS_IoctlvAsync(g_fp.fpdHandle, (uint32)m_requestId.value(), m_numVecOut, m_numVecIn, m_vec, MEMPTR(PPCInterpreter_makeCallableExportDepr(AsyncHandler)), MEMPTR(this)); + owner.release(); + return r; + } + + private: + size_t GetVecInIndex(uint8 inIndex) + { + return m_numVecOut + inIndex; + } + + size_t GetVecOutIndex(uint8 outIndex) + { + return outIndex; + } + + void InitSubmissionBuffer() + { + // allocate a chunk of memory to hold the input/output vectors and their data + uint32 vecOffset[MAX_VEC_COUNT]; + uint32 totalBufferSize = 0; + for(uint8 i=0; i 0) + { + m_dataBuffer = FPIpcBufferAllocator.Allocate(totalBufferSize, 32); + cemu_assert_debug(m_dataBuffer); + } + // update Ioctl vector addresses + for(uint8 i=0; i(m_vec[vecIndex].baseVirt).GetPtr(), MEMPTR(m_vecOriginalAddress[vecIndex]).GetPtr(), m_vec[vecIndex].size); + } + } + + static void AsyncHandler(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(result, 0); + ppcDefineParamPtr(ipcCtx, FPIpcContext, 1); + ipcCtx->m_asyncResult = result; // store result in variable since FP callbacks pass a pointer to nnResult and not the value directly + ipcCtx->CopyBackOutputs(); + PPCCoreCallback(ipcCtx->m_callbackFunc, &ipcCtx->m_asyncResult, ipcCtx->m_callbackParam); + delete ipcCtx; + osLib_returnFromFunction(hCPU, 0); + } + + void CopyBackOutputs() + { + if(m_numVecOut > 0) + { + // copy output from temporary output buffers to the original addresses + for(uint8 i=0; i m_requestId; + uint8be m_numVecIn{0}; + uint8be m_numVecOut{0}; + IPCIoctlVector m_vec[MAX_VEC_COUNT]; + MEMPTR m_vecOriginalAddress[MAX_VEC_COUNT]{}; + MEMPTR m_dataBuffer{nullptr}; + MEMPTR m_callbackFunc{nullptr}; + MEMPTR m_callbackParam{nullptr}; + betype m_asyncResult; + }; + + struct FPGlobalLock + { + FPGlobalLock() + { + coreinit::OSLockMutex(&g_fp.fpMutex); + } + ~FPGlobalLock() + { + coreinit::OSUnlockMutex(&g_fp.fpMutex); + } + }; + #define FP_API_BASE() if (g_fp.initCounter == 0) return 0xC0C00580; FPGlobalLock _fpLock; + #define FP_API_BASE_ZeroOnError() if (g_fp.initCounter == 0) return 0; FPGlobalLock _fpLock; + + nnResult Initialize() + { + FPGlobalLock _fpLock; + if (g_fp.initCounter == 0) + { + g_fp.fpdHandle = coreinit::IOS_Open("/dev/fpd", 0); + } + g_fp.initCounter++; + return FPResult_OkZero; + } + + uint32 IsInitialized() + { + FPGlobalLock _fpLock; + return g_fp.initCounter > 0 ? 1 : 0; + } + + nnResult InitializeAdmin(PPCInterpreter_t* hCPU) + { + FPGlobalLock _fpLock; + g_fp.isAdminMode = true; + return Initialize(); + } + + uint32 IsInitializedAdmin() + { + FPGlobalLock _fpLock; + return g_fp.initCounter > 0 ? 1 : 0; + } + + nnResult Finalize() + { + FPGlobalLock _fpLock; + if (g_fp.initCounter == 1) + { + g_fp.initCounter = 0; + g_fp.isAdminMode = false; + g_fp.isLoggedIn = false; + coreinit::IOS_Close(g_fp.fpdHandle); + g_fp.getNotificationCalled = false; + } + else if (g_fp.initCounter > 0) + g_fp.initCounter--; + return FPResult_OkZero; + } + + nnResult FinalizeAdmin() + { + return Finalize(); + } + + void GetNextNotificationAsync(); + + nnResult SetNotificationHandler(uint32 notificationMask, void* funcPtr, void* userParam) + { + FP_API_BASE(); + g_fp.notificationHandler = funcPtr; + g_fp.notificationHandlerParam = userParam; + StackAllocator notificationMaskBuf; notificationMaskBuf = notificationMask; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::SetNotificationMask); + ipcCtx->AddInput(¬ificationMaskBuf, sizeof(uint32be)); + nnResult r = ipcCtx->Submit(std::move(ipcCtx)); + if (NN_RESULT_IS_SUCCESS(r)) + { + // async query for notifications + GetNextNotificationAsync(); + } + return r; + } + + void GetNextNotificationAsyncHandler(PPCInterpreter_t* hCPU) + { + coreinit::OSLockMutex(&g_fp.fpMutex); + cemu_assert_debug(g_fp.getNotificationCalled); + g_fp.getNotificationCalled = false; + auto bufPtr = g_fp.notificationBuffer.GetPtr(); + uint32 count = g_fp.notificationCount->value(); + if (count == 0) + { + GetNextNotificationAsync(); + coreinit::OSUnlockMutex(&g_fp.fpMutex); + osLib_returnFromFunction(hCPU, 0); return; } - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_LOGIN_ASYNC; - fpdRequest->loginAsync.funcPtr = funcPtr; - fpdRequest->loginAsync.custom = custom; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + // copy notifications to temporary buffer using std::copy + iosu::fpd::FPDNotification tempBuffer[256]; + std::copy(g_fp.notificationBuffer.GetPtr(), g_fp.notificationBuffer.GetPtr() + count, tempBuffer); + // call handler for each notification, but do it outside of the lock + void* notificationHandler = g_fp.notificationHandler; + void* notificationHandlerParam = g_fp.notificationHandlerParam; + coreinit::OSUnlockMutex(&g_fp.fpMutex); + iosu::fpd::FPDNotification* notificationBuffer = g_fp.notificationBuffer.GetPtr(); + for (uint32 i = 0; i < count; i++) + PPCCoreCallback(notificationHandler, (uint32)notificationBuffer[i].type, notificationBuffer[i].pid, notificationHandlerParam); + coreinit::OSLockMutex(&g_fp.fpMutex); + // query more notifications + GetNextNotificationAsync(); + coreinit::OSUnlockMutex(&g_fp.fpMutex); osLib_returnFromFunction(hCPU, 0); } - void export_HasLoggedIn(PPCInterpreter_t* hCPU) + void GetNextNotificationAsync() { - // Sonic All Star Racing needs this - cemuLog_logDebug(LogType::Force, "nn_fp.HasLoggedIn()"); - osLib_returnFromFunction(hCPU, 1); + if (g_fp.getNotificationCalled) + return; + g_fp.getNotificationCalled = true; + g_fp.notificationCount = 0; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetNotificationAsync); + ipcCtx->AddOutput(g_fp.notificationBuffer.GetPtr(), g_fp.notificationBuffer.GetByteSize()); + ipcCtx->AddOutput(g_fp.notificationCount.GetPtr(), sizeof(uint32be)); + cemu_assert_debug(g_fp.notificationBuffer.GetByteSize() == 0x800); + nnResult r = ipcCtx->SubmitAsync(std::move(ipcCtx), MEMPTR(PPCInterpreter_makeCallableExportDepr(GetNextNotificationAsyncHandler)), nullptr); } - void export_IsOnline(PPCInterpreter_t* hCPU) + nnResult LoginAsync(void* funcPtr, void* userParam) { - //cemuLog_logDebug(LogType::Force, "nn_fp.IsOnline();"); - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_IS_ONLINE; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - cemuLog_logDebug(LogType::Force, "nn_fp.IsOnline() -> {}", fpdRequest->resultU32.u32); - - osLib_returnFromFunction(hCPU, fpdRequest->resultU32.u32); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::LoginAsync); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, userParam); } - void export_GetFriendList(PPCInterpreter_t* hCPU) + uint32 HasLoggedIn() { - ppcDefineParamMEMPTR(pidList, uint32be, 0); - ppcDefineParamMEMPTR(returnedCount, uint32be, 1); - ppcDefineParamU32(startIndex, 2); - ppcDefineParamU32(maxCount, 3); - - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendList(...)"); - //debug_printf("nn_fp.GetFriendList(0x%08x, 0x%08x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_LIST; - - fpdRequest->getFriendList.pidList = pidList; - fpdRequest->getFriendList.startIndex = startIndex; - fpdRequest->getFriendList.maxCount = maxCount; - - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - *returnedCount = fpdRequest->resultU32.u32; - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE_ZeroOnError(); + // Sonic All Star Racing uses this + // and Monster Hunter 3 Ultimate needs this to return false at least once to initiate login and not get stuck + // this returns false until LoginAsync was called and has completed (?) even if the user is already logged in + StackAllocator resultBuf; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::HasLoggedIn); + ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); + ipcCtx->Submit(std::move(ipcCtx)); + return resultBuf != 0 ? 1 : 0; } - void export_GetFriendRequestList(PPCInterpreter_t* hCPU) + uint32 IsOnline() { - // GetFriendRequestList__Q2_2nn2fpFPUiT1UiT3 - ppcDefineParamMEMPTR(pidList, uint32be, 0); - ppcDefineParamMEMPTR(returnedCount, uint32be, 1); - ppcDefineParamU32(startIndex, 2); - ppcDefineParamU32(maxCount, 3); - - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendRequestList(...)"); - //debug_printf("nn_fp.GetFriendList(0x%08x, 0x%08x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIENDREQUEST_LIST; - - fpdRequest->getFriendList.pidList = pidList; - fpdRequest->getFriendList.startIndex = startIndex; - fpdRequest->getFriendList.maxCount = maxCount; - - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - *returnedCount = fpdRequest->resultU32.u32; - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE_ZeroOnError(); + StackAllocator resultBuf; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::IsOnline); + ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); + ipcCtx->Submit(std::move(ipcCtx)); + return resultBuf != 0 ? 1 : 0; } - void export_GetFriendListAll(PPCInterpreter_t* hCPU) + nnResult GetFriendList(uint32be* pidList, uint32be* returnedCount, uint32 startIndex, uint32 maxCount) { - ppcDefineParamMEMPTR(pidList, uint32be, 0); - ppcDefineParamMEMPTR(returnedCount, uint32be, 1); - ppcDefineParamU32(startIndex, 2); - ppcDefineParamU32(maxCount, 3); - - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendListAll(...)"); - - //debug_printf("nn_fp.GetFriendListAll(0x%08x, 0x%08x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_LIST_ALL; - - fpdRequest->getFriendList.pidList = pidList; - fpdRequest->getFriendList.startIndex = startIndex; - fpdRequest->getFriendList.maxCount = maxCount; - - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - *returnedCount = fpdRequest->resultU32.u32; - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator startIndexBuf; startIndexBuf = startIndex; + StackAllocator maxCountBuf; maxCountBuf = maxCount; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendList); + ipcCtx->AddOutput(pidList, sizeof(uint32be) * maxCount); + ipcCtx->AddOutput(returnedCount, sizeof(uint32be)); + ipcCtx->AddInput(&startIndexBuf, sizeof(uint32be)); + ipcCtx->AddInput(&maxCountBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendListEx(PPCInterpreter_t* hCPU) + nnResult GetFriendRequestList(uint32be* pidList, uint32be* returnedCount, uint32 startIndex, uint32 maxCount) { - ppcDefineParamMEMPTR(friendData, iosu::fpd::friendData_t, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendListEx(...)"); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_LIST_EX; - - fpdRequest->getFriendListEx.friendData = friendData; - fpdRequest->getFriendListEx.pidList = pidList; - fpdRequest->getFriendListEx.count = count; - - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator startIndexBuf; startIndexBuf = startIndex; + StackAllocator maxCountBuf; maxCountBuf = maxCount; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendRequestList); + ipcCtx->AddOutput(pidList, sizeof(uint32be) * maxCount); + ipcCtx->AddOutput(returnedCount, sizeof(uint32be)); + ipcCtx->AddInput(&startIndexBuf, sizeof(uint32be)); + ipcCtx->AddInput(&maxCountBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendRequestListEx(PPCInterpreter_t* hCPU) + nnResult GetFriendListAll(uint32be* pidList, uint32be* returnedCount, uint32 startIndex, uint32 maxCount) { - ppcDefineParamMEMPTR(friendRequest, iosu::fpd::friendRequest_t, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendRequestListEx(...)"); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIENDREQUEST_LIST_EX; - - fpdRequest->getFriendRequestListEx.friendRequest = friendRequest; - fpdRequest->getFriendRequestListEx.pidList = pidList; - fpdRequest->getFriendRequestListEx.count = count; - - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator startIndexBuf; startIndexBuf = startIndex; + StackAllocator maxCountBuf; maxCountBuf = maxCount; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendListAll); + ipcCtx->AddOutput(pidList, sizeof(uint32be) * maxCount); + ipcCtx->AddOutput(returnedCount, sizeof(uint32be)); + ipcCtx->AddInput(&startIndexBuf, sizeof(uint32be)); + ipcCtx->AddInput(&maxCountBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetBasicInfoAsync(PPCInterpreter_t* hCPU) + nnResult GetFriendListEx(iosu::fpd::FriendData* friendData, uint32be* pidList, uint32 count) { - ppcDefineParamMEMPTR(basicInfo, iosu::fpd::friendBasicInfo_t, 0); - ppcDefineParamTypePtr(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - ppcDefineParamMPTR(funcMPTR, 3); - ppcDefineParamU32(customParam, 4); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_BASIC_INFO_ASYNC; - fpdRequest->getBasicInfo.basicInfo = basicInfo; - fpdRequest->getBasicInfo.pidList = pidList; - fpdRequest->getBasicInfo.count = count; - fpdRequest->getBasicInfo.funcPtr = funcMPTR; - fpdRequest->getBasicInfo.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector);; - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendListEx); + ipcCtx->AddOutput(friendData, sizeof(iosu::fpd::FriendData) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetMyPrincipalId(PPCInterpreter_t* hCPU) + nnResult GetFriendRequestListEx(iosu::fpd::FriendRequest* friendRequest, uint32be* pidList, uint32 count) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetMyPrincipalId()"); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_PRINCIPAL_ID; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - uint32 principalId = fpdRequest->resultU32.u32; - - osLib_returnFromFunction(hCPU, principalId); + FP_API_BASE(); + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendRequestListEx); + ipcCtx->AddOutput(friendRequest, sizeof(iosu::fpd::FriendRequest) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetMyAccountId(PPCInterpreter_t* hCPU) + nnResult GetBasicInfoAsync(iosu::fpd::FriendBasicInfo* basicInfo, uint32be* pidList, uint32 count, void* funcPtr, void* customParam) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetMyAccountId(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamTypePtr(accountId, uint8, 0); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_ACCOUNT_ID; - fpdRequest->common.ptr = (void*)accountId; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetBasicInfoAsync); + ipcCtx->AddOutput(basicInfo, sizeof(iosu::fpd::FriendBasicInfo) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_GetMyScreenName(PPCInterpreter_t* hCPU) + uint32 GetMyPrincipalId() { - cemuLog_logDebug(LogType::Force, "nn_fp.GetMyScreenName(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamTypePtr(screenname, uint16be, 0); - - screenname[0] = '\0'; - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_SCREENNAME; - fpdRequest->common.ptr = (void*)screenname; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, 0); + FP_API_BASE_ZeroOnError(); + StackAllocator resultBuf; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyPrincipalId); + ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); + ipcCtx->Submit(std::move(ipcCtx)); + return resultBuf->value(); } - typedef struct + nnResult GetMyAccountId(uint8be* accountId) { - uint8 showOnline; // show online status to others - uint8 showGame; // show played game to others - uint8 blockFriendRequests; // block friend requests - }fpPerference_t; - - void export_GetMyPreference(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "nn_fp.GetMyPreference(0x{:08x}) - placeholder", hCPU->gpr[3]); - ppcDefineParamTypePtr(pref, fpPerference_t, 0); - - pref->showOnline = 1; - pref->showGame = 1; - pref->blockFriendRequests = 0; - - osLib_returnFromFunction(hCPU, 0); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyAccountId); + ipcCtx->AddOutput(accountId, ACT_ACCOUNTID_LENGTH); + return ipcCtx->Submit(std::move(ipcCtx)); } - // GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference - - void export_GetMyMii(PPCInterpreter_t* hCPU) + nnResult GetMyScreenName(uint16be* screenname) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetMyMii(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamTypePtr(fflData, FFLData_t, 0); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_MII; - fpdRequest->common.ptr = (void*)fflData; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyScreenName); + ipcCtx->AddOutput(screenname, ACT_NICKNAME_SIZE*sizeof(uint16)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendAccountId(PPCInterpreter_t* hCPU) + nnResult GetMyPreference(iosu::fpd::FPDPreference* myPreference) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendAccountId(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamMEMPTR(accountIds, char, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_ACCOUNT_ID; - fpdRequest->getFriendAccountId.accountIds = accountIds; - fpdRequest->getFriendAccountId.pidList = pidList; - fpdRequest->getFriendAccountId.count = count; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyPreference); + ipcCtx->AddOutput(myPreference, sizeof(iosu::fpd::FPDPreference)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendScreenName(PPCInterpreter_t* hCPU) + nnResult GetMyMii(FFLData_t* fflData) { - // GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendScreenName(0x{:08x},0x{:08x},0x{:08x},{},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); - ppcDefineParamMEMPTR(nameList, uint16be, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - ppcDefineParamU32(replaceNonAscii, 3); - ppcDefineParamMEMPTR(languageList, uint8, 4); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_SCREENNAME; - fpdRequest->getFriendScreenname.nameList = nameList; - fpdRequest->getFriendScreenname.pidList = pidList; - fpdRequest->getFriendScreenname.count = count; - fpdRequest->getFriendScreenname.replaceNonAscii = replaceNonAscii != 0; - fpdRequest->getFriendScreenname.languageList = languageList; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyMii); + ipcCtx->AddOutput(fflData, sizeof(FFLData_t)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendMii(PPCInterpreter_t* hCPU) + nnResult GetFriendAccountId(uint8be* accountIdArray, uint32be* pidList, uint32 count) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendMii(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamMEMPTR(miiList, FFLData_t, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_MII; - fpdRequest->getFriendMii.miiList = (uint8*)miiList.GetPtr(); - fpdRequest->getFriendMii.pidList = pidList; - fpdRequest->getFriendMii.count = count; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + if (count == 0) + return 0; + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendAccountId); + ipcCtx->AddOutput(accountIdArray, ACT_ACCOUNTID_LENGTH * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendPresence(PPCInterpreter_t* hCPU) + nnResult GetFriendScreenName(uint16be* nameList, uint32be* pidList, uint32 count, uint8 replaceNonAscii, uint8be* languageList) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendPresence(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamMEMPTR(presenceList, uint8, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_PRESENCE; - fpdRequest->getFriendPresence.presenceList = (uint8*)presenceList.GetPtr(); - fpdRequest->getFriendPresence.pidList = pidList; - fpdRequest->getFriendPresence.count = count; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + if (count == 0) + return 0; + StackAllocator countBuf; countBuf = count; + StackAllocator replaceNonAsciiBuf; replaceNonAsciiBuf = replaceNonAscii; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendScreenName); + ipcCtx->AddOutput(nameList, ACT_NICKNAME_SIZE * sizeof(uint16be) * count); + ipcCtx->AddOutput(languageList, languageList ? sizeof(uint8be) * count : 0); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + ipcCtx->AddInput(&replaceNonAsciiBuf, sizeof(uint8be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_GetFriendRelationship(PPCInterpreter_t* hCPU) + nnResult GetFriendMii(FFLData_t* miiList, uint32be* pidList, uint32 count) { - cemuLog_logDebug(LogType::Force, "nn_fp.GetFriendRelationship(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamMEMPTR(relationshipList, uint8, 0); - ppcDefineParamMEMPTR(pidList, uint32be, 1); - ppcDefineParamU32(count, 2); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_RELATIONSHIP; - fpdRequest->getFriendRelationship.relationshipList = (uint8*)relationshipList.GetPtr(); - fpdRequest->getFriendRelationship.pidList = pidList; - fpdRequest->getFriendRelationship.count = count; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + if(count == 0) + return 0; + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendMii); + ipcCtx->AddOutput(miiList, sizeof(FFLData_t) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_IsJoinable(PPCInterpreter_t* hCPU) + nnResult GetFriendPresence(iosu::fpd::FriendPresence* presenceList, uint32be* pidList, uint32 count) { - ppcDefineParamTypePtr(presence, iosu::fpd::friendPresence_t, 0); - ppcDefineParamU64(joinMask, 2); + FP_API_BASE(); + if(count == 0) + return 0; + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendPresence); + ipcCtx->AddOutput(presenceList, sizeof(iosu::fpd::FriendPresence) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); + } + nnResult GetFriendRelationship(uint8* relationshipList, uint32be* pidList, uint32 count) + { + FP_API_BASE(); + if(count == 0) + return 0; + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetFriendRelationship); + ipcCtx->AddOutput(relationshipList, sizeof(uint8) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->Submit(std::move(ipcCtx)); + } + + uint32 IsJoinable(iosu::fpd::FriendPresence* presence, uint64 joinMask) + { if (presence->isValid == 0 || presence->isOnline == 0 || presence->gameMode.joinGameId == 0 || @@ -428,368 +557,237 @@ namespace nn presence->gameMode.groupId == 0 || presence->gameMode.joinGameMode >= 64 ) { - osLib_returnFromFunction(hCPU, 0); - return; + return 0; } uint32 joinGameMode = presence->gameMode.joinGameMode; uint64 joinModeMask = (1ULL<gameMode.joinFlagMask; if (joinFlagMask == 0) - { - osLib_returnFromFunction(hCPU, 0); - return; - } + return 0; if (joinFlagMask == 1) - { - osLib_returnFromFunction(hCPU, 1); - return; - } + return 1; if (joinFlagMask == 2) { - // check relationship + // check relationship uint8 relationship[1] = { 0 }; StackAllocator pidList; - pidList[0] = presence->gameMode.hostPid; - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_RELATIONSHIP; - fpdRequest->getFriendRelationship.relationshipList = relationship; - fpdRequest->getFriendRelationship.pidList = pidList.GetPointer(); - fpdRequest->getFriendRelationship.count = 1; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - + pidList = presence->gameMode.hostPid; + GetFriendRelationship(relationship, &pidList, 1); if(relationship[0] == iosu::fpd::RELATIONSHIP_FRIEND) - osLib_returnFromFunction(hCPU, 1); - else - osLib_returnFromFunction(hCPU, 0); - return; + return 1; + return 0; } if (joinFlagMask == 0x65 || joinFlagMask == 0x66) { cemuLog_log(LogType::Force, "Unsupported friend invite"); } - - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_CheckSettingStatusAsync(PPCInterpreter_t* hCPU) + nnResult CheckSettingStatusAsync(uint8* status, void* funcPtr, void* customParam) { - cemuLog_logDebug(LogType::Force, "nn_fp.CheckSettingStatusAsync(0x{:08x},0x{:08x},0x{:08x}) - placeholder", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamTypePtr(uknR3, uint8, 0); - ppcDefineParamMPTR(funcMPTR, 1); - ppcDefineParamU32(customParam, 2); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::CheckSettingStatusAsync); + ipcCtx->AddOutput(status, sizeof(uint8be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); + } - if (g_fp.isAdminMode == false) + uint32 IsPreferenceValid() + { + FP_API_BASE_ZeroOnError(); + StackAllocator resultBuf; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::IsPreferenceValid); + ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); + ipcCtx->Submit(std::move(ipcCtx)); + return resultBuf != 0 ? 1 : 0; + } + + nnResult UpdatePreferenceAsync(iosu::fpd::FPDPreference* newPreference, void* funcPtr, void* customParam) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::UpdatePreferenceAsync); + ipcCtx->AddInput(newPreference, sizeof(iosu::fpd::FPDPreference)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); + } + + nnResult UpdateGameModeWithUnusedParam(iosu::fpd::GameMode* gameMode, uint16be* gameModeMessage, uint32 unusedParam) + { + FP_API_BASE(); + uint32 messageLen = CafeStringHelpers::Length(gameModeMessage, iosu::fpd::GAMEMODE_MAX_MESSAGE_LENGTH); + if(messageLen >= iosu::fpd::GAMEMODE_MAX_MESSAGE_LENGTH) { - - osLib_returnFromFunction(hCPU, 0xC0C00800); - return; + cemuLog_log(LogType::Force, "UpdateGameMode: message too long"); + return FPResult_InvalidIPCParam; } - - *uknR3 = 1; - - StackAllocator callbackResultCode; - - *callbackResultCode.GetPointer() = 0; - - hCPU->gpr[3] = callbackResultCode.GetMPTR(); - hCPU->gpr[4] = customParam; - PPCCore_executeCallbackInternal(funcMPTR); - - osLib_returnFromFunction(hCPU, 0); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::UpdateGameModeVariation2); + ipcCtx->AddInput(gameMode, sizeof(iosu::fpd::GameMode)); + ipcCtx->AddInput(gameModeMessage, sizeof(uint16be) * (messageLen + 1)); + return ipcCtx->Submit(std::move(ipcCtx)); } - void export_IsPreferenceValid(PPCInterpreter_t* hCPU) + nnResult UpdateGameMode(iosu::fpd::GameMode* gameMode, uint16be* gameModeMessage) { - cemuLog_logDebug(LogType::Force, "nn_fp.IsPreferenceValid()"); - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_IS_PREFERENCE_VALID; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->resultU32.u32); + return UpdateGameModeWithUnusedParam(gameMode, gameModeMessage, 0); } - void export_UpdatePreferenceAsync(PPCInterpreter_t* hCPU) + nnResult GetRequestBlockSettingAsync(uint8* blockSettingList, uint32be* pidList, uint32 count, void* funcPtr, void* customParam) { - cemuLog_logDebug(LogType::Force, "nn_fp.UpdatePreferenceAsync(0x{:08x},0x{:08x},0x{:08x}) - placeholder", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamTypePtr(uknR3, uint8, 0); - ppcDefineParamMPTR(funcMPTR, 1); - ppcDefineParamU32(customParam, 2); - - if (g_fp.isAdminMode == false) - { - - osLib_returnFromFunction(hCPU, 0xC0C00800); - return; - } - - //*uknR3 = 0; // seems to be 3 bytes (nn::fp::Preference const *) - - StackAllocator callbackResultCode; - - *callbackResultCode.GetPointer() = 0; - - hCPU->gpr[3] = callbackResultCode.GetMPTR(); - hCPU->gpr[4] = customParam; - PPCCore_executeCallbackInternal(funcMPTR); - - osLib_returnFromFunction(hCPU, 0); + FP_API_BASE(); + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetRequestBlockSettingAsync); + ipcCtx->AddOutput(blockSettingList, sizeof(uint8be) * count); + ipcCtx->AddInput(pidList, sizeof(uint32be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_UpdateGameMode(PPCInterpreter_t* hCPU) + // overload of AddFriendAsync + nnResult AddFriendAsyncByPid(uint32 pid, void* funcPtr, void* customParam) { - cemuLog_logDebug(LogType::Force, "nn_fp.UpdateGameMode(0x{:08x},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - ppcDefineParamMEMPTR(gameMode, iosu::fpd::gameMode_t, 0); - ppcDefineParamMEMPTR(gameModeMessage, uint16be, 1); - ppcDefineParamU32(uknR5, 2); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_UPDATE_GAMEMODE; - fpdRequest->updateGameMode.gameMode = gameMode; - fpdRequest->updateGameMode.gameModeMessage = gameModeMessage; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, 0); + FP_API_BASE(); + StackAllocator pidBuf; pidBuf = pid; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::AddFriendAsyncByPid); + ipcCtx->AddInput(&pidBuf, sizeof(uint32be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_GetRequestBlockSettingAsync(PPCInterpreter_t* hCPU) + nnResult DeleteFriendFlagsAsync(uint32be* pidList, uint32 pidCount, uint32 ukn, void* funcPtr, void* customParam) { - ppcDefineParamTypePtr(settingList, uint8, 0); - ppcDefineParamTypePtr(pidList, uint32be, 1); - ppcDefineParamU32(pidCount, 2); - ppcDefineParamMPTR(funcMPTR, 3); - ppcDefineParamMPTR(customParam, 4); - - cemuLog_logDebug(LogType::Force, "GetRequestBlockSettingAsync(...) - todo"); - - for (uint32 i = 0; i < pidCount; i++) - settingList[i] = 0; - // 0 means not blocked. Friend app will continue with GetBasicInformation() - // 1 means blocked. Friend app will continue with AddFriendAsync to add the user as a provisional friend - - StackAllocator callbackResultCode; - - *callbackResultCode.GetPointer() = 0; - - hCPU->gpr[3] = callbackResultCode.GetMPTR(); - hCPU->gpr[4] = customParam; - PPCCore_executeCallbackInternal(funcMPTR); - - osLib_returnFromFunction(hCPU, 0); + // admin function? + FP_API_BASE(); + StackAllocator pidCountBuf; pidCountBuf = pidCount; + StackAllocator uknBuf; uknBuf = ukn; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::DeleteFriendFlagsAsync); + ipcCtx->AddInput(pidList, sizeof(uint32be) * pidCount); + ipcCtx->AddInput(&pidCountBuf, sizeof(uint32be)); + ipcCtx->AddInput(&uknBuf, sizeof(uint32be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_AddFriendAsync(PPCInterpreter_t* hCPU) + // overload of AddFriendRequestAsync + nnResult AddFriendRequestByPlayRecordAsync(iosu::fpd::RecentPlayRecordEx* playRecord, uint16be* message, void* funcPtr, void* customParam) { - // AddFriendAsync__Q2_2nn2fpFPCcPFQ2_2nn6ResultPv_vPv - ppcDefineParamU32(principalId, 0); - ppcDefineParamMPTR(funcMPTR, 1); - ppcDefineParamMPTR(customParam, 2); - -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); -#endif - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_ADD_FRIEND; - fpdRequest->addOrRemoveFriend.pid = principalId; - fpdRequest->addOrRemoveFriend.funcPtr = funcMPTR; - fpdRequest->addOrRemoveFriend.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync); + uint32 messageLen = 0; + while(message[messageLen] != 0) + messageLen++; + ipcCtx->AddInput(playRecord, sizeof(iosu::fpd::RecentPlayRecordEx)); + ipcCtx->AddInput(message, sizeof(uint16be) * (messageLen+1)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - - void export_DeleteFriendFlagsAsync(PPCInterpreter_t* hCPU) + nnResult RemoveFriendAsync(uint32 pid, void* funcPtr, void* customParam) { - cemuLog_logDebug(LogType::Force, "nn_fp.DeleteFriendFlagsAsync(...) - todo"); - ppcDefineParamU32(uknR3, 0); // example value: pointer - ppcDefineParamU32(uknR4, 1); // example value: 1 - ppcDefineParamU32(uknR5, 2); // example value: 1 - ppcDefineParamMPTR(funcMPTR, 3); - ppcDefineParamU32(customParam, 4); - - if (g_fp.isAdminMode == false) - { - osLib_returnFromFunction(hCPU, 0xC0C00800); - return; - } - - StackAllocator callbackResultCode; - - *callbackResultCode.GetPointer() = 0; - - hCPU->gpr[3] = callbackResultCode.GetMPTR(); - hCPU->gpr[4] = customParam; - PPCCore_executeCallbackInternal(funcMPTR); - - osLib_returnFromFunction(hCPU, 0); + FP_API_BASE(); + StackAllocator pidBuf; pidBuf = pid; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::RemoveFriendAsync); + ipcCtx->AddInput(&pidBuf, sizeof(uint32be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - - typedef struct + nnResult MarkFriendRequestsAsReceivedAsync(uint64be* messageIdList, uint32 count, void* funcPtr, void* customParam) { - /* +0x00 */ uint32be pid; - /* +0x04 */ uint8 ukn04; - /* +0x05 */ uint8 ukn05; - /* +0x06 */ uint8 ukn06[0x22]; - /* +0x28 */ uint8 ukn28[0x22]; - /* +0x4A */ uint8 _uknOrPadding4A[6]; - /* +0x50 */ uint32be ukn50; - /* +0x54 */ uint32be ukn54; - /* +0x58 */ uint16be ukn58; - /* +0x5C */ uint8 _padding5C[4]; - /* +0x60 */ iosu::fpd::fpdDate_t date; - }RecentPlayRecordEx_t; - - static_assert(sizeof(RecentPlayRecordEx_t) == 0x68, ""); - static_assert(offsetof(RecentPlayRecordEx_t, ukn06) == 0x06, ""); - static_assert(offsetof(RecentPlayRecordEx_t, ukn50) == 0x50, ""); - - void export_AddFriendRequestAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(playRecord, RecentPlayRecordEx_t, 0); - ppcDefineParamTypePtr(message, uint16be, 1); - ppcDefineParamMPTR(funcMPTR, 2); - ppcDefineParamMPTR(customParam, 3); - - fpdPrepareRequest(); - - uint8* uknData = (uint8*)playRecord; - - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_ADD_FRIEND_REQUEST; - fpdRequest->addFriendRequest.pid = playRecord->pid; - fpdRequest->addFriendRequest.message = message; - fpdRequest->addFriendRequest.funcPtr = funcMPTR; - fpdRequest->addFriendRequest.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator countBuf; countBuf = count; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::MarkFriendRequestsAsReceivedAsync); + ipcCtx->AddInput(messageIdList, sizeof(uint64be) * count); + ipcCtx->AddInput(&countBuf, sizeof(uint32be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_RemoveFriendAsync(PPCInterpreter_t* hCPU) + nnResult CancelFriendRequestAsync(uint64 requestId, void* funcPtr, void* customParam) { - ppcDefineParamU32(principalId, 0); - ppcDefineParamMPTR(funcMPTR, 1); - ppcDefineParamMPTR(customParam, 2); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_REMOVE_FRIEND_ASYNC; - fpdRequest->addOrRemoveFriend.pid = principalId; - fpdRequest->addOrRemoveFriend.funcPtr = funcMPTR; - fpdRequest->addOrRemoveFriend.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator requestIdBuf; requestIdBuf = requestId; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::CancelFriendRequestAsync); + ipcCtx->AddInput(&requestIdBuf, sizeof(uint64be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_MarkFriendRequestsAsReceivedAsync(PPCInterpreter_t* hCPU) + nnResult DeleteFriendRequestAsync(uint64 requestId, void* funcPtr, void* customParam) { - ppcDefineParamTypePtr(messageIdList, uint64, 0); - ppcDefineParamU32(count, 1); - ppcDefineParamMPTR(funcMPTR, 2); - ppcDefineParamMPTR(customParam, 3); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC; - fpdRequest->markFriendRequest.messageIdList = messageIdList; - fpdRequest->markFriendRequest.count = count; - fpdRequest->markFriendRequest.funcPtr = funcMPTR; - fpdRequest->markFriendRequest.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator requestIdBuf; requestIdBuf = requestId; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::DeleteFriendRequestAsync); + ipcCtx->AddInput(&requestIdBuf, sizeof(uint64be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } - void export_CancelFriendRequestAsync(PPCInterpreter_t* hCPU) + nnResult AcceptFriendRequestAsync(uint64 requestId, void* funcPtr, void* customParam) { - ppcDefineParamU64(frqMessageId, 0); - ppcDefineParamMPTR(funcMPTR, 2); - ppcDefineParamMPTR(customParam, 3); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC; - fpdRequest->cancelOrAcceptFriendRequest.messageId = frqMessageId; - fpdRequest->cancelOrAcceptFriendRequest.funcPtr = funcMPTR; - fpdRequest->cancelOrAcceptFriendRequest.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); - } - - void export_AcceptFriendRequestAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(frqMessageId, 0); - ppcDefineParamMPTR(funcMPTR, 2); - ppcDefineParamMPTR(customParam, 3); - - fpdPrepareRequest(); - fpdRequest->requestCode = iosu::fpd::IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC; - fpdRequest->cancelOrAcceptFriendRequest.messageId = frqMessageId; - fpdRequest->cancelOrAcceptFriendRequest.funcPtr = funcMPTR; - fpdRequest->cancelOrAcceptFriendRequest.custom = customParam; - __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); - - osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + FP_API_BASE(); + StackAllocator requestIdBuf; requestIdBuf = requestId; + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::AcceptFriendRequestAsync); + ipcCtx->AddInput(&requestIdBuf, sizeof(uint64be)); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } void load() { - osLib_addFunction("nn_fp", "Initialize__Q2_2nn2fpFv", export_Initialize); - osLib_addFunction("nn_fp", "InitializeAdmin__Q2_2nn2fpFv", export_InitializeAdmin); - osLib_addFunction("nn_fp", "IsInitialized__Q2_2nn2fpFv", export_IsInitialized); - osLib_addFunction("nn_fp", "IsInitializedAdmin__Q2_2nn2fpFv", export_IsInitializedAdmin); + g_fp.initCounter = 0; + g_fp.isAdminMode = false; + g_fp.isLoggedIn = false; + g_fp.getNotificationCalled = false; + g_fp.notificationHandler = nullptr; + g_fp.notificationHandlerParam = nullptr; - osLib_addFunction("nn_fp", "SetNotificationHandler__Q2_2nn2fpFUiPFQ3_2nn2fp16NotificationTypeUiPv_vPv", export_SetNotificationHandler); + coreinit::OSInitMutex(&g_fp.fpMutex); + FPIpcBufferAllocator.Init(); - osLib_addFunction("nn_fp", "LoginAsync__Q2_2nn2fpFPFQ2_2nn6ResultPv_vPv", export_LoginAsync); - osLib_addFunction("nn_fp", "HasLoggedIn__Q2_2nn2fpFv", export_HasLoggedIn); + cafeExportRegisterFunc(Initialize, "nn_fp", "Initialize__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(InitializeAdmin, "nn_fp", "InitializeAdmin__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(IsInitialized, "nn_fp", "IsInitialized__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(IsInitializedAdmin, "nn_fp", "IsInitializedAdmin__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(Finalize, "nn_fp", "Finalize__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(FinalizeAdmin, "nn_fp", "FinalizeAdmin__Q2_2nn2fpFv", LogType::NN_FP); - osLib_addFunction("nn_fp", "IsOnline__Q2_2nn2fpFv", export_IsOnline); + cafeExportRegisterFunc(SetNotificationHandler, "nn_fp", "SetNotificationHandler__Q2_2nn2fpFUiPFQ3_2nn2fp16NotificationTypeUiPv_vPv", LogType::NN_FP); - osLib_addFunction("nn_fp", "GetFriendList__Q2_2nn2fpFPUiT1UiT3", export_GetFriendList); - osLib_addFunction("nn_fp", "GetFriendRequestList__Q2_2nn2fpFPUiT1UiT3", export_GetFriendRequestList); - osLib_addFunction("nn_fp", "GetFriendListAll__Q2_2nn2fpFPUiT1UiT3", export_GetFriendListAll); - osLib_addFunction("nn_fp", "GetFriendListEx__Q2_2nn2fpFPQ3_2nn2fp10FriendDataPCUiUi", export_GetFriendListEx); - osLib_addFunction("nn_fp", "GetFriendRequestListEx__Q2_2nn2fpFPQ3_2nn2fp13FriendRequestPCUiUi", export_GetFriendRequestListEx); - osLib_addFunction("nn_fp", "GetBasicInfoAsync__Q2_2nn2fpFPQ3_2nn2fp9BasicInfoPCUiUiPFQ2_2nn6ResultPv_vPv", export_GetBasicInfoAsync); + cafeExportRegisterFunc(LoginAsync, "nn_fp", "LoginAsync__Q2_2nn2fpFPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(HasLoggedIn, "nn_fp", "HasLoggedIn__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(IsOnline, "nn_fp", "IsOnline__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendList, "nn_fp", "GetFriendList__Q2_2nn2fpFPUiT1UiT3", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendRequestList, "nn_fp", "GetFriendRequestList__Q2_2nn2fpFPUiT1UiT3", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendListAll, "nn_fp", "GetFriendListAll__Q2_2nn2fpFPUiT1UiT3", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendListEx, "nn_fp", "GetFriendListEx__Q2_2nn2fpFPQ3_2nn2fp10FriendDataPCUiUi", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendRequestListEx, "nn_fp", "GetFriendRequestListEx__Q2_2nn2fpFPQ3_2nn2fp13FriendRequestPCUiUi", LogType::NN_FP); + cafeExportRegisterFunc(GetBasicInfoAsync, "nn_fp", "GetBasicInfoAsync__Q2_2nn2fpFPQ3_2nn2fp9BasicInfoPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); - osLib_addFunction("nn_fp", "GetMyPrincipalId__Q2_2nn2fpFv", export_GetMyPrincipalId); - osLib_addFunction("nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", export_GetMyAccountId); - osLib_addFunction("nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", export_GetMyScreenName); - osLib_addFunction("nn_fp", "GetMyMii__Q2_2nn2fpFP12FFLStoreData", export_GetMyMii); - osLib_addFunction("nn_fp", "GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference", export_GetMyPreference); + cafeExportRegisterFunc(GetMyPrincipalId, "nn_fp", "GetMyPrincipalId__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(GetMyAccountId, "nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", LogType::NN_FP); + cafeExportRegisterFunc(GetMyScreenName, "nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", LogType::NN_FP); + cafeExportRegisterFunc(GetMyMii, "nn_fp", "GetMyMii__Q2_2nn2fpFP12FFLStoreData", LogType::NN_FP); + cafeExportRegisterFunc(GetMyPreference, "nn_fp", "GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference", LogType::NN_FP); - osLib_addFunction("nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", export_GetFriendAccountId); - osLib_addFunction("nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", export_GetFriendScreenName); - osLib_addFunction("nn_fp", "GetFriendMii__Q2_2nn2fpFP12FFLStoreDataPCUiUi", export_GetFriendMii); - osLib_addFunction("nn_fp", "GetFriendPresence__Q2_2nn2fpFPQ3_2nn2fp14FriendPresencePCUiUi", export_GetFriendPresence); - osLib_addFunction("nn_fp", "GetFriendRelationship__Q2_2nn2fpFPUcPCUiUi", export_GetFriendRelationship); - osLib_addFunction("nn_fp", "IsJoinable__Q2_2nn2fpFPCQ3_2nn2fp14FriendPresenceUL", export_IsJoinable); + cafeExportRegisterFunc(GetFriendAccountId, "nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendScreenName, "nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendMii, "nn_fp", "GetFriendMii__Q2_2nn2fpFP12FFLStoreDataPCUiUi", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendPresence, "nn_fp", "GetFriendPresence__Q2_2nn2fpFPQ3_2nn2fp14FriendPresencePCUiUi", LogType::NN_FP); + cafeExportRegisterFunc(GetFriendRelationship, "nn_fp", "GetFriendRelationship__Q2_2nn2fpFPUcPCUiUi", LogType::NN_FP); + cafeExportRegisterFunc(IsJoinable, "nn_fp", "IsJoinable__Q2_2nn2fpFPCQ3_2nn2fp14FriendPresenceUL", LogType::NN_FP); - osLib_addFunction("nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", export_CheckSettingStatusAsync); - osLib_addFunction("nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", export_IsPreferenceValid); - osLib_addFunction("nn_fp", "UpdatePreferenceAsync__Q2_2nn2fpFPCQ3_2nn2fp10PreferencePFQ2_2nn6ResultPv_vPv", export_UpdatePreferenceAsync); + cafeExportRegisterFunc(CheckSettingStatusAsync, "nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(IsPreferenceValid, "nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(UpdatePreferenceAsync, "nn_fp", "UpdatePreferenceAsync__Q2_2nn2fpFPCQ3_2nn2fp10PreferencePFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(GetRequestBlockSettingAsync, "nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); - osLib_addFunction("nn_fp", "UpdateGameMode__Q2_2nn2fpFPCQ3_2nn2fp8GameModePCwUi", export_UpdateGameMode); - - osLib_addFunction("nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", export_GetRequestBlockSettingAsync); - - osLib_addFunction("nn_fp", "AddFriendAsync__Q2_2nn2fpFPCcPFQ2_2nn6ResultPv_vPv", export_AddFriendAsync); - osLib_addFunction("nn_fp", "AddFriendRequestAsync__Q2_2nn2fpFPCQ3_2nn2fp18RecentPlayRecordExPCwPFQ2_2nn6ResultPv_vPv", export_AddFriendRequestAsync); - osLib_addFunction("nn_fp", "DeleteFriendFlagsAsync__Q2_2nn2fpFPCUiUiT2PFQ2_2nn6ResultPv_vPv", export_DeleteFriendFlagsAsync); - - osLib_addFunction("nn_fp", "RemoveFriendAsync__Q2_2nn2fpFUiPFQ2_2nn6ResultPv_vPv", export_RemoveFriendAsync); - osLib_addFunction("nn_fp", "MarkFriendRequestsAsReceivedAsync__Q2_2nn2fpFPCULUiPFQ2_2nn6ResultPv_vPv", export_MarkFriendRequestsAsReceivedAsync); - osLib_addFunction("nn_fp", "CancelFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", export_CancelFriendRequestAsync); - osLib_addFunction("nn_fp", "AcceptFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", export_AcceptFriendRequestAsync); + cafeExportRegisterFunc(UpdateGameModeWithUnusedParam, "nn_fp", "UpdateGameMode__Q2_2nn2fpFPCQ3_2nn2fp8GameModePCwUi", LogType::NN_FP); + cafeExportRegisterFunc(UpdateGameMode, "nn_fp", "UpdateGameMode__Q2_2nn2fpFPCQ3_2nn2fp8GameModePCw", LogType::NN_FP); + cafeExportRegisterFunc(AddFriendAsyncByPid, "nn_fp", "AddFriendAsync__Q2_2nn2fpFUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(AddFriendRequestByPlayRecordAsync, "nn_fp", "AddFriendRequestAsync__Q2_2nn2fpFPCQ3_2nn2fp18RecentPlayRecordExPCwPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(DeleteFriendFlagsAsync, "nn_fp", "DeleteFriendFlagsAsync__Q2_2nn2fpFPCUiUiT2PFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(RemoveFriendAsync, "nn_fp", "RemoveFriendAsync__Q2_2nn2fpFUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(MarkFriendRequestsAsReceivedAsync, "nn_fp", "MarkFriendRequestsAsReceivedAsync__Q2_2nn2fpFPCULUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(CancelFriendRequestAsync, "nn_fp", "CancelFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(DeleteFriendRequestAsync, "nn_fp", "DeleteFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); + cafeExportRegisterFunc(AcceptFriendRequestAsync, "nn_fp", "AcceptFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); } } } diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp index d7555394..ad2ea203 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -180,7 +180,7 @@ struct bool hasOpenApplicationArea; // set to true if application area was opened or created // currently active Amiibo bool hasActiveAmiibo; - std::wstring amiiboPath; + fs::path amiiboPath; bool hasInvalidHMAC; uint32 amiiboTouchTime; AmiiboRawNFCData amiiboNFCData; // raw data @@ -188,7 +188,6 @@ struct AmiiboProcessedData amiiboProcessedData; }nfp_data = { 0 }; -bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError); bool nnNfp_writeCurrentAmiibo(); #include "AmiiboCrypto.h" @@ -216,7 +215,7 @@ void nnNfpExport_SetDeactivateEvent(PPCInterpreter_t* hCPU) ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); ppcDefineParamMPTR(osEventMPTR, 0); - cemuLog_log(LogType::nn_nfp, "SetDeactivateEvent(0x{:08x})", osEventMPTR); + cemuLog_log(LogType::NN_NFP, "SetDeactivateEvent(0x{:08x})", osEventMPTR); coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); @@ -241,7 +240,7 @@ void nnNfpExport_Initialize(PPCInterpreter_t* hCPU) void nnNfpExport_StartDetection(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "StartDetection()"); + cemuLog_log(LogType::NN_NFP, "StartDetection()"); nnNfpLock(); nfp_data.isDetecting = true; nnNfpUnlock(); @@ -250,7 +249,7 @@ void nnNfpExport_StartDetection(PPCInterpreter_t* hCPU) void nnNfpExport_StopDetection(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "StopDetection()"); + cemuLog_log(LogType::NN_NFP, "StopDetection()"); nnNfpLock(); nfp_data.isDetecting = false; nnNfpUnlock(); @@ -274,7 +273,7 @@ static_assert(sizeof(nfpTagInfo_t) == 0x54, "nfpTagInfo_t has invalid size"); void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "GetTagInfo(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "GetTagInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(tagInfo, nfpTagInfo_t, 0); nnNfpLock(); @@ -306,7 +305,7 @@ typedef struct 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); + cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); cemu_assert(index == 0); @@ -331,7 +330,7 @@ uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userP void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "Mount()"); + cemuLog_log(LogType::NN_NFP, "Mount()"); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { @@ -348,14 +347,14 @@ void nnNfpExport_Mount(PPCInterpreter_t* hCPU) void nnNfpExport_Unmount(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "Unmount()"); + cemuLog_log(LogType::NN_NFP, "Unmount()"); nfp_data.hasOpenApplicationArea = false; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_MountRom(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "MountRom()"); + cemuLog_log(LogType::NN_NFP, "MountRom()"); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { @@ -386,7 +385,7 @@ static_assert(sizeof(nfpRomInfo_t) == 0x36, "nfpRomInfo_t has invalid size"); void nnNfpExport_GetNfpRomInfo(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "GetNfpRomInfo(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "GetNfpRomInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(romInfo, nfpRomInfo_t, 0); nnNfpLock(); @@ -438,7 +437,7 @@ static_assert(offsetof(nfpCommonData_t, applicationAreaSize) == 0xE, "nfpCommonD void nnNfpExport_GetNfpCommonInfo(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "GetNfpCommonInfo(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "GetNfpCommonInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(commonInfo, nfpCommonData_t, 0); nnNfpLock(); @@ -492,7 +491,7 @@ typedef struct void nnNfpExport_GetNfpRegisterInfo(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "GetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "GetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfo, nfpRegisterInfo_t, 0); if(!registerInfo) @@ -515,7 +514,7 @@ void nnNfpExport_GetNfpRegisterInfo(PPCInterpreter_t* hCPU) void nnNfpExport_InitializeRegisterInfoSet(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "InitializeRegisterInfoSet(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "InitializeRegisterInfoSet(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); memset(registerInfoSet, 0, sizeof(nfpRegisterInfoSet_t)); @@ -525,7 +524,7 @@ void nnNfpExport_InitializeRegisterInfoSet(PPCInterpreter_t* hCPU) void nnNfpExport_SetNfpRegisterInfo(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "SetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "SetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); memcpy(nfp_data.amiiboInternal.amiiboSettings.mii, registerInfoSet->ownerMii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); @@ -538,7 +537,7 @@ void nnNfpExport_SetNfpRegisterInfo(PPCInterpreter_t* hCPU) void nnNfpExport_IsExistApplicationArea(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "IsExistApplicationArea()"); + cemuLog_log(LogType::NN_NFP, "IsExistApplicationArea()"); if (!nfp_data.hasActiveAmiibo || !nfp_data.isMounted) { osLib_returnFromFunction(hCPU, 0); @@ -550,7 +549,7 @@ void nnNfpExport_IsExistApplicationArea(PPCInterpreter_t* hCPU) void nnNfpExport_OpenApplicationArea(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "OpenApplicationArea(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "OpenApplicationArea(0x{:08x})", hCPU->gpr[3]); ppcDefineParamU32(appAreaId, 0); // note - this API doesn't fail if the application area has already been opened? @@ -575,7 +574,7 @@ void nnNfpExport_OpenApplicationArea(PPCInterpreter_t* hCPU) void nnNfpExport_ReadApplicationArea(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "ReadApplicationArea(0x{:08x}, 0x{:x})", hCPU->gpr[3], hCPU->gpr[4]); + cemuLog_log(LogType::NN_NFP, "ReadApplicationArea(0x{:08x}, 0x{:x})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamPtr(bufferPtr, uint8*, 0); ppcDefineParamU32(len, 1); @@ -592,7 +591,7 @@ void nnNfpExport_ReadApplicationArea(PPCInterpreter_t* hCPU) void nnNfpExport_WriteApplicationArea(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "WriteApplicationArea(0x{:08x}, 0x{:x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + cemuLog_log(LogType::NN_NFP, "WriteApplicationArea(0x{:08x}, 0x{:x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamPtr(bufferPtr, uint8*, 0); ppcDefineParamU32(len, 1); @@ -628,7 +627,7 @@ typedef struct void nnNfpExport_CreateApplicationArea(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "CreateApplicationArea(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "CreateApplicationArea(0x{:08x})", hCPU->gpr[3]); ppcDefineParamPtr(createInfo, NfpCreateInfo_t, 0); if (nfp_data.hasOpenApplicationArea || (nfp_data.amiiboInternal.amiiboSettings.flags&0x20)) @@ -677,7 +676,7 @@ void nnNfpExport_CreateApplicationArea(PPCInterpreter_t* hCPU) void nnNfpExport_DeleteApplicationArea(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "DeleteApplicationArea()"); + cemuLog_log(LogType::NN_NFP, "DeleteApplicationArea()"); if (nfp_data.isReadOnly) { @@ -707,7 +706,7 @@ void nnNfpExport_DeleteApplicationArea(PPCInterpreter_t* hCPU) void nnNfpExport_Flush(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "Flush()"); + cemuLog_log(LogType::NN_NFP, "Flush()"); // write Amiibo data if (nfp_data.isReadOnly) @@ -748,7 +747,7 @@ static_assert(offsetof(AmiiboSettingsArgs_t, commonInfo) == 0x114); void nnNfpExport_GetAmiiboSettingsArgs(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "GetAmiiboSettingsArgs(0x{:08x})", hCPU->gpr[3]); + cemuLog_log(LogType::NN_NFP, "GetAmiiboSettingsArgs(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(settingsArg, AmiiboSettingsArgs_t, 0); memset(settingsArg, 0, sizeof(AmiiboSettingsArgs_t)); @@ -770,7 +769,7 @@ void nnNfp_unloadAmiibo() nnNfpUnlock(); } -bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError) +bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; auto nfcData = FileStream::LoadIntoMemory(filePath); @@ -847,11 +846,11 @@ bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError) memcpy(&nfp_data.amiiboNFCData, &rawData, sizeof(AmiiboRawNFCData)); // decrypt amiibo amiiboDecrypt(); - nfp_data.amiiboPath = std::wstring(filePath); + nfp_data.amiiboPath = filePath; nfp_data.hasActiveAmiibo = true; if (nfp_data.activateEvent) { - coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.activateEvent); + MEMPTR osEvent(nfp_data.activateEvent); coreinit::OSSignalEvent(osEvent); } nfp_data.amiiboTouchTime = GetTickCount(); @@ -917,7 +916,7 @@ void nnNfp_update() void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::nn_nfp, "GetNfpState()"); + cemuLog_log(LogType::NN_NFP, "GetNfpState()"); // workaround for Mario Party 10 eating CPU cycles in an infinite loop (maybe due to incorrect NFP detection handling?) uint64 titleId = CafeSystem::GetForegroundTitleId(); diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h index 793b3bc3..e8a1c55f 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h @@ -8,7 +8,7 @@ namespace nn::nfp void nnNfp_load(); void nnNfp_update(); -bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError); +bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_NONE (0) #define NFP_STATE_INIT (1) diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp index 50036249..99c113c4 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -5,6 +5,7 @@ #include "nn_olv_DownloadCommunityTypes.h" #include "nn_olv_UploadFavoriteTypes.h" #include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" @@ -13,179 +14,6 @@ namespace nn { namespace olv { - struct DownloadedPostData_t - { - /* +0x0000 */ uint32be flags; - /* +0x0004 */ uint32be userPrincipalId; - /* +0x0008 */ char postId[0x20]; // size guessed - /* +0x0028 */ uint64 postDate; - /* +0x0030 */ uint8 feeling; - /* +0x0031 */ uint8 padding0031[3]; - /* +0x0034 */ uint32be regionId; - /* +0x0038 */ uint8 platformId; - /* +0x0039 */ uint8 languageId; - /* +0x003A */ uint8 countryId; - /* +0x003B */ uint8 padding003B[1]; - /* +0x003C */ uint16be bodyText[0x100]; // actual size is unknown - /* +0x023C */ uint32be bodyTextLength; - /* +0x0240 */ uint8 compressedMemoBody[0xA000]; // 40KB - /* +0xA240 */ uint32be compressedMemoBodyRelated; // size of compressed data? - /* +0xA244 */ uint16be topicTag[0x98]; - // app data - /* +0xA374 */ uint8 appData[0x400]; - /* +0xA774 */ uint32be appDataLength; - // external binary - /* +0xA778 */ uint8 externalBinaryUrl[0x100]; - /* +0xA878 */ uint32be externalBinaryDataSize; - // external image - /* +0xA87C */ uint8 externalImageDataUrl[0x100]; - /* +0xA97C */ uint32be externalImageDataSize; - // external url ? - /* +0xA980 */ char externalUrl[0x100]; - // mii - /* +0xAA80 */ uint8 miiData[0x60]; - /* +0xAAE0 */ uint16be miiNickname[0x20]; - /* +0xAB20 */ uint8 unusedAB20[0x14E0]; - - // everything above is part of DownloadedDataBase - // everything below is part of DownloadedPostData - /* +0xC000 */ uint8 uknDataC000[8]; // ?? - /* +0xC008 */ uint32be communityId; - /* +0xC00C */ uint32be empathyCount; - /* +0xC010 */ uint32be commentCount; - /* +0xC014 */ uint8 unused[0x1F4]; - }; // size: 0xC208 - - static_assert(sizeof(DownloadedPostData_t) == 0xC208, ""); - static_assert(offsetof(DownloadedPostData_t, postDate) == 0x0028, ""); - static_assert(offsetof(DownloadedPostData_t, platformId) == 0x0038, ""); - static_assert(offsetof(DownloadedPostData_t, bodyText) == 0x003C, ""); - static_assert(offsetof(DownloadedPostData_t, compressedMemoBody) == 0x0240, ""); - static_assert(offsetof(DownloadedPostData_t, topicTag) == 0xA244, ""); - static_assert(offsetof(DownloadedPostData_t, appData) == 0xA374, ""); - static_assert(offsetof(DownloadedPostData_t, externalBinaryUrl) == 0xA778, ""); - static_assert(offsetof(DownloadedPostData_t, externalImageDataUrl) == 0xA87C, ""); - static_assert(offsetof(DownloadedPostData_t, externalUrl) == 0xA980, ""); - static_assert(offsetof(DownloadedPostData_t, miiData) == 0xAA80, ""); - static_assert(offsetof(DownloadedPostData_t, miiNickname) == 0xAAE0, ""); - static_assert(offsetof(DownloadedPostData_t, unusedAB20) == 0xAB20, ""); - static_assert(offsetof(DownloadedPostData_t, communityId) == 0xC008, ""); - static_assert(offsetof(DownloadedPostData_t, empathyCount) == 0xC00C, ""); - static_assert(offsetof(DownloadedPostData_t, commentCount) == 0xC010, ""); - - const int POST_DATA_FLAG_HAS_BODY_TEXT = (0x0001); - const int POST_DATA_FLAG_HAS_BODY_MEMO = (0x0002); - - - void export_DownloadPostDataList(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedTopicData, void, 0); // DownloadedTopicData - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 1); // DownloadedPostData - ppcDefineParamTypePtr(downloadedPostDataSize, uint32be, 2); - ppcDefineParamS32(maxCount, 3); - ppcDefineParamTypePtr(listParam, void, 4); // DownloadPostDataListParam - - maxCount = 0; // DISABLED - - // just some test - for (sint32 i = 0; i < maxCount; i++) - { - DownloadedPostData_t* postData = downloadedPostData + i; - memset(postData, 0, sizeof(DownloadedPostData_t)); - postData->userPrincipalId = 0x1000 + i; - // post id - sprintf(postData->postId, "postid-%04x", i+(GetTickCount()%10000)); - postData->bodyTextLength = 12; - postData->bodyText[0] = 'H'; - postData->bodyText[1] = 'e'; - postData->bodyText[2] = 'l'; - postData->bodyText[3] = 'l'; - postData->bodyText[4] = 'o'; - postData->bodyText[5] = ' '; - postData->bodyText[6] = 'w'; - postData->bodyText[7] = 'o'; - postData->bodyText[8] = 'r'; - postData->bodyText[9] = 'l'; - postData->bodyText[10] = 'd'; - postData->bodyText[11] = '!'; - - postData->miiNickname[0] = 'C'; - postData->miiNickname[1] = 'e'; - postData->miiNickname[2] = 'm'; - postData->miiNickname[3] = 'u'; - postData->miiNickname[4] = '-'; - postData->miiNickname[5] = 'M'; - postData->miiNickname[6] = 'i'; - postData->miiNickname[7] = 'i'; - - postData->topicTag[0] = 't'; - postData->topicTag[1] = 'o'; - postData->topicTag[2] = 'p'; - postData->topicTag[3] = 'i'; - postData->topicTag[4] = 'c'; - - postData->flags = POST_DATA_FLAG_HAS_BODY_TEXT; - } - *downloadedPostDataSize = maxCount; - - osLib_returnFromFunction(hCPU, 0); - } - - void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - ppcDefineParamU32(testFlags, 1); - - if (((uint32)downloadedPostData->flags) & testFlags) - osLib_returnFromFunction(hCPU, 1); - else - osLib_returnFromFunction(hCPU, 0); - } - - void exportDownloadPostData_GetPostId(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->postId)); - } - - void exportDownloadPostData_GetMiiNickname(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - if(downloadedPostData->miiNickname[0] == 0 ) - osLib_returnFromFunction(hCPU, MPTR_NULL); - else - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->miiNickname)); - } - - void exportDownloadPostData_GetTopicTag(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->topicTag)); - } - - void exportDownloadPostData_GetBodyText(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - ppcDefineParamWStrBE(strOut, 1); - ppcDefineParamS32(maxLength, 2); - - if (((uint32)downloadedPostData->flags&POST_DATA_FLAG_HAS_BODY_TEXT) == 0) - { - osLib_returnFromFunction(hCPU, 0xC1106800); - return; - } - - memset(strOut, 0, sizeof(uint16be)*maxLength); - sint32 copyLen = std::min(maxLength - 1, (sint32)downloadedPostData->bodyTextLength); - for (sint32 i = 0; i < copyLen; i++) - { - strOut[i] = downloadedPostData->bodyText[i]; - } - strOut[copyLen] = '\0'; - - osLib_returnFromFunction(hCPU, 0); - } - struct PortalAppParam_t { /* +0x1A663B */ char serviceToken[32]; // size is unknown @@ -284,6 +112,10 @@ namespace nn void load() { + g_ReportTypes = 0; + g_IsOnlineMode = false; + g_IsInitialized = false; + g_IsOfflineDBMode = false; loadOliveInitializeTypes(); loadOliveUploadCommunityTypes(); @@ -291,27 +123,25 @@ namespace nn loadOliveUploadFavoriteTypes(); loadOlivePostAndTopicTypes(); - cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::None); - - osLib_addFunction("nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", export_DownloadPostDataList); -// osLib_addFunction("nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", exportDownloadPostData_TestFlags); -// osLib_addFunction("nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetPostId); -// osLib_addFunction("nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetMiiNickname); -// osLib_addFunction("nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetTopicTag); -// osLib_addFunction("nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", exportDownloadPostData_GetBodyText); + cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::NN_OLV); osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken); - cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Force); - cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadCommentDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv31UploadCommentDataByPostAppParam", LogType::Force); - cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadDirectMessageDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv37UploadDirectMessageDataByPostAppParam", LogType::Force); + cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::NN_OLV); + cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadCommentDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv31UploadCommentDataByPostAppParam", LogType::NN_OLV); + cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadDirectMessageDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv37UploadDirectMessageDataByPostAppParam", LogType::NN_OLV); - cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultByPostApp__Q2_2nn3olvFv", LogType::Force); - cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedPostDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv16UploadedPostData", LogType::Force); - cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedDirectMessageDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv25UploadedDirectMessageData", LogType::Force); - cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedCommentDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv19UploadedCommentData", LogType::Force); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultByPostApp__Q2_2nn3olvFv", LogType::NN_OLV); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedPostDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv16UploadedPostData", LogType::NN_OLV); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedDirectMessageDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv25UploadedDirectMessageData", LogType::NN_OLV); + cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedCommentDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv19UploadedCommentData", LogType::NN_OLV); - cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::Force); + cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::NN_OLV); + } + + void unload() // not called yet + { + OfflineDB_Shutdown(); } } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.h b/src/Cafe/OS/libs/nn_olv/nn_olv.h index c608e391..52474b49 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h @@ -19,5 +19,6 @@ namespace nn sint32 GetOlvAccessKey(uint32_t* pOutKey); void load(); + void unload(); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h index 718c10c3..c598e7ba 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h @@ -69,6 +69,7 @@ namespace nn extern uint32_t g_ReportTypes; extern bool g_IsInitialized; extern bool g_IsOnlineMode; + extern bool g_IsOfflineDBMode; // use offline cache for posts static void InitializeOliveRequest(CurlRequestHelper& req) { @@ -175,5 +176,39 @@ namespace nn bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId); sint32 olv_curlformcode_to_error(CURLFORMcode code); + + // convert and copy utf8 string into UC2 big-endian array + template + uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) + { + if(unescape) + { + // todo + } + std::wstring ws = boost::nowide::widen(sv); + size_t copyLen = std::min(TLength-1, ws.size()); + for(size_t i=0; i + uint32 SetStringUC2(uint16be(&str)[TLength], const uint16be* strIn) + { + size_t copyLen = TLength-1; + for(size_t i=0; i requestDoneEvent; - coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future requestRes = std::async(std::launch::async, DownloadCommunityDataList_AsyncRequest, std::ref(req), reqUrl, requestDoneEvent.GetPointer(), pOutList, pOutNum, numMaxList, pParam); - coreinit::OSWaitEvent(requestDoneEvent); + coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h index 3f5df35c..794195a1 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h @@ -501,30 +501,30 @@ namespace nn static void loadOliveDownloadCommunityTypes() { - cafeExportRegisterFunc(DownloadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv23DownloadedCommunityDataFv", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiData, "nn_olv", "GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData", LogType::None); - cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiNickname, "nn_olv", "GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv23DownloadedCommunityDataFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiData, "nn_olv", "GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiNickname, "nn_olv", "GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); - cafeExportRegisterFunc(DownloadCommunityDataListParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv30DownloadCommunityDataListParamFv", LogType::None); - cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); - cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetCommunityDataMaxNum, "nn_olv", "SetCommunityDataMaxNum__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); - cafeExportRegisterFunc(DownloadCommunityDataListParam::__GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv30DownloadCommunityDataListParamCFPcUi", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv30DownloadCommunityDataListParamFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetCommunityDataMaxNum, "nn_olv", "SetCommunityDataMaxNum__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv30DownloadCommunityDataListParamCFPcUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32 (*)(DownloadCommunityDataListParam*, uint32))DownloadCommunityDataListParam::__SetCommunityId, - "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(DownloadCommunityDataListParam*, uint32, uint8))DownloadCommunityDataListParam::__SetCommunityId, - "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUiUc", LogType::None); + "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUiUc", LogType::NN_OLV); - cafeExportRegisterFunc(DownloadCommunityDataList, "nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataList, "nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", LogType::NN_OLV); } } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp index 62a2654d..5e6dba7e 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp @@ -9,10 +9,10 @@ namespace nn { namespace olv { - uint32_t g_ReportTypes = 0; bool g_IsOnlineMode = false; bool g_IsInitialized = false; + bool g_IsOfflineDBMode = false; ParamPackStorage g_ParamPack; DiscoveryResultStorage g_DiscoveryResults; @@ -199,9 +199,9 @@ namespace nn InitializeOliveRequest(req); StackAllocator requestDoneEvent; - coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future requestRes = std::async(std::launch::async, MakeDiscoveryRequest_AsyncRequest, std::ref(req), requestUrl.c_str(), requestDoneEvent.GetPointer()); - coreinit::OSWaitEvent(requestDoneEvent); + coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } @@ -241,9 +241,15 @@ namespace nn g_IsInitialized = true; + if(ActiveSettings::GetNetworkService() == NetworkService::Nintendo) + { + // since the official Miiverse was shut down, use local post archive instead + g_IsOnlineMode = true; + g_IsOfflineDBMode = true; + return OLV_RESULT_SUCCESS; + } if ((pParam->m_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0) { - g_IsOnlineMode = true; independentServiceToken_t token; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h index 603b167c..51dce8fe 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h @@ -113,16 +113,16 @@ namespace nn static void loadOliveInitializeTypes() { - cafeExportRegisterFunc(Initialize, "nn_olv", "Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam", LogType::None); - cafeExportRegisterFunc(IsInitialized, "nn_olv", "IsInitialized__Q2_2nn3olvFv", LogType::None); - cafeExportRegisterFunc(Report::GetReportTypes, "nn_olv", "GetReportTypes__Q3_2nn3olv6ReportFv", LogType::None); - cafeExportRegisterFunc(Report::SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv6ReportFUi", LogType::None); + cafeExportRegisterFunc(Initialize, "nn_olv", "Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam", LogType::NN_OLV); + cafeExportRegisterFunc(IsInitialized, "nn_olv", "IsInitialized__Q2_2nn3olvFv", LogType::NN_OLV); + cafeExportRegisterFunc(Report::GetReportTypes, "nn_olv", "GetReportTypes__Q3_2nn3olv6ReportFv", LogType::NN_OLV); + cafeExportRegisterFunc(Report::SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv6ReportFUi", LogType::NN_OLV); - cafeExportRegisterFunc(InitializeParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv15InitializeParamFv", LogType::None); - cafeExportRegisterFunc(InitializeParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv15InitializeParamFUi", LogType::None); - cafeExportRegisterFunc(InitializeParam::__SetWork, "nn_olv", "SetWork__Q3_2nn3olv15InitializeParamFPUcUi", LogType::None); - cafeExportRegisterFunc(InitializeParam::__SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv15InitializeParamFUi", LogType::None); - cafeExportRegisterFunc(InitializeParam::__SetSysArgs, "nn_olv", "SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv15InitializeParamFv", LogType::NN_OLV); + cafeExportRegisterFunc(InitializeParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv15InitializeParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(InitializeParam::__SetWork, "nn_olv", "SetWork__Q3_2nn3olv15InitializeParamFPUcUi", LogType::NN_OLV); + cafeExportRegisterFunc(InitializeParam::__SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv15InitializeParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(InitializeParam::__SetSysArgs, "nn_olv", "SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", LogType::NN_OLV); } } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp new file mode 100644 index 00000000..309394e6 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp @@ -0,0 +1,215 @@ +#include "nn_olv_Common.h" +#include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" +#include "Cemu/ncrypto/ncrypto.h" // for base64 encoder/decoder +#include "util/helpers/helpers.h" +#include "config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" +#include +#include +#include + +namespace nn +{ + namespace olv + { + std::mutex g_offlineDBMutex; + bool g_offlineDBInitialized = false; + ZArchiveReader* g_offlineDBArchive{nullptr}; + + void OfflineDB_LazyInit() + { + std::scoped_lock _l(g_offlineDBMutex); + if(g_offlineDBInitialized) + return; + // open archive + g_offlineDBArchive = ZArchiveReader::OpenFromFile(ActiveSettings::GetUserDataPath("resources/miiverse/OfflineDB.zar")); + if(!g_offlineDBArchive) + cemuLog_log(LogType::Force, "Offline miiverse posts are not available"); + g_offlineDBInitialized = true; + } + + void OfflineDB_Shutdown() + { + std::scoped_lock _l(g_offlineDBMutex); + if(!g_offlineDBInitialized) + return; + delete g_offlineDBArchive; + g_offlineDBInitialized = false; + } + + bool CheckForOfflineDBFile(const char* filePath, uint32* fileSize) + { + if(!g_offlineDBArchive) + return false; + ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); + if (!g_offlineDBArchive->IsFile(fileHandle)) + return false; + if(fileSize) + *fileSize = g_offlineDBArchive->GetFileSize(fileHandle); + return true; + } + + bool LoadOfflineDBFile(const char* filePath, std::vector& fileData) + { + fileData.clear(); + if(!g_offlineDBArchive) + return false; + ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); + if (!g_offlineDBArchive->IsFile(fileHandle)) + return false; + fileData.resize(g_offlineDBArchive->GetFileSize(fileHandle)); + g_offlineDBArchive->ReadFromFile(fileHandle, 0, fileData.size(), fileData.data()); + return true; + } + + void TryLoadCompressedMemoImage(DownloadedPostData& downloadedPostData) + { + const unsigned char tgaHeader_320x120_32BPP[] = {0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x1,0x78,0x0,0x20,0x8}; + std::string memoImageFilename = fmt::format("memo/{}", (char*)downloadedPostData.downloadedDataBase.postId); + std::vector bitmaskCompressedImg; + if (!LoadOfflineDBFile(memoImageFilename.c_str(), bitmaskCompressedImg)) + return; + if (bitmaskCompressedImg.size() != (320*120)/8) + return; + std::vector decompressedImage; + decompressedImage.resize(sizeof(tgaHeader_320x120_32BPP) + 320 * 120 * 4); + memcpy(decompressedImage.data(), tgaHeader_320x120_32BPP, sizeof(tgaHeader_320x120_32BPP)); + uint8* pOut = decompressedImage.data() + sizeof(tgaHeader_320x120_32BPP); + for(int i=0; i<320*120; i++) + { + bool isWhite = (bitmaskCompressedImg[i/8] & (1 << (i%8))) != 0; + if(isWhite) + { + pOut[0] = pOut[1] = pOut[2] = pOut[3] = 0xFF; + } + else + { + pOut[0] = pOut[1] = pOut[2] = 0; + pOut[3] = 0xFF; + } + pOut += 4; + } + // store compressed image + uLongf compressedDestLen = 40960; + int r = compress((uint8*)downloadedPostData.downloadedDataBase.compressedMemoBody, &compressedDestLen, decompressedImage.data(), decompressedImage.size()); + if( r != Z_OK) + return; + downloadedPostData.downloadedDataBase.compressedMemoBodySize = compressedDestLen; + downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); + } + + void CheckForExternalImage(DownloadedPostData& downloadedPostData) + { + std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)downloadedPostData.downloadedDataBase.postId); + uint32 fileSize; + if (!CheckForOfflineDBFile(externalImageFilename.c_str(), &fileSize)) + return; + strcpy((char*)downloadedPostData.downloadedDataBase.externalImageDataUrl, externalImageFilename.c_str()); + downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE); + downloadedPostData.downloadedDataBase.externalImageDataSize = fileSize; + } + + nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + + uint64 titleId = CafeSystem::GetForegroundTitleId(); + + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + memset(downloadedPostData, 0, sizeof(DownloadedPostData) * maxCount); + *postCountOut = 0; + + const char* postXmlFilename = nullptr; + if(titleId == 0x0005000010143400 || titleId == 0x0005000010143500 || titleId == 0x0005000010143600) + postXmlFilename = "PostList_WindWakerHD.xml"; + + if (!postXmlFilename) + return OLV_RESULT_SUCCESS; + + // load post XML + std::vector xmlData; + if (!LoadOfflineDBFile(postXmlFilename, xmlData)) + return OLV_RESULT_SUCCESS; + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_buffer(xmlData.data(), xmlData.size()); + if (!result) + return OLV_RESULT_SUCCESS; + // collect list of all post xml nodes + std::vector postXmlNodes; + for (pugi::xml_node postNode = doc.child("posts").child("post"); postNode; postNode = postNode.next_sibling("post")) + postXmlNodes.push_back(postNode); + + // randomly select up to maxCount posts + srand(GetTickCount()); + uint32 postCount = 0; + while(!postXmlNodes.empty() && postCount < maxCount) + { + uint32 index = rand() % postXmlNodes.size(); + pugi::xml_node& postNode = postXmlNodes[index]; + + auto& addedPost = downloadedPostData[postCount]; + memset(&addedPost, 0, sizeof(DownloadedPostData)); + if (!ParseXML_DownloadedPostData(addedPost, postNode) ) + continue; + TryLoadCompressedMemoImage(addedPost); + CheckForExternalImage(addedPost); + postCount++; + // remove from post list + postXmlNodes[index] = postXmlNodes.back(); + postXmlNodes.pop_back(); + } + *postCountOut = postCount; + return OLV_RESULT_SUCCESS; + } + + nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + OfflineDB_LazyInit(); + + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + downloadedTopicData->communityId = param->communityId; + *postCountOut = 0; + + if(param->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) + return OLV_RESULT_SUCCESS; // the offlineDB doesn't contain any self posts + + StackAllocator doneEvent; + coreinit::OSInitEvent(&doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList, doneEvent.GetPointer(), downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); + coreinit::OSWaitEvent(&doneEvent); + nnResult r = asyncTask.get(); + return r; + } + + nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + + if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE)) + return OLV_RESULT_MISSING_DATA; + + // not all games may use JPEG files? + std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)_this->postId); + std::vector jpegData; + if (!LoadOfflineDBFile(externalImageFilename.c_str(), jpegData)) + return OLV_RESULT_FAILED_REQUEST; + + memcpy(imageDataOut, jpegData.data(), jpegData.size()); + *imageSizeOut = jpegData.size(); + + return OLV_RESULT_SUCCESS; + } + + nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + StackAllocator doneEvent; + coreinit::OSInitEvent(&doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData, doneEvent.GetPointer(), _this, imageDataOut, imageSizeOut, maxSize); + coreinit::OSWaitEvent(&doneEvent); + nnResult r = asyncTask.get(); + return r; + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h new file mode 100644 index 00000000..ed790479 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h @@ -0,0 +1,16 @@ +#pragma once +#include "Cafe/OS/libs/nn_common.h" +#include "nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + void OfflineDB_Init(); + void OfflineDB_Shutdown(); + + nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param); + nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp index 5056f2fe..5257530d 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp @@ -1,5 +1,6 @@ #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" #include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" #include "Cemu/ncrypto/ncrypto.h" // for base64 decoder #include "util/helpers/helpers.h" #include @@ -9,41 +10,28 @@ namespace nn { namespace olv { - - template - uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) - { - if(unescape) - { - // todo - } - std::wstring ws = boost::nowide::widen(sv); - size_t copyLen = std::min(TLength-1, ws.size()); - for(size_t i=0; i 0) obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT); } + if(tokenNode = xmlNode.child("topic_tag"); tokenNode) + { + SetStringUC2(obj.topicTag, tokenNode.child_value(), true); + } if(tokenNode = xmlNode.child("feeling_id"); tokenNode) { obj.feeling = ConvertString(tokenNode.child_value()); if(obj.feeling < 0 || obj.feeling >= 5) { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: feeling_id out of range"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: feeling_id out of range"); return false; } } @@ -52,7 +40,7 @@ namespace nn std::string_view id_sv = tokenNode.child_value(); if(id_sv.size() > 22) { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: id too long"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: id too long"); return false; } memcpy(obj.postId, id_sv.data(), id_sv.size()); @@ -67,7 +55,7 @@ namespace nn obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST); else { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: is_autopost has invalid value"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: is_autopost has invalid value"); return false; } } @@ -116,6 +104,36 @@ namespace nn { obj.countryId = ConvertString(tokenNode.child_value()); } + if(tokenNode = xmlNode.child("painting"); tokenNode) + { + if(pugi::xml_node subNode = tokenNode.child("content"); subNode) + { + std::vector paintingData = NCrypto::base64Decode(subNode.child_value()); + if (paintingData.size() > 0xA000) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase painting content is too large"); + return false; + } + memcpy(obj.compressedMemoBody, paintingData.data(), paintingData.size()); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); + } + if(pugi::xml_node subNode = tokenNode.child("size"); subNode) + { + obj.compressedMemoBodySize = ConvertString(subNode.child_value()); + } + } + if(tokenNode = xmlNode.child("app_data"); tokenNode) + { + std::vector appData = NCrypto::base64Decode(tokenNode.child_value()); + if (appData.size() > 0x400) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase AppData is too large"); + return false; + } + memcpy(obj.appData, appData.data(), appData.size()); + obj.appDataLength = appData.size(); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_APP_DATA); + } return true; } @@ -256,54 +274,188 @@ namespace nn return 0; } + nnResult DownloadedDataBase::DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + if(g_IsOfflineDBMode) + return OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(_this, imageDataOut, imageSizeOut, maxSize); + + if(!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) + return OLV_RESULT_MISSING_DATA; + + cemuLog_logDebug(LogType::Force, "DownloadedDataBase::DownloadExternalImageData not implemented"); + return OLV_RESULT_FAILED_REQUEST; // placeholder error + } + + nnResult DownloadPostDataListParam::GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize) + { + if(!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + //if(_this->communityId == 0) + // cemuLog_log(LogType::Force, "DownloadPostDataListParam::GetRawDataUrl called with invalid communityId"); + + // get base url + std::string baseUrl; + baseUrl.append(g_DiscoveryResults.apiEndpoint); + //baseUrl.append(fmt::format("/v1/communities/{}/posts", (uint32)_this->communityId)); + cemu_assert_debug(_this->communityId == 0); + baseUrl.append(fmt::format("/v1/posts.search", (uint32)_this->communityId)); + + // "v1/posts.search" + + // build parameter string + std::string params; + + // this function behaves differently for the Wii U menu? Where it can lookup posts by titleId? + if(_this->titleId != 0) + { + cemu_assert_unimplemented(); // Wii U menu mode + } + + // todo: Generic parameters. Which includes: language_id, limit, type=text/memo + + // handle postIds + for(size_t i=0; i<_this->MAX_NUM_POST_ID; i++) + { + if(_this->searchPostId[i].str[0] == '\0') + continue; + cemu_assert_unimplemented(); // todo + // todo - postId parameter + // handle filters + if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_MII)) + params.append("&with_mii=1"); + if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_EMPATHY)) + params.append("&with_empathy_added=1"); + if(_this->bodyTextMaxLength != 0) + params.append(fmt::format("&max_body_length={}", _this->bodyTextMaxLength)); + } + + if(_this->titleId != 0) + params.append(fmt::format("&title_id={}", (uint64)_this->titleId)); + + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FRIENDS_ONLY)) + params.append("&by=friend"); + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FOLLOWERS_ONLY)) + params.append("&by=followings"); + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) + params.append("&by=self"); + + if(!params.empty()) + params[0] = '?'; // replace the leading ampersand + + baseUrl.append(params); + if(baseUrl.size()+1 > urlMaxSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + strncpy(urlOut, baseUrl.c_str(), urlMaxSize); + return OLV_RESULT_SUCCESS; + } + + nnResult DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + if(g_IsOfflineDBMode) + return OfflineDB_DownloadPostDataListParam_DownloadPostDataList(downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + downloadedTopicData->communityId = param->communityId; + *postCountOut = 0; + + char urlBuffer[2048]; + if (NN_RESULT_IS_FAILURE(DownloadPostDataListParam::GetRawDataUrl(param, urlBuffer, sizeof(urlBuffer)))) + return OLV_RESULT_INVALID_PARAMETER; + + /* + CurlRequestHelper req; + req.initate(urlBuffer, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + bool reqResult = req.submitRequest(); + if (!reqResult) + { + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + cemuLog_log(LogType::Force, "Failed request: {} ({})", urlBuffer, httpCode); + if (!(httpCode >= 400)) + return OLV_RESULT_FAILED_REQUEST; + } + pugi::xml_document doc; + if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in community download response")); + return OLV_RESULT_INVALID_XML; + } + */ + + *postCountOut = 0; + + return OLV_RESULT_SUCCESS; + } + void loadOlivePostAndTopicTypes() { - cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::None); + cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::NN_OLV); // DownloadedDataBase getters - cafeExportRegisterFunc(DownloadedDataBase::TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetUserPid, "nn_olv", "GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetPostDate, "nn_olv", "GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetFeeling, "nn_olv", "GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetRegionId, "nn_olv", "GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetPlatformId, "nn_olv", "GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetLanguageId, "nn_olv", "GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetCountryId, "nn_olv", "GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetExternalUrl, "nn_olv", "GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetMiiData1, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetMiiNickname, "nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetBodyText, "nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetBodyMemo, "nn_olv", "GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetTopicTag, "nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); - cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetUserPid, "nn_olv", "GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetPostDate, "nn_olv", "GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetFeeling, "nn_olv", "GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetRegionId, "nn_olv", "GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetPlatformId, "nn_olv", "GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetLanguageId, "nn_olv", "GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetCountryId, "nn_olv", "GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetExternalUrl, "nn_olv", "GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetMiiData1, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetMiiNickname, "nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetBodyText, "nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetBodyMemo, "nn_olv", "GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetTopicTag, "nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::DownloadExternalImageData, "nn_olv", "DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedDataBase::GetExternalImageDataSize, "nn_olv", "GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); // DownloadedPostData getters - cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); - cafeExportRegisterFunc(DownloadedPostData::GetEmpathyCount, "nn_olv", "GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); - cafeExportRegisterFunc(DownloadedPostData::GetCommentCount, "nn_olv", "GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); - cafeExportRegisterFunc(DownloadedPostData::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedPostData::GetEmpathyCount, "nn_olv", "GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedPostData::GetCommentCount, "nn_olv", "GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadedPostData::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); // DownloadedSystemPostData getters - cafeExportRegisterFunc(hidden::DownloadedSystemPostData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemPostData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv", LogType::NN_OLV); // DownloadedTopicData getters - cafeExportRegisterFunc(DownloadedTopicData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedTopicData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv", LogType::NN_OLV); // DownloadedSystemTopicData getters - cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::TestFlags, "nn_olv", "TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIdNum, "nn_olv", "GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleText, "nn_olv", "GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIconData, "nn_olv", "GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::TestFlags, "nn_olv", "TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIdNum, "nn_olv", "GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleText, "nn_olv", "GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIconData, "nn_olv", "GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi", LogType::NN_OLV); // DownloadedSystemTopicDataList getters - cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicDataNum, "nn_olv", "GetDownloadedSystemTopicDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFv", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostDataNum, "nn_olv", "GetDownloadedSystemPostDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None); - cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::None); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicDataNum, "nn_olv", "GetDownloadedSystemTopicDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFv", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostDataNum, "nn_olv", "GetDownloadedSystemPostDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::NN_OLV); + cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::NN_OLV); + + // DownloadPostDataListParam constructor and getters + cafeExportRegisterFunc(DownloadPostDataListParam::Construct, "nn_olv", "__ct__Q3_2nn3olv25DownloadPostDataListParamFv", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetLanguageId, "nn_olv", "SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKey, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKeySingle, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchPid, "nn_olv", "SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostId, "nn_olv", "SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDate, "nn_olv", "SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDataMaxNum, "nn_olv", "SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataListParam::SetBodyTextMaxLength, "nn_olv", "SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); + + // URL and downloading functions + cafeExportRegisterFunc(DownloadPostDataListParam::GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi", LogType::NN_OLV); + cafeExportRegisterFunc(DownloadPostDataList, "nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", LogType::NN_OLV); } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h index 3ca4f87e..62dad755 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h @@ -1,5 +1,6 @@ #pragma once #include +#include "nn_olv_Common.h" namespace nn { @@ -154,8 +155,11 @@ namespace nn return OLV_RESULT_INVALID_PTR; if (maxLength == 0) return OLV_RESULT_NOT_ENOUGH_SIZE; + if (!TestFlags(_this, FLAGS::HAS_BODY_TEXT)) + return OLV_RESULT_MISSING_DATA; + memset(bodyTextOut, 0, maxLength * sizeof(uint16)); uint32 outputLength = std::min(_this->bodyTextLength, maxLength); - olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, _this->bodyTextLength); + olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, outputLength); return OLV_RESULT_SUCCESS; } @@ -213,9 +217,19 @@ namespace nn return _this->postId; } - // todo: // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi + static nnResult DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); + // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetExternalImageDataSize(DownloadedDataBase* _this) + { + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) + return 0; + return _this->externalImageDataSize; + } + + // todo: + // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi (implement downloading) // DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi // GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv }; @@ -425,6 +439,184 @@ namespace nn static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000); } + + struct DownloadPostDataListParam + { + static constexpr size_t MAX_NUM_SEARCH_PID = 12; + static constexpr size_t MAX_NUM_SEARCH_KEY = 5; + static constexpr size_t MAX_NUM_POST_ID = 20; + + enum class FLAGS + { + FRIENDS_ONLY = 0x01, // friends only + FOLLOWERS_ONLY = 0x02, // followers only + SELF_ONLY = 0x04, // self only + ONLY_TYPE_TEXT = 0x08, + ONLY_TYPE_MEMO = 0x10, + UKN_20 = 0x20, + WITH_MII = 0x40, // with mii + WITH_EMPATHY = 0x80, // with yeahs added + UKN_100 = 0x100, + UKN_200 = 0x200, // "is_delay" parameter + UKN_400 = 0x400, // "is_hot" parameter + + + }; + + struct SearchKey + { + uint16be str[152]; + }; + + struct PostId + { + char str[32]; + }; + + betype flags; + uint32be communityId; + uint32be searchPid[MAX_NUM_SEARCH_PID]; + uint8 languageId; + uint8 hasLanguageId_039; + uint8 padding03A[2]; + uint32be postDataMaxNum; + SearchKey searchKeyArray[MAX_NUM_SEARCH_KEY]; + PostId searchPostId[MAX_NUM_POST_ID]; + uint64be postDate; // OSTime? + uint64be titleId; // only used by System posts? + uint32be bodyTextMaxLength; + uint8 padding8C4[1852]; + + bool _HasFlag(FLAGS flag) + { + return ((uint32)flags.value() & (uint32)flag) != 0; + } + + void _SetFlags(FLAGS flag) + { + flags = (FLAGS)((uint32)flags.value() | (uint32)flag); + } + + // constructor and getters + // __ct__Q3_2nn3olv25DownloadPostDataListParamFv + static DownloadPostDataListParam* Construct(DownloadPostDataListParam* _this) + { + memset(_this, 0, sizeof(DownloadPostDataListParam)); + return _this; + } + + // SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetFlags(DownloadPostDataListParam* _this, FLAGS flags) + { + // todo - verify flag combos + _this->flags = flags; + return OLV_RESULT_SUCCESS; + } + + // SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc + static nnResult SetLanguageId(DownloadPostDataListParam* _this, uint8 languageId) + { + _this->languageId = languageId; + _this->hasLanguageId_039 = 1; + return OLV_RESULT_SUCCESS; + } + + // SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetCommunityId(DownloadPostDataListParam* _this, uint32 communityId) + { + _this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + + // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc + static nnResult SetSearchKey(DownloadPostDataListParam* _this, const uint16be* searchKey, uint8 searchKeyIndex) + { + if( !searchKey ) + { + memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey)); + return OLV_RESULT_SUCCESS; + } + if (searchKeyIndex >= MAX_NUM_SEARCH_KEY) + return OLV_RESULT_INVALID_PARAMETER; + memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey)); + if(olv_wstrnlen((const char16_t*)searchKey, 152) > 50) + { + cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetSearchKey: searchKey is too long\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + SetStringUC2(_this->searchKeyArray[searchKeyIndex].str, searchKey); + return OLV_RESULT_SUCCESS; + } + + // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw + static nnResult SetSearchKeySingle(DownloadPostDataListParam* _this, const uint16be* searchKey) + { + if (searchKey == nullptr) + { + cemuLog_logDebug(LogType::NN_OLV, "DownloadPostDataListParam::SetSearchKeySingle: searchKeySingle is Null\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + return SetSearchKey(_this, searchKey, 0); + } + + // SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetSearchPid(DownloadPostDataListParam* _this, uint32 searchPid) + { + if(_this->_HasFlag(FLAGS::FRIENDS_ONLY) || _this->_HasFlag(FLAGS::FOLLOWERS_ONLY) || _this->_HasFlag(FLAGS::SELF_ONLY)) + return OLV_RESULT_INVALID_PARAMETER; + _this->searchPid[0] = searchPid; + return OLV_RESULT_SUCCESS; + } + + // SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi + static nnResult SetPostId(DownloadPostDataListParam* _this, const char* postId, uint32 postIdIndex) + { + if (postIdIndex >= MAX_NUM_POST_ID) + return OLV_RESULT_INVALID_PARAMETER; + memset(&_this->searchPostId[postIdIndex], 0, sizeof(PostId)); + if (strlen(postId) > 22) + { + cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetPostId: postId is too long\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + strcpy(_this->searchPostId[postIdIndex].str, postId); + return OLV_RESULT_SUCCESS; + } + + // SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL + static nnResult SetPostDate(DownloadPostDataListParam* _this, uint64 postDate) + { + _this->postDate = postDate; + return OLV_RESULT_SUCCESS; + } + + // SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetPostDataMaxNum(DownloadPostDataListParam* _this, uint32 postDataMaxNum) + { + if(postDataMaxNum == 0) + return OLV_RESULT_INVALID_PARAMETER; + _this->postDataMaxNum = postDataMaxNum; + return OLV_RESULT_SUCCESS; + } + + // SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetBodyTextMaxLength(DownloadPostDataListParam* _this, uint32 bodyTextMaxLength) + { + if(bodyTextMaxLength >= 256) + return OLV_RESULT_INVALID_PARAMETER; + _this->bodyTextMaxLength = bodyTextMaxLength; + return OLV_RESULT_SUCCESS; + } + + // GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi + static nnResult GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize); + }; + + static_assert(sizeof(DownloadPostDataListParam) == 0x1000); + + // parsing functions + bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode); + void loadOlivePostAndTopicTypes(); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp index b76e6d63..179d66bd 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp @@ -54,9 +54,9 @@ namespace nn InitializeOliveRequest(req); StackAllocator requestDoneEvent; - coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future requestRes = std::async(std::launch::async, UploadCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); - coreinit::OSWaitEvent(requestDoneEvent); + coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h index 4944e314..2c53f118 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h @@ -256,10 +256,8 @@ namespace nn this->flags = 0; memset(this->titleText, 0, sizeof(this->titleText)); memset(this->description, 0, sizeof(this->description)); - int v2 = 0; - do - memset(this->searchKeys[v2++], 0, sizeof(this->searchKeys[v2++])); - while (v2 < 5); + for (int i = 0; i < 5; i++) + memset(this->searchKeys[i], 0, sizeof(this->searchKeys[0])); } static UploadCommunityDataParam* __ctor(UploadCommunityDataParam* _this) { @@ -402,30 +400,30 @@ namespace nn static void loadOliveUploadCommunityTypes() { - cafeExportRegisterFunc(UploadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv21UploadedCommunityDataFv", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv21UploadedCommunityDataCFUi", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv21UploadedCommunityDataCFPcUi", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(UploadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv21UploadedCommunityDataFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv21UploadedCommunityDataCFUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv21UploadedCommunityDataCFPcUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); - cafeExportRegisterFunc(UploadCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv24UploadCommunityDataParamFv", LogType::None); - cafeExportRegisterFunc(UploadCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::None); - cafeExportRegisterFunc(UploadCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::None); - cafeExportRegisterFunc(UploadCommunityDataParam::__SetAppData, "nn_olv", "SetAppData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::None); - cafeExportRegisterFunc(UploadCommunityDataParam::__SetTitleText, "nn_olv", "SetTitleText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::None); - cafeExportRegisterFunc(UploadCommunityDataParam::__SetDescriptionText, "nn_olv", "SetDescriptionText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::None); - cafeExportRegisterFunc(UploadCommunityDataParam::__SetIconData, "nn_olv", "SetIconData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv24UploadCommunityDataParamFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetAppData, "nn_olv", "SetAppData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetTitleText, "nn_olv", "SetTitleText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::NN_OLV); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetDescriptionText, "nn_olv", "SetDescriptionText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::NN_OLV); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetIconData, "nn_olv", "SetIconData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(UploadCommunityDataParam const*))UploadCommunityData, - "nn_olv", "UploadCommunityData__Q2_2nn3olvFPCQ3_2nn3olv24UploadCommunityDataParam", LogType::None); + "nn_olv", "UploadCommunityData__Q2_2nn3olvFPCQ3_2nn3olv24UploadCommunityDataParam", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(UploadedCommunityData *, UploadCommunityDataParam const*))UploadCommunityData, - "nn_olv", "UploadCommunityData__Q2_2nn3olvFPQ3_2nn3olv21UploadedCommunityDataPCQ3_2nn3olv24UploadCommunityDataParam", LogType::None); + "nn_olv", "UploadCommunityData__Q2_2nn3olvFPQ3_2nn3olv21UploadedCommunityDataPCQ3_2nn3olv24UploadCommunityDataParam", LogType::NN_OLV); } } 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 7d9220fc..307004b9 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp @@ -44,9 +44,9 @@ namespace nn InitializeOliveRequest(req); StackAllocator requestDoneEvent; - coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future requestRes = std::async(std::launch::async, UploadFavoriteToCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); - coreinit::OSWaitEvent(requestDoneEvent); + coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h index 05ef1dd4..dfa43ec3 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h @@ -315,27 +315,27 @@ namespace nn static void loadOliveUploadFavoriteTypes() { - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv31UploadedFavoriteToCommunityDataFv", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFUi", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPcUi", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); - cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv31UploadedFavoriteToCommunityDataFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPcUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::NN_OLV); - cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFv", LogType::None); - cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::None); - cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityCode, "nn_olv", "SetCommunityCode__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFPCc", LogType::None); - cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFv", LogType::NN_OLV); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::NN_OLV); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityCode, "nn_olv", "SetCommunityCode__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFPCc", LogType::NN_OLV); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, - "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::None); + "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(UploadedFavoriteToCommunityData*, const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, - "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPQ3_2nn3olv31UploadedFavoriteToCommunityDataPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::None); + "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPQ3_2nn3olv31UploadedFavoriteToCommunityDataPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::NN_OLV); } } diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 8a372a41..c655ec90 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -370,19 +370,20 @@ namespace save 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) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParamsNew_t asyncParams; asyncParams.ioMsgQueue = nullptr; asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetPointer(); SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -604,19 +605,20 @@ namespace save SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -673,19 +675,20 @@ namespace save SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -786,19 +789,20 @@ namespace save SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -859,19 +863,20 @@ 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 currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -995,19 +1000,20 @@ namespace save SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -1046,19 +1052,20 @@ namespace save 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) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParamsNew_t asyncParams; asyncParams.ioMsgQueue = nullptr; asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetPointer(); SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, hFile, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -1106,19 +1113,20 @@ 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 currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -1157,19 +1165,20 @@ 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 currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -1346,19 +1355,20 @@ namespace save SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } @@ -1415,19 +1425,20 @@ namespace save SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { + MEMPTR currentThread{coreinit::OSGetCurrentThread()}; FSAsyncParams_t asyncParams; asyncParams.ioMsgQueue = MPTR_NULL; asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); StackAllocator param; - param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetMPTRBE(); SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + coreinit_suspendThread(currentThread, 1000); PPCCore_switchToScheduler(); return param->returnStatus; } diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp new file mode 100644 index 00000000..6e6cb123 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -0,0 +1,41 @@ +#include "nsyshid.h" +#include "Backend.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +#include "BackendLibusb.h" + +#endif + +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include "BackendWindowsHID.h" + +#endif + +namespace nsyshid::backend +{ + void AttachDefaultBackends() + { +#if NSYSHID_ENABLE_BACKEND_LIBUSB + // add libusb backend + { + auto backendLibusb = std::make_shared(); + if (backendLibusb->IsInitialisedOk()) + { + AttachBackend(backendLibusb); + } + } +#endif // NSYSHID_ENABLE_BACKEND_LIBUSB +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + // add windows hid backend + { + auto backendWindowsHID = std::make_shared(); + if (backendWindowsHID->IsInitialisedOk()) + { + AttachBackend(backendWindowsHID); + } + } +#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID + } +} // namespace nsyshid::backend diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h new file mode 100644 index 00000000..641104f5 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -0,0 +1,141 @@ +#ifndef CEMU_NSYSHID_BACKEND_H +#define CEMU_NSYSHID_BACKEND_H + +#include +#include +#include + +#include "Common/precompiled.h" + +namespace nsyshid +{ + typedef struct + { + /* +0x00 */ uint32be handle; + /* +0x04 */ uint32 ukn04; + /* +0x08 */ uint16 vendorId; // little-endian ? + /* +0x0A */ uint16 productId; // little-endian ? + /* +0x0C */ uint8 ifIndex; + /* +0x0D */ uint8 subClass; + /* +0x0E */ uint8 protocol; + /* +0x0F */ uint8 paddingGuessed0F; + /* +0x10 */ uint16be maxPacketSizeRX; + /* +0x12 */ uint16be maxPacketSizeTX; + } HID_t; + + static_assert(offsetof(HID_t, vendorId) == 0x8, ""); + static_assert(offsetof(HID_t, productId) == 0xA, ""); + static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); + static_assert(offsetof(HID_t, protocol) == 0xE, ""); + + class Device { + public: + Device() = delete; + + Device(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol); + + Device(const Device& device) = delete; + + Device& operator=(const Device& device) = delete; + + virtual ~Device() = default; + + HID_t* m_hid; // this info is passed to applications and must remain intact + + uint16 m_vendorId; + uint16 m_productId; + uint8 m_interfaceIndex; + uint8 m_interfaceSubClass; + uint8 m_protocol; + uint16 m_maxPacketSizeRX; + uint16 m_maxPacketSizeTX; + + virtual void AssignHID(HID_t* hid); + + virtual bool Open() = 0; + + virtual void Close() = 0; + + virtual bool IsOpened() = 0; + + enum class ReadResult + { + Success, + Error, + ErrorTimeout, + }; + + virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0; + + enum class WriteResult + { + Success, + Error, + ErrorTimeout, + }; + + virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0; + + virtual bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) = 0; + + virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; + + virtual bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) = 0; + }; + + class Backend { + public: + Backend(); + + Backend(const Backend& backend) = delete; + + Backend& operator=(const Backend& backend) = delete; + + virtual ~Backend() = default; + + void DetachAllDevices(); + + // called from nsyshid when this backend is attached - do not call this yourself! + void OnAttach(); + + // called from nsyshid when this backend is detached - do not call this yourself! + void OnDetach(); + + bool IsBackendAttached(); + + virtual bool IsInitialisedOk() = 0; + + protected: + // try to attach a device - only works if this backend is attached + bool AttachDevice(const std::shared_ptr& device); + + void DetachDevice(const std::shared_ptr& device); + + std::shared_ptr FindDevice(std::function&)> isWantedDevice); + + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); + + // called from OnAttach() - attach devices that your backend can see here + virtual void AttachVisibleDevices() = 0; + + private: + std::list> m_devices; + std::recursive_mutex m_devicesMutex; + bool m_isAttached; + }; + + namespace backend + { + void AttachDefaultBackends(); + } +} // namespace nsyshid + +#endif // CEMU_NSYSHID_BACKEND_H diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp new file mode 100644 index 00000000..4f88b7ed --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -0,0 +1,791 @@ +#include "BackendLibusb.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +namespace nsyshid::backend::libusb +{ + BackendLibusb::BackendLibusb() + : m_ctx(nullptr), + m_initReturnCode(0), + m_callbackRegistered(false), + m_hotplugCallbackHandle(0), + m_hotplugThreadStop(false) + { + m_initReturnCode = libusb_init(&m_ctx); + if (m_initReturnCode < 0) + { + m_ctx = nullptr; + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb with return code %i", + m_initReturnCode); + return; + } + + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + { + int ret = libusb_hotplug_register_callback(m_ctx, + (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), + (libusb_hotplug_flag)0, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + HotplugCallback, + this, + &m_hotplugCallbackHandle); + if (ret != LIBUSB_SUCCESS) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb: failed to register hotplug callback with return code %i", + ret); + } + else + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: registered hotplug callback"); + m_callbackRegistered = true; + m_hotplugThread = std::thread([this] { + while (!m_hotplugThreadStop) + { + timeval timeout{ + .tv_sec = 1, + .tv_usec = 0, + }; + int ret = libusb_handle_events_timeout_completed(m_ctx, &timeout, nullptr); + if (ret != 0) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", + ret); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + } + }); + } + } + else + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: hotplug not supported by this version of libusb"); + } + } + + bool BackendLibusb::IsInitialisedOk() + { + return m_initReturnCode == 0; + } + + void BackendLibusb::AttachVisibleDevices() + { + // add all currently connected devices + libusb_device** devices; + ssize_t deviceCount = libusb_get_device_list(m_ctx, &devices); + if (deviceCount < 0) + { + cemuLog_log(LogType::Force, "nsyshid::BackendLibusb: failed to get usb devices"); + return; + } + libusb_device* dev; + for (int i = 0; (dev = devices[i]) != nullptr; i++) + { + auto device = CheckAndCreateDevice(dev); + if (device != nullptr) + { + if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) + { + if (!AttachDevice(device)) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb: failed to attach device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + else + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb: device not on whitelist: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + } + + libusb_free_device_list(devices, 1); + } + + int BackendLibusb::HotplugCallback(libusb_context* ctx, + libusb_device* dev, + libusb_hotplug_event event, + void* user_data) + { + if (user_data) + { + BackendLibusb* backend = static_cast(user_data); + return backend->OnHotplug(dev, event); + } + return 0; + } + + int BackendLibusb::OnHotplug(libusb_device* dev, libusb_hotplug_event event) + { + struct libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): failed to get device descriptor"); + return 0; + } + + switch (event) + { + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}", + desc.idVendor, + desc.idProduct); + auto device = CheckAndCreateDevice(dev); + if (device != nullptr) + { + if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) + { + if (!AttachDevice(device)) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::OnHotplug(): failed to attach device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + else + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::OnHotplug(): device not on whitelist: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + } + break; + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}", + desc.idVendor, + desc.idProduct); + auto device = FindLibusbDevice(dev); + if (device != nullptr) + { + DetachDevice(device); + } + } + break; + } + + return 0; + } + + BackendLibusb::~BackendLibusb() + { + if (m_callbackRegistered) + { + m_hotplugThreadStop = true; + libusb_hotplug_deregister_callback(m_ctx, m_hotplugCallbackHandle); + m_hotplugThread.join(); + } + DetachAllDevices(); + if (m_ctx) + { + libusb_exit(m_ctx); + m_ctx = nullptr; + } + } + + std::shared_ptr BackendLibusb::FindLibusbDevice(libusb_device* dev) + { + libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); + return nullptr; + } + uint8 busNumber = libusb_get_bus_number(dev); + uint8 deviceAddress = libusb_get_device_address(dev); + auto device = FindDevice([desc, busNumber, deviceAddress](const std::shared_ptr& d) -> bool { + auto device = std::dynamic_pointer_cast(d); + if (device != nullptr && + desc.idVendor == device->m_vendorId && + desc.idProduct == device->m_productId && + busNumber == device->m_libusbBusNumber && + deviceAddress == device->m_libusbDeviceAddress) + { + // we found our device! + return true; + } + return false; + }); + + if (device != nullptr) + { + return device; + } + return nullptr; + } + + std::shared_ptr BackendLibusb::CheckAndCreateDevice(libusb_device* dev) + { + struct libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): failed to get device descriptor; return code: %i", + 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(m_ctx, + desc.idVendor, + desc.idProduct, + 1, + 2, + 0, + libusb_get_bus_number(dev), + libusb_get_device_address(dev)); + // figure out device endpoints + if (!FindDefaultDeviceEndpoints(dev, + device->m_libusbHasEndpointIn, + device->m_libusbEndpointIn, + device->m_maxPacketSizeRX, + device->m_libusbHasEndpointOut, + device->m_libusbEndpointOut, + device->m_maxPacketSizeTX)) + { + // most likely couldn't read config descriptor + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): failed to find default endpoints for device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + return nullptr; + } + return device; + } + + bool BackendLibusb::FindDefaultDeviceEndpoints(libusb_device* dev, bool& endpointInFound, uint8& endpointIn, + uint16& endpointInMaxPacketSize, bool& endpointOutFound, + uint8& endpointOut, uint16& endpointOutMaxPacketSize) + { + endpointInFound = false; + endpointIn = 0; + endpointInMaxPacketSize = 0; + endpointOutFound = false; + endpointOut = 0; + endpointOutMaxPacketSize = 0; + + struct libusb_config_descriptor* conf = nullptr; + int ret = libusb_get_active_config_descriptor(dev, &conf); + + if (ret == 0) + { + for (uint8 interfaceIndex = 0; interfaceIndex < conf->bNumInterfaces; interfaceIndex++) + { + const struct libusb_interface& interface = conf->interface[interfaceIndex]; + for (int altsettingIndex = 0; altsettingIndex < interface.num_altsetting; altsettingIndex++) + { + const struct libusb_interface_descriptor& altsetting = interface.altsetting[altsettingIndex]; + for (uint8 endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) + { + const struct libusb_endpoint_descriptor& endpoint = altsetting.endpoint[endpointIndex]; + // figure out direction + if ((endpoint.bEndpointAddress & (1 << 7)) != 0) + { + // in + if (!endpointInFound) + { + endpointInFound = true; + endpointIn = endpoint.bEndpointAddress; + endpointInMaxPacketSize = endpoint.wMaxPacketSize; + } + } + else + { + // out + if (!endpointOutFound) + { + endpointOutFound = true; + endpointOut = endpoint.bEndpointAddress; + endpointOutMaxPacketSize = endpoint.wMaxPacketSize; + } + } + } + } + } + libusb_free_config_descriptor(conf); + return true; + } + return false; + } + + DeviceLibusb::DeviceLibusb(libusb_context* ctx, + uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + uint8 libusbBusNumber, + uint8 libusbDeviceAddress) + : Device(vendorId, + productId, + interfaceIndex, + interfaceSubClass, + protocol), + m_ctx(ctx), + m_libusbHandle(nullptr), + m_handleInUseCounter(-1), + m_libusbBusNumber(libusbBusNumber), + m_libusbDeviceAddress(libusbDeviceAddress), + m_libusbHasEndpointIn(false), + m_libusbEndpointIn(0), + m_libusbHasEndpointOut(false), + m_libusbEndpointOut(0) + { + } + + DeviceLibusb::~DeviceLibusb() + { + CloseDevice(); + } + + bool DeviceLibusb::Open() + { + std::unique_lock lock(m_handleMutex); + if (IsOpened()) + { + return true; + } + // we may still be in the process of closing the device; wait for that to finish + while (m_handleInUseCounter != -1) + { + m_handleInUseCounterDecremented.wait(lock); + } + + libusb_device** devices; + ssize_t deviceCount = libusb_get_device_list(m_ctx, &devices); + if (deviceCount < 0) + { + cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to get usb devices"); + return false; + } + libusb_device* dev; + libusb_device* found = nullptr; + for (int i = 0; (dev = devices[i]) != nullptr; i++) + { + struct libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_log(LogType::Force, + "nsyshid::DeviceLibusb::open(): failed to get device descriptor; return code: %i", + ret); + libusb_free_device_list(devices, 1); + return false; + } + if (desc.idVendor == this->m_vendorId && + desc.idProduct == this->m_productId && + libusb_get_bus_number(dev) == this->m_libusbBusNumber && + libusb_get_device_address(dev) == this->m_libusbDeviceAddress) + { + // we found our device! + found = dev; + break; + } + } + + if (found != nullptr) + { + { + int ret = libusb_open(dev, &(this->m_libusbHandle)); + if (ret < 0) + { + this->m_libusbHandle = nullptr; + cemuLog_log(LogType::Force, + "nsyshid::DeviceLibusb::open(): failed to open device; return code: %i", + ret); + libusb_free_device_list(devices, 1); + return false; + } + 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); + if (ret != 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); + } + } + } + + libusb_free_device_list(devices, 1); + return found != nullptr; + } + + void DeviceLibusb::Close() + { + CloseDevice(); + } + + void DeviceLibusb::CloseDevice() + { + std::unique_lock lock(m_handleMutex); + if (IsOpened()) + { + auto handle = m_libusbHandle; + m_libusbHandle = nullptr; + while (m_handleInUseCounter > 0) + { + m_handleInUseCounterDecremented.wait(lock); + } + libusb_release_interface(handle, 0); + libusb_close(handle); + m_handleInUseCounter = -1; + m_handleInUseCounterDecremented.notify_all(); + } + } + + bool DeviceLibusb::IsOpened() + { + return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; + } + + Device::ReadResult DeviceLibusb::Read(uint8* data, sint32 length, sint32& bytesRead) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); + return ReadResult::Error; + } + + const unsigned int timeout = 50; + int actualLength = 0; + int ret = 0; + do + { + ret = libusb_bulk_transfer(handleLock->GetHandle(), + this->m_libusbEndpointIn, + data, + length, + &actualLength, + timeout); + } + while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened()); + + if (ret == 0 || ret == LIBUSB_ERROR_TIMEOUT) + { + // success + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", + actualLength, + length); + bytesRead = actualLength; + return ReadResult::Success; + } + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::read(): failed with error code: {}", + ret); + return ReadResult::Error; + } + + Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); + return WriteResult::Error; + } + + bytesWritten = 0; + int actualLength = 0; + int ret = libusb_bulk_transfer(handleLock->GetHandle(), + this->m_libusbEndpointOut, + data, + length, + &actualLength, + 0); + + if (ret == 0) + { + // success + bytesWritten = actualLength; + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", + bytesWritten, + length); + return WriteResult::Success; + } + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::write(): failed with error code: {}", + ret); + return WriteResult::Error; + } + + bool DeviceLibusb::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::getDescriptor(): device is not opened"); + return false; + } + + if (descType == 0x02) + { + struct libusb_config_descriptor* conf = nullptr; + libusb_device* dev = libusb_get_device(handleLock->GetHandle()); + int ret = libusb_get_active_config_descriptor(dev, &conf); + + if (ret == 0) + { + std::vector configurationDescriptor(conf->wTotalLength); + uint8* currentWritePtr = &configurationDescriptor[0]; + + // configuration descriptor + cemu_assert_debug(conf->bLength == LIBUSB_DT_CONFIG_SIZE); + *(uint8*)(currentWritePtr + 0) = conf->bLength; // bLength + *(uint8*)(currentWritePtr + 1) = conf->bDescriptorType; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = conf->wTotalLength; // wTotalLength + *(uint8*)(currentWritePtr + 4) = conf->bNumInterfaces; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = conf->bConfigurationValue; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = conf->iConfiguration; // iConfiguration + *(uint8*)(currentWritePtr + 7) = conf->bmAttributes; // bmAttributes + *(uint8*)(currentWritePtr + 8) = conf->MaxPower; // MaxPower + currentWritePtr = currentWritePtr + conf->bLength; + + for (uint8_t interfaceIndex = 0; interfaceIndex < conf->bNumInterfaces; interfaceIndex++) + { + const struct libusb_interface& interface = conf->interface[interfaceIndex]; + for (int altsettingIndex = 0; altsettingIndex < interface.num_altsetting; altsettingIndex++) + { + // interface descriptor + const struct libusb_interface_descriptor& altsetting = interface.altsetting[altsettingIndex]; + cemu_assert_debug(altsetting.bLength == LIBUSB_DT_INTERFACE_SIZE); + *(uint8*)(currentWritePtr + 0) = altsetting.bLength; // bLength + *(uint8*)(currentWritePtr + 1) = altsetting.bDescriptorType; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = altsetting.bInterfaceNumber; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = altsetting.bAlternateSetting; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = altsetting.bNumEndpoints; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = altsetting.bInterfaceClass; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = altsetting.bInterfaceSubClass; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = altsetting.bInterfaceProtocol; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = altsetting.iInterface; // iInterface + currentWritePtr = currentWritePtr + altsetting.bLength; + + if (altsetting.extra_length > 0) + { + // unknown descriptors - copy the ones that we can identify ourselves + const unsigned char* extraReadPointer = altsetting.extra; + while (extraReadPointer - altsetting.extra < altsetting.extra_length) + { + uint8 bLength = *(uint8*)(extraReadPointer + 0); + if (bLength == 0) + { + // prevent endless loop + break; + } + if (extraReadPointer + bLength - altsetting.extra > altsetting.extra_length) + { + // prevent out of bounds read + break; + } + uint8 bDescriptorType = *(uint8*)(extraReadPointer + 1); + // HID descriptor + if (bDescriptorType == LIBUSB_DT_HID && bLength == 9) + { + *(uint8*)(currentWritePtr + 0) = + *(uint8*)(extraReadPointer + 0); // bLength + *(uint8*)(currentWritePtr + 1) = + *(uint8*)(extraReadPointer + 1); // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = + *(uint16*)(extraReadPointer + 2); // bcdHID + *(uint8*)(currentWritePtr + 4) = + *(uint8*)(extraReadPointer + 4); // bCountryCode + *(uint8*)(currentWritePtr + 5) = + *(uint8*)(extraReadPointer + 5); // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = + *(uint8*)(extraReadPointer + 6); // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = + *(uint16*)(extraReadPointer + 7); // wDescriptorLength + currentWritePtr += bLength; + } + extraReadPointer += bLength; + } + } + + for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) + { + // endpoint descriptor + const struct libusb_endpoint_descriptor& endpoint = altsetting.endpoint[endpointIndex]; + cemu_assert_debug(endpoint.bLength == LIBUSB_DT_ENDPOINT_SIZE || + endpoint.bLength == LIBUSB_DT_ENDPOINT_AUDIO_SIZE); + *(uint8*)(currentWritePtr + 0) = endpoint.bLength; + *(uint8*)(currentWritePtr + 1) = endpoint.bDescriptorType; + *(uint8*)(currentWritePtr + 2) = endpoint.bEndpointAddress; + *(uint8*)(currentWritePtr + 3) = endpoint.bmAttributes; + *(uint16be*)(currentWritePtr + 4) = endpoint.wMaxPacketSize; + *(uint8*)(currentWritePtr + 6) = endpoint.bInterval; + if (endpoint.bLength == LIBUSB_DT_ENDPOINT_AUDIO_SIZE) + { + *(uint8*)(currentWritePtr + 7) = endpoint.bRefresh; + *(uint8*)(currentWritePtr + 8) = endpoint.bSynchAddress; + } + currentWritePtr += endpoint.bLength; + } + } + } + uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0]; + libusb_free_config_descriptor(conf); + cemu_assert_debug(bytesWritten <= conf->wTotalLength); + + memcpy(output, &configurationDescriptor[0], + std::min(outputMaxLength, bytesWritten)); + return true; + } + else + { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::getDescriptor(): failed to get config descriptor with error code: {}", + ret); + return false; + } + } + else + { + cemu_assert_unimplemented(); + } + return false; + } + + bool DeviceLibusb::SetProtocol(uint32 ifIndex, uint32 protocol) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); + return false; + } + + // 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"); + return true; + } + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::setProtocol(): failed with error code: {}", + ret); + return false; +#endif + + // pretend that everything is fine + return true; + } + + bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData, + sint32 originalLength) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): device is not opened"); + 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, + reportData, + length, + timeout); +#endif + + // pretend that everything is fine + return true; + } + + std::unique_ptr DeviceLibusb::AquireHandleLock() + { + return std::make_unique(&m_libusbHandle, + m_handleMutex, + m_handleInUseCounter, + m_handleInUseCounterDecremented, + *this); + } + + DeviceLibusb::HandleLock::HandleLock(libusb_device_handle** handle, + std::mutex& handleMutex, + std::atomic& handleInUseCounter, + std::condition_variable& handleInUseCounterDecremented, + DeviceLibusb& device) + : m_handle(nullptr), + m_handleMutex(handleMutex), + m_handleInUseCounter(handleInUseCounter), + m_handleInUseCounterDecremented(handleInUseCounterDecremented) + { + std::lock_guard lock(handleMutex); + if (device.IsOpened() && handle != nullptr && handleInUseCounter >= 0) + { + this->m_handle = *handle; + this->m_handleInUseCounter++; + } + } + + DeviceLibusb::HandleLock::~HandleLock() + { + if (IsValid()) + { + std::lock_guard lock(m_handleMutex); + m_handleInUseCounter--; + m_handleInUseCounterDecremented.notify_all(); + } + } + + bool DeviceLibusb::HandleLock::IsValid() + { + return m_handle != nullptr; + } + + libusb_device_handle* DeviceLibusb::HandleLock::GetHandle() + { + return m_handle; + } +} // namespace nsyshid::backend::libusb + +#endif // NSYSHID_ENABLE_BACKEND_LIBUSB diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h new file mode 100644 index 00000000..216be6ce --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -0,0 +1,129 @@ +#ifndef CEMU_NSYSHID_BACKEND_LIBUSB_H +#define CEMU_NSYSHID_BACKEND_LIBUSB_H + +#include "nsyshid.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +#include +#include "Backend.h" + +namespace nsyshid::backend::libusb +{ + class BackendLibusb : public nsyshid::Backend { + public: + BackendLibusb(); + + ~BackendLibusb(); + + bool IsInitialisedOk() override; + + protected: + void AttachVisibleDevices() override; + + private: + libusb_context* m_ctx; + int m_initReturnCode; + bool m_callbackRegistered; + libusb_hotplug_callback_handle m_hotplugCallbackHandle; + std::thread m_hotplugThread; + std::atomic m_hotplugThreadStop; + + // called by libusb + static int HotplugCallback(libusb_context* ctx, libusb_device* dev, + libusb_hotplug_event event, void* user_data); + + int OnHotplug(libusb_device* dev, libusb_hotplug_event event); + + std::shared_ptr CheckAndCreateDevice(libusb_device* dev); + + std::shared_ptr FindLibusbDevice(libusb_device* dev); + + bool FindDefaultDeviceEndpoints(libusb_device* dev, + bool& endpointInFound, uint8& endpointIn, uint16& endpointInMaxPacketSize, + bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); + }; + + class DeviceLibusb : public nsyshid::Device { + public: + DeviceLibusb(libusb_context* ctx, + uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + uint8 libusbBusNumber, + uint8 libusbDeviceAddress); + + ~DeviceLibusb() override; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + + WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + + bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + + uint8 m_libusbBusNumber; + uint8 m_libusbDeviceAddress; + bool m_libusbHasEndpointIn; + uint8 m_libusbEndpointIn; + bool m_libusbHasEndpointOut; + uint8 m_libusbEndpointOut; + + private: + void CloseDevice(); + + libusb_context* m_ctx; + std::mutex m_handleMutex; + std::atomic m_handleInUseCounter; + std::condition_variable m_handleInUseCounterDecremented; + libusb_device_handle* m_libusbHandle; + + class HandleLock { + public: + HandleLock() = delete; + + HandleLock(libusb_device_handle** handle, + std::mutex& handleMutex, + std::atomic& handleInUseCounter, + std::condition_variable& handleInUseCounterDecremented, + DeviceLibusb& device); + + ~HandleLock(); + + HandleLock(const HandleLock&) = delete; + + HandleLock& operator=(const HandleLock&) = delete; + + bool IsValid(); + + libusb_device_handle* GetHandle(); + + private: + libusb_device_handle* m_handle; + std::mutex& m_handleMutex; + std::atomic& m_handleInUseCounter; + std::condition_variable& m_handleInUseCounterDecremented; + }; + + std::unique_ptr AquireHandleLock(); + }; +} // namespace nsyshid::backend::libusb + +#endif // NSYSHID_ENABLE_BACKEND_LIBUSB + +#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp new file mode 100644 index 00000000..520a0d31 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -0,0 +1,454 @@ +#include "BackendWindowsHID.h" + +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include +#include +#include + +#pragma comment(lib, "Setupapi.lib") +#pragma comment(lib, "hid.lib") + +DEFINE_GUID(GUID_DEVINTERFACE_HID, + 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); + +namespace nsyshid::backend::windows +{ + BackendWindowsHID::BackendWindowsHID() + { + } + + void BackendWindowsHID::AttachVisibleDevices() + { + // add all currently connected devices + HDEVINFO hDevInfo; + SP_DEVICE_INTERFACE_DATA DevIntfData; + PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData; + SP_DEVINFO_DATA DevData; + + DWORD dwSize, dwMemberIdx; + + hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + + if (hDevInfo != INVALID_HANDLE_VALUE) + { + DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + dwMemberIdx = 0; + + SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, + dwMemberIdx, &DevIntfData); + + while (GetLastError() != ERROR_NO_MORE_ITEMS) + { + DevData.cbSize = sizeof(DevData); + SetupDiGetDeviceInterfaceDetail( + hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL); + + DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + dwSize); + DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, + DevIntfDetailData, dwSize, &dwSize, &DevData)) + { + HANDLE hHIDDevice = OpenDevice(DevIntfDetailData->DevicePath); + if (hHIDDevice != INVALID_HANDLE_VALUE) + { + auto device = CheckAndCreateDevice(DevIntfDetailData->DevicePath, hHIDDevice); + if (device != nullptr) + { + if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) + { + if (!AttachDevice(device)) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendWindowsHID: failed to attach device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + else + { + cemuLog_log(LogType::Force, + "nsyshid::BackendWindowsHID: device not on whitelist: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + CloseHandle(hHIDDevice); + } + } + HeapFree(GetProcessHeap(), 0, DevIntfDetailData); + // next + SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData); + } + SetupDiDestroyDeviceInfoList(hDevInfo); + } + } + + BackendWindowsHID::~BackendWindowsHID() + { + } + + bool BackendWindowsHID::IsInitialisedOk() + { + return true; + } + + std::shared_ptr BackendWindowsHID::CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice) + { + HIDD_ATTRIBUTES hidAttr; + hidAttr.Size = sizeof(HIDD_ATTRIBUTES); + if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE) + return nullptr; + + auto device = std::make_shared(hidAttr.VendorID, + hidAttr.ProductID, + 1, + 2, + 0, + _wcsdup(devicePath)); + // get additional device info + sint32 maxPacketInputLength = -1; + sint32 maxPacketOutputLength = -1; + PHIDP_PREPARSED_DATA ppData = nullptr; + if (HidD_GetPreparsedData(hDevice, &ppData)) + { + HIDP_CAPS caps; + if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS) + { + // length includes the report id byte + maxPacketInputLength = caps.InputReportByteLength - 1; + maxPacketOutputLength = caps.OutputReportByteLength - 1; + } + HidD_FreePreparsedData(ppData); + } + if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) + { + cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", + maxPacketInputLength); + maxPacketInputLength = 0x20; + } + if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) + { + cemuLog_log(LogType::Force, "HID: Output packet length not available or out of range (length = {})", + maxPacketOutputLength); + maxPacketOutputLength = 0x20; + } + + device->m_maxPacketSizeRX = maxPacketInputLength; + device->m_maxPacketSizeTX = maxPacketOutputLength; + + return device; + } + + DeviceWindowsHID::DeviceWindowsHID(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + wchar_t* devicePath) + : Device(vendorId, + productId, + interfaceIndex, + interfaceSubClass, + protocol), + m_devicePath(devicePath), + m_hFile(INVALID_HANDLE_VALUE) + { + } + + DeviceWindowsHID::~DeviceWindowsHID() + { + if (m_hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hFile); + m_hFile = INVALID_HANDLE_VALUE; + } + } + + bool DeviceWindowsHID::Open() + { + if (IsOpened()) + { + return true; + } + m_hFile = OpenDevice(m_devicePath); + if (m_hFile == INVALID_HANDLE_VALUE) + { + return false; + } + HidD_SetNumInputBuffers(m_hFile, 2); // don't cache too many reports + return true; + } + + void DeviceWindowsHID::Close() + { + if (m_hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hFile); + m_hFile = INVALID_HANDLE_VALUE; + } + } + + bool DeviceWindowsHID::IsOpened() + { + return m_hFile != INVALID_HANDLE_VALUE; + } + + Device::ReadResult DeviceWindowsHID::Read(uint8* data, sint32 length, sint32& bytesRead) + { + bytesRead = 0; + DWORD bt; + OVERLAPPED ovlp = {0}; + ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + uint8* tempBuffer = (uint8*)malloc(length + 1); + sint32 transferLength = 0; // minus report byte + + _debugPrintHex("HID_READ_BEFORE", data, length); + + cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length); + BOOL readResult = ReadFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + if (readResult != FALSE) + { + // sometimes we get the result immediately + if (bt == 0) + transferLength = 0; + else + transferLength = bt - 1; + cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}", + GetLastError(), transferLength); + } + else + { + // wait for result + cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError()); + // async hid read is never supposed to return unless there is a response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out) + DWORD r = WaitForSingleObject(ovlp.hEvent, 2000 * 100); + if (r == WAIT_TIMEOUT) + { + cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError()); + // return -108 in case of timeout + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return ReadResult::ErrorTimeout; + } + + cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete"); + GetOverlappedResult(this->m_hFile, &ovlp, &bt, false); + if (bt == 0) + transferLength = 0; + else + transferLength = bt - 1; + cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength); + } + sint32 returnCode = 0; + ReadResult result = ReadResult::Success; + if (bt != 0) + { + memcpy(data, tempBuffer + 1, transferLength); + sint32 hidReadLength = transferLength; + + char debugOutput[1024] = {0}; + for (sint32 i = 0; i < transferLength; i++) + { + sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]); + } + cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); + + bytesRead = transferLength; + result = ReadResult::Success; + } + else + { + cemuLog_log(LogType::Force, "Failed HID read"); + result = ReadResult::Error; + } + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return result; + } + + Device::WriteResult DeviceWindowsHID::Write(uint8* data, sint32 length, sint32& bytesWritten) + { + 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); + 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); + if (writeResult != FALSE) + { + // sometimes we get the result immediately + cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}", + GetLastError()); + } + else + { + // wait for result + cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError()); + // todo - check for error type + DWORD r = WaitForSingleObject(ovlp.hEvent, 2000); + if (r == WAIT_TIMEOUT) + { + cemuLog_logDebug(LogType::Force, "HidWrite internal timeout"); + // return -108 in case of timeout + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return WriteResult::ErrorTimeout; + } + + cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete"); + GetOverlappedResult(this->m_hFile, &ovlp, &bt, false); + cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete"); + } + + free(tempBuffer); + CloseHandle(ovlp.hEvent); + + if (bt != 0) + { + bytesWritten = length; + return WriteResult::Success; + } + return WriteResult::Error; + } + + bool DeviceWindowsHID::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + if (!IsOpened()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceWindowsHID::getDescriptor(): device is not opened"); + return false; + } + if (descType == 0x02) + { + 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) = + this->m_maxPacketSizeRX; // 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) = + this->m_maxPacketSizeTX; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + else + { + cemu_assert_unimplemented(); + } + return false; + } + + bool DeviceWindowsHID::SetProtocol(uint32 ifIndef, uint32 protocol) + { + // ToDo: implement this + // pretend that everything is fine + return true; + } + + bool DeviceWindowsHID::SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + { + sint32 retryCount = 0; + while (true) + { + BOOL r = HidD_SetOutputReport(this->m_hFile, reportData, length); + if (r != FALSE) + break; + Sleep(20); // retry + retryCount++; + if (retryCount >= 50) + { + cemuLog_log(LogType::Force, "nsyshid::DeviceWindowsHID::SetReport(): HID SetReport failed"); + return false; + } + } + return true; + } + + HANDLE OpenDevice(wchar_t* devicePath) + { + return CreateFile(devicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | + FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + } + + void _debugPrintHex(std::string prefix, uint8* data, size_t len) + { + char debugOutput[1024] = {0}; + len = std::min(len, (size_t)100); + for (sint32 i = 0; i < len; i++) + { + sprintf(debugOutput + i * 3, "%02x ", data[i]); + } + fmt::print("{} Data: {}\n", prefix, debugOutput); + cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); + } +} // namespace nsyshid::backend::windows + +#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h new file mode 100644 index 00000000..049b33e4 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -0,0 +1,66 @@ +#ifndef CEMU_NSYSHID_BACKEND_WINDOWS_HID_H +#define CEMU_NSYSHID_BACKEND_WINDOWS_HID_H + +#include "nsyshid.h" + +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include "Backend.h" + +namespace nsyshid::backend::windows +{ + class BackendWindowsHID : public nsyshid::Backend { + public: + BackendWindowsHID(); + + ~BackendWindowsHID(); + + bool IsInitialisedOk() override; + + protected: + void AttachVisibleDevices() override; + + private: + std::shared_ptr CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice); + }; + + class DeviceWindowsHID : public nsyshid::Device { + public: + DeviceWindowsHID(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + wchar_t* devicePath); + + ~DeviceWindowsHID(); + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + + WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) 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; + + private: + wchar_t* m_devicePath; + HANDLE m_hFile; + }; + + HANDLE OpenDevice(wchar_t* devicePath); + + void _debugPrintHex(std::string prefix, uint8* data, size_t len); +} // namespace nsyshid::backend::windows + +#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#endif // CEMU_NSYSHID_BACKEND_WINDOWS_HID_H diff --git a/src/Cafe/OS/libs/nsyshid/Whitelist.cpp b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp new file mode 100644 index 00000000..f20e4c45 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp @@ -0,0 +1,53 @@ +#include "Whitelist.h" + +namespace nsyshid +{ + Whitelist& Whitelist::GetInstance() + { + static Whitelist whitelist; + return whitelist; + } + + Whitelist::Whitelist() + { + // add known devices + { + // lego dimensions portal + m_devices.emplace_back(0x0e6f, 0x0241); + // skylanders portal + m_devices.emplace_back(0x1430, 0x0150); + // disney infinity base + m_devices.emplace_back(0x0e6f, 0x0129); + } + } + + bool Whitelist::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) + { + auto it = std::find(m_devices.begin(), m_devices.end(), + std::tuple(vendorId, productId)); + return it != m_devices.end(); + } + + void Whitelist::AddDevice(uint16 vendorId, uint16 productId) + { + if (!IsDeviceWhitelisted(vendorId, productId)) + { + m_devices.emplace_back(vendorId, productId); + } + } + + void Whitelist::RemoveDevice(uint16 vendorId, uint16 productId) + { + m_devices.remove(std::tuple(vendorId, productId)); + } + + std::list> Whitelist::GetDevices() + { + return m_devices; + } + + void Whitelist::RemoveAllDevices() + { + m_devices.clear(); + } +} // namespace nsyshid diff --git a/src/Cafe/OS/libs/nsyshid/Whitelist.h b/src/Cafe/OS/libs/nsyshid/Whitelist.h new file mode 100644 index 00000000..73b7742b --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Whitelist.h @@ -0,0 +1,32 @@ +#ifndef CEMU_NSYSHID_WHITELIST_H +#define CEMU_NSYSHID_WHITELIST_H + +namespace nsyshid +{ + class Whitelist { + public: + static Whitelist& GetInstance(); + + Whitelist(const Whitelist&) = delete; + + Whitelist& operator=(const Whitelist&) = delete; + + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); + + void AddDevice(uint16 vendorId, uint16 productId); + + void RemoveDevice(uint16 vendorId, uint16 productId); + + std::list> GetDevices(); + + void RemoveAllDevices(); + + private: + Whitelist(); + + // vendorId, productId + std::list> m_devices; + }; +} // namespace nsyshid + +#endif // CEMU_NSYSHID_WHITELIST_H diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index 9b9d2d61..b21e2a43 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -1,289 +1,259 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include +#include #include "nsyshid.h" - -#if BOOST_OS_WINDOWS - -#include -#include -#include - #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" - -#pragma comment(lib,"Setupapi.lib") -#pragma comment(lib,"hid.lib") - -DEFINE_GUID(GUID_DEVINTERFACE_HID, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); +#include "Backend.h" +#include "Whitelist.h" namespace nsyshid { - typedef struct - { - /* +0x00 */ uint32be handle; - /* +0x04 */ uint32 ukn04; - /* +0x08 */ uint16 vendorId; // little-endian ? - /* +0x0A */ uint16 productId; // little-endian ? - /* +0x0C */ uint8 ifIndex; - /* +0x0D */ uint8 subClass; - /* +0x0E */ uint8 protocol; - /* +0x0F */ uint8 paddingGuessed0F; - /* +0x10 */ uint16be maxPacketSizeRX; - /* +0x12 */ uint16be maxPacketSizeTX; - }HIDDevice_t; - - static_assert(offsetof(HIDDevice_t, vendorId) == 0x8, ""); - static_assert(offsetof(HIDDevice_t, productId) == 0xA, ""); - static_assert(offsetof(HIDDevice_t, ifIndex) == 0xC, ""); - static_assert(offsetof(HIDDevice_t, protocol) == 0xE, ""); - - typedef struct _HIDDeviceInfo_t - { - uint32 handle; - uint32 physicalDeviceInstance; - uint16 vendorId; - uint16 productId; - uint8 interfaceIndex; - uint8 interfaceSubClass; - uint8 protocol; - HIDDevice_t* hidDevice; // this info is passed to applications and must remain intact - wchar_t* devicePath; - _HIDDeviceInfo_t* next; - // host - HANDLE hFile; - }HIDDeviceInfo_t; - - HIDDeviceInfo_t* firstDevice = nullptr; + std::list> backendList; + std::list> deviceList; typedef struct _HIDClient_t { - MEMPTR<_HIDClient_t> next; uint32be callbackFunc; // attach/detach callback - }HIDClient_t; + } HIDClient_t; - HIDClient_t* firstHIDClient = nullptr; + std::list HIDClientList; - HANDLE openDevice(wchar_t* devicePath) - { - return CreateFile(devicePath, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | - FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - NULL); - } + std::recursive_mutex hidMutex; - void attachClientToList(HIDClient_t* hidClient) + void AttachClientToList(HIDClient_t* hidClient) { + std::lock_guard lock(hidMutex); // todo - append at the beginning or end of the list? List order matters because it also controls the order in which attach callbacks are called - if (firstHIDClient) - { - hidClient->next = firstHIDClient; - firstHIDClient = hidClient; - } - else - { - hidClient->next = nullptr; - firstHIDClient = hidClient; - } + HIDClientList.push_front(hidClient); } - void attachDeviceToList(HIDDeviceInfo_t* hidDeviceInfo) + void DetachClientFromList(HIDClient_t* hidClient) { - if (firstDevice) - { - hidDeviceInfo->next = firstDevice; - firstDevice = hidDeviceInfo; - } - else - { - hidDeviceInfo->next = nullptr; - firstDevice = hidDeviceInfo; - } + std::lock_guard lock(hidMutex); + HIDClientList.remove(hidClient); } - HIDDeviceInfo_t* getHIDDeviceInfoByHandle(uint32 handle, bool openFileHandle = false) + std::shared_ptr GetDeviceByHandle(uint32 handle, bool openIfClosed = false) { - HIDDeviceInfo_t* deviceItr = firstDevice; - while (deviceItr) + std::shared_ptr device; { - if (deviceItr->handle == handle) + std::lock_guard lock(hidMutex); + for (const auto& d : deviceList) { - if (openFileHandle && deviceItr->hFile == INVALID_HANDLE_VALUE) + if (d->m_hid->handle == handle) { - deviceItr->hFile = openDevice(deviceItr->devicePath); - if (deviceItr->hFile == INVALID_HANDLE_VALUE) - { - cemuLog_log(LogType::Force, "HID: Failed to open device \"{}\"", boost::nowide::narrow(std::wstring(deviceItr->devicePath))); - return nullptr; - } - HidD_SetNumInputBuffers(deviceItr->hFile, 2); // dont cache too many reports + device = d; + break; } - return deviceItr; } - deviceItr = deviceItr->next; + } + if (device != nullptr) + { + if (openIfClosed && !device->IsOpened()) + { + if (!device->Open()) + { + return nullptr; + } + } + return device; } return nullptr; } uint32 _lastGeneratedHidHandle = 1; - uint32 generateHIDHandle() + uint32 GenerateHIDHandle() { + std::lock_guard lock(hidMutex); _lastGeneratedHidHandle++; return _lastGeneratedHidHandle; } const int HID_MAX_NUM_DEVICES = 128; - SysAllocator _devicePool; - std::bitset _devicePoolMask; + SysAllocator HIDPool; + std::queue HIDPoolIndexQueue; - HIDDevice_t* getFreeDevice() + void InitHIDPoolIndexQueue() { - for (sint32 i = 0; i < HID_MAX_NUM_DEVICES; i++) + static bool HIDPoolIndexQueueInitialized = false; + std::lock_guard lock(hidMutex); + if (HIDPoolIndexQueueInitialized) { - if (_devicePoolMask.test(i) == false) - { - _devicePoolMask.set(i); - return _devicePool.GetPtr() + i; - } + return; + } + HIDPoolIndexQueueInitialized = true; + for (size_t i = 0; i < HID_MAX_NUM_DEVICES; i++) + { + HIDPoolIndexQueue.push(i); } - return nullptr; } - void checkAndAddDevice(wchar_t* devicePath, HANDLE hDevice) + HID_t* GetFreeHID() { - HIDD_ATTRIBUTES hidAttr; - hidAttr.Size = sizeof(HIDD_ATTRIBUTES); - if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE) - return; - HIDDevice_t* hidDevice = getFreeDevice(); - if (hidDevice == nullptr) + std::lock_guard lock(hidMutex); + InitHIDPoolIndexQueue(); + if (HIDPoolIndexQueue.empty()) { - cemuLog_log(LogType::Force, "HID: Maximum number of supported devices exceeded"); - return; + return nullptr; } - - HIDDeviceInfo_t* deviceInfo = (HIDDeviceInfo_t*)malloc(sizeof(HIDDeviceInfo_t)); - memset(deviceInfo, 0, sizeof(HIDDeviceInfo_t)); - deviceInfo->devicePath = _wcsdup(devicePath); - deviceInfo->vendorId = hidAttr.VendorID; - deviceInfo->productId = hidAttr.ProductID; - deviceInfo->hFile = INVALID_HANDLE_VALUE; - // generate handle - deviceInfo->handle = generateHIDHandle(); - // get additional device info - sint32 maxPacketInputLength = -1; - sint32 maxPacketOutputLength = -1; - PHIDP_PREPARSED_DATA ppData = nullptr; - if (HidD_GetPreparsedData(hDevice, &ppData)) - { - HIDP_CAPS caps; - if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS) - { - // length includes the report id byte - maxPacketInputLength = caps.InputReportByteLength - 1; - maxPacketOutputLength = caps.OutputReportByteLength - 1; - } - HidD_FreePreparsedData(ppData); - } - if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) - { - cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength); - maxPacketInputLength = 0x20; - } - if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) - { - cemuLog_log(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength); - maxPacketOutputLength = 0x20; - } - // setup HIDDevice struct - deviceInfo->hidDevice = hidDevice; - memset(hidDevice, 0, sizeof(HIDDevice_t)); - hidDevice->handle = deviceInfo->handle; - hidDevice->vendorId = deviceInfo->vendorId; - hidDevice->productId = deviceInfo->productId; - hidDevice->maxPacketSizeRX = maxPacketInputLength; - hidDevice->maxPacketSizeTX = maxPacketOutputLength; - - hidDevice->ukn04 = 0x11223344; - - hidDevice->ifIndex = 1; - hidDevice->protocol = 0; - hidDevice->subClass = 2; - - // todo - other values - //hidDevice->ifIndex = 1; - - - attachDeviceToList(deviceInfo); - + size_t index = HIDPoolIndexQueue.front(); + HIDPoolIndexQueue.pop(); + return HIDPool.GetPtr() + index; } - void initDeviceList() + void ReleaseHID(HID_t* device) { - if (firstDevice) - return; - HDEVINFO hDevInfo; - SP_DEVICE_INTERFACE_DATA DevIntfData; - PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData; - SP_DEVINFO_DATA DevData; - - DWORD dwSize, dwMemberIdx; - - hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); - - if (hDevInfo != INVALID_HANDLE_VALUE) + // this should never happen, but having a safeguard can't hurt + if (device == nullptr) { - DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - dwMemberIdx = 0; - - SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, - dwMemberIdx, &DevIntfData); - - while (GetLastError() != ERROR_NO_MORE_ITEMS) - { - DevData.cbSize = sizeof(DevData); - SetupDiGetDeviceInterfaceDetail( - hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL); - - DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize); - DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - - if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, - DevIntfDetailData, dwSize, &dwSize, &DevData)) - { - HANDLE hHIDDevice = openDevice(DevIntfDetailData->DevicePath); - if (hHIDDevice != INVALID_HANDLE_VALUE) - { - checkAndAddDevice(DevIntfDetailData->DevicePath, hHIDDevice); - CloseHandle(hHIDDevice); - } - } - HeapFree(GetProcessHeap(), 0, DevIntfDetailData); - // next - SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData); - } - SetupDiDestroyDeviceInfoList(hDevInfo); + cemu_assert_error(); } + std::lock_guard lock(hidMutex); + InitHIDPoolIndexQueue(); + size_t index = device - HIDPool.GetPtr(); + HIDPoolIndexQueue.push(index); } const int HID_CALLBACK_DETACH = 0; const int HID_CALLBACK_ATTACH = 1; - uint32 doAttachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo) + uint32 DoAttachCallback(HIDClient_t* hidClient, const std::shared_ptr& device) { - return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_ATTACH); + return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_ATTACH); } - void doDetachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo) + void DoAttachCallbackAsync(HIDClient_t* hidClient, const std::shared_ptr& device) { - PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_DETACH); + coreinitAsyncCallback_add(hidClient->callbackFunc, 3, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_ATTACH); + } + + void DoDetachCallback(HIDClient_t* hidClient, const std::shared_ptr& device) + { + PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_DETACH); + } + + void DoDetachCallbackAsync(HIDClient_t* hidClient, const std::shared_ptr& device) + { + coreinitAsyncCallback_add(hidClient->callbackFunc, 3, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_DETACH); + } + + void AttachBackend(const std::shared_ptr& backend) + { + { + std::lock_guard lock(hidMutex); + backendList.push_back(backend); + } + backend->OnAttach(); + } + + void DetachBackend(const std::shared_ptr& backend) + { + { + std::lock_guard lock(hidMutex); + backendList.remove(backend); + } + backend->OnDetach(); + } + + void DetachAllBackends() + { + std::list> backendListCopy; + { + std::lock_guard lock(hidMutex); + backendListCopy = backendList; + backendList.clear(); + } + for (const auto& backend : backendListCopy) + { + backend->OnDetach(); + } + } + + void AttachDefaultBackends() + { + backend::AttachDefaultBackends(); + } + + bool AttachDevice(const std::shared_ptr& device) + { + std::lock_guard lock(hidMutex); + + // is the device already attached? + { + auto it = std::find(deviceList.begin(), deviceList.end(), device); + if (it != deviceList.end()) + { + cemuLog_logDebug(LogType::Force, + "nsyshid.AttachDevice(): failed to attach device: {:04x}:{:04x}: already attached", + device->m_vendorId, + device->m_productId); + return false; + } + } + + HID_t* hidDevice = GetFreeHID(); + if (hidDevice == nullptr) + { + cemuLog_logDebug(LogType::Force, + "nsyshid.AttachDevice(): failed to attach device: {:04x}:{:04x}: no free device slots left", + device->m_vendorId, + device->m_productId); + return false; + } + hidDevice->handle = GenerateHIDHandle(); + device->AssignHID(hidDevice); + deviceList.push_back(device); + + // do attach callbacks + for (auto client : HIDClientList) + { + DoAttachCallbackAsync(client, device); + } + + cemuLog_logDebug(LogType::Force, "nsyshid.AttachDevice(): device attached: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + return true; + } + + void DetachDevice(const std::shared_ptr& device) + { + { + std::lock_guard lock(hidMutex); + + // remove from list + auto it = std::find(deviceList.begin(), deviceList.end(), device); + if (it == deviceList.end()) + { + cemuLog_logDebug(LogType::Force, "nsyshid.DetachDevice(): device not found: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + return; + } + deviceList.erase(it); + + // do detach callbacks + for (auto client : HIDClientList) + { + DoDetachCallbackAsync(client, device); + } + ReleaseHID(device->m_hid); + } + + device->Close(); + + cemuLog_logDebug(LogType::Force, "nsyshid.DetachDevice(): device removed: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); } void export_HIDAddClient(PPCInterpreter_t* hCPU) @@ -292,15 +262,14 @@ namespace nsyshid ppcDefineParamMPTR(callbackFuncMPTR, 1); cemuLog_logDebug(LogType::Force, "nsyshid.HIDAddClient(0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); hidClient->callbackFunc = callbackFuncMPTR; - attachClientToList(hidClient); - initDeviceList(); + + std::lock_guard lock(hidMutex); + AttachClientToList(hidClient); + // do attach callbacks - HIDDeviceInfo_t* deviceItr = firstDevice; - while (deviceItr) + for (const auto& device : deviceList) { - if (doAttachCallback(hidClient, deviceItr) != 0) - break; - deviceItr = deviceItr->next; + DoAttachCallback(hidClient, device); } osLib_returnFromFunction(hCPU, 0); @@ -310,14 +279,14 @@ namespace nsyshid { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); cemuLog_logDebug(LogType::Force, "nsyshid.HIDDelClient(0x{:08x})", hCPU->gpr[3]); - - // todo + + std::lock_guard lock(hidMutex); + DetachClientFromList(hidClient); + // do detach callbacks - HIDDeviceInfo_t* deviceItr = firstDevice; - while (deviceItr) + for (const auto& device : deviceList) { - doDetachCallback(hidClient, deviceItr); - deviceItr = deviceItr->next; + DoDetachCallback(hidClient, device); } osLib_returnFromFunction(hCPU, 0); @@ -325,127 +294,68 @@ namespace nsyshid void export_HIDGetDescriptor(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU8(descType, 1); // r4 - ppcDefineParamU8(descIndex, 2); // r5 - ppcDefineParamU8(lang, 3); // r6 - ppcDefineParamUStr(output, 4); // r7 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU8(descType, 1); // r4 + ppcDefineParamU8(descIndex, 2); // r5 + ppcDefineParamU8(lang, 3); // r6 + ppcDefineParamUStr(output, 4); // r7 ppcDefineParamU32(outputMaxLength, 5); // r8 - ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 - ppcDefineParamMPTR(cbParamMPTR, 7); // r10 + ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 + ppcDefineParamMPTR(cbParamMPTR, 7); // r10 - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle); - if (hidDeviceInfo) + int returnValue = -1; + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device) { - HANDLE hHIDDevice = openDevice(hidDeviceInfo->devicePath); - if (hHIDDevice != INVALID_HANDLE_VALUE) + memset(output, 0, outputMaxLength); + if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) { - if (descType == 0x02) - { - 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 + 1) = 0x81; // bEndpointAddress - *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes - *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize - *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval - currentWritePtr = currentWritePtr + 7; - // endpoint descriptor 2 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress - *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes - *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize - *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval - currentWritePtr = currentWritePtr + 7; - - cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); - - memcpy(output, configurationDescriptor, std::min(outputMaxLength, sizeof(configurationDescriptor))); - } - else - { - cemu_assert_unimplemented(); - } - CloseHandle(hHIDDevice); + returnValue = 0; } else { - cemu_assert_unimplemented(); + returnValue = -1; } } else { cemu_assert_suspicious(); } - osLib_returnFromFunction(hCPU, 0); + osLib_returnFromFunction(hCPU, returnValue); } void _debugPrintHex(std::string prefix, uint8* data, size_t len) { - char debugOutput[1024] = { 0 }; + char debugOutput[1024] = {0}; len = std::min(len, (size_t)100); for (sint32 i = 0; i < len; i++) { sprintf(debugOutput + i * 3, "%02x ", data[i]); } + fmt::print("{} Data: {}\n", prefix, debugOutput); cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); } - void doHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, MPTR buffer, sint32 length) + void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, + MPTR buffer, sint32 length) { coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR); } void export_HIDSetIdle(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(ukn, 2); // r5 - ppcDefineParamU32(duration, 3); // r6 - ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(ifIndex, 1); // r4 + ppcDefineParamU32(ukn, 2); // r5 + ppcDefineParamU32(duration, 3); // r6 + ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 ppcDefineParamMPTR(callbackParamMPTR, 5); // r8 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)"); // todo if (callbackFuncMPTR) { - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); } else { @@ -456,67 +366,78 @@ namespace nsyshid void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(protocol, 2); // r5 - ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(ifIndex, 1); // r4 + ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)"); - - if (callbackFuncMPTR) + + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + sint32 returnCode = -1; + if (device) { - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + if (!device->IsOpened()) + { + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(): error: device is not opened"); + } + else + { + if (device->SetProtocol(ifIndex, protocol)) + { + returnCode = 0; + } + } } else { - cemu_assert_unimplemented(); + cemu_assert_suspicious(); } - osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred + + if (callbackFuncMPTR) + { + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + } + osLib_returnFromFunction(hCPU, returnCode); } // handler for async HIDSetReport transfers - void _hidSetReportAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidSetReportAsync(std::shared_ptr device, uint8* reportData, sint32 length, + uint8* originalData, + sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { - sint32 retryCount = 0; - while (true) + cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); + if (device->SetReport(reportData, length, originalData, originalLength)) { - BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length); - if (r != FALSE) - break; - Sleep(20); // retry - retryCount++; - if (retryCount >= 40) - { - cemuLog_log(LogType::Force, "HID async SetReport failed"); - sint32 errorCode = -1; - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(originalData), 0); - free(reportData); - return; - } + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + 0, + memory_getVirtualOffsetFromPointer(originalData), + originalLength); + } + else + { + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + -1, + memory_getVirtualOffsetFromPointer(originalData), + 0); } - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, 0, memory_getVirtualOffsetFromPointer(originalData), originalLength); free(reportData); } // handler for synchronous HIDSetReport transfers - sint32 _hidSetReportSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, OSThread_t* osThread) + sint32 _hidSetReportSync(std::shared_ptr device, uint8* reportData, sint32 length, + uint8* originalData, + sint32 originalLength, OSThread_t* osThread) { - //cemuLog_logDebug(LogType::Force, "_hidSetReportSync begin"); _debugPrintHex("_hidSetReportSync Begin", reportData, length); - sint32 retryCount = 0; sint32 returnCode = 0; - while (true) + if (device->SetReport(reportData, length, originalData, originalLength)) { - BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length); - if (r != FALSE) - { - returnCode = originalLength; - break; - } - Sleep(100); // retry - retryCount++; - if (retryCount >= 10) - assert_dbg(); + returnCode = originalLength; } free(reportData); cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); @@ -526,14 +447,15 @@ namespace nsyshid void export_HIDSetReport(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(reportRelatedUkn, 1); // r4 - ppcDefineParamU32(reportId, 2); // r5 - ppcDefineParamUStr(data, 3); // r6 - ppcDefineParamU32(dataLength, 4); // r7 - ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(reportRelatedUkn, 1); // r4 + ppcDefineParamU32(reportId, 2); // r5 + ppcDefineParamUStr(data, 3); // r6 + ppcDefineParamU32(dataLength, 4); // r7 + ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 ppcDefineParamMPTR(callbackParamMPTR, 6); // r9 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn, reportId); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn, + reportId); _debugPrintHex("HIDSetReport", data, dataLength); @@ -542,8 +464,8 @@ namespace nsyshid assert_dbg(); #endif - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); - if (hidDeviceInfo == nullptr) + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDSetReport(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); @@ -552,19 +474,20 @@ namespace nsyshid // prepare report data // note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport) - // Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal - sint32 paddedLength = (dataLength +0x1F)&~0x1F; - uint8* reportData = (uint8*)malloc(paddedLength+1); - memset(reportData, 0, paddedLength+1); + // Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal + sint32 paddedLength = (dataLength + 0x1F) & ~0x1F; + uint8* reportData = (uint8*)malloc(paddedLength + 1); + memset(reportData, 0, paddedLength + 1); reportData[0] = 0; memcpy(reportData + 1, data, dataLength); - // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { - std::future res = std::async(std::launch::async, &_hidSetReportSync, hidDeviceInfo, reportData, paddedLength + 1, data, dataLength, coreinitThread_getCurrentThreadDepr(hCPU)); + std::future res = std::async(std::launch::async, &_hidSetReportSync, device, reportData, + paddedLength + 1, data, dataLength, + coreinitThread_getCurrentThreadDepr(hCPU)); coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); PPCCore_switchToScheduler(); returnCode = res.get(); @@ -572,110 +495,88 @@ namespace nsyshid else { // asynchronous - std::thread(&_hidSetReportAsync, hidDeviceInfo, reportData, paddedLength+1, data, dataLength, callbackFuncMPTR, callbackParamMPTR).detach(); + std::thread(&_hidSetReportAsync, device, reportData, paddedLength + 1, data, dataLength, + callbackFuncMPTR, callbackParamMPTR) + .detach(); returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } - sint32 _hidReadInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength) + sint32 _hidReadInternalSync(std::shared_ptr device, uint8* data, sint32 maxLength) { - DWORD bt; - OVERLAPPED ovlp = { 0 }; - ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - uint8* tempBuffer = (uint8*)malloc(maxLength + 1); - sint32 transferLength = 0; // minus report byte - - _debugPrintHex("HID_READ_BEFORE", data, maxLength); - cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", maxLength); - BOOL readResult = ReadFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp); - if (readResult != FALSE) + if (!device->IsOpened()) { - // sometimes we get the result immediately - if (bt == 0) - transferLength = 0; - else - transferLength = bt - 1; - cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}", GetLastError(), transferLength); + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): cannot read from a non-opened device"); + return -1; } - else + memset(data, 0, maxLength); + + sint32 bytesRead = 0; + Device::ReadResult readResult = device->Read(data, maxLength, bytesRead); + switch (readResult) { - // wait for result - cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError()); - // async hid read is never supposed to return unless there is an response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out) - DWORD r = WaitForSingleObject(ovlp.hEvent, 2000*100); - if (r == WAIT_TIMEOUT) - { - cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError()); - // return -108 in case of timeout - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return -108; - } - - - cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete"); - GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false); - if (bt == 0) - transferLength = 0; - else - transferLength = bt - 1; - cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength); - } - sint32 returnCode = 0; - if (bt != 0) + case Device::ReadResult::Success: { - memcpy(data, tempBuffer + 1, transferLength); - sint32 hidReadLength = transferLength; - - char debugOutput[1024] = { 0 }; - for (sint32 i = 0; i < transferLength; i++) - { - sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]); - } - cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); - - returnCode = transferLength; + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", + bytesRead, + maxLength); + return bytesRead; } - else + break; + case Device::ReadResult::Error: { - cemuLog_log(LogType::Force, "Failed HID read"); - returnCode = -1; + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error"); + return -1; } - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return returnCode; + break; + case Device::ReadResult::ErrorTimeout: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error: timeout"); + return -108; + } + break; + } + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error: unknown"); + return -1; } - void _hidReadAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidReadAsync(std::shared_ptr device, + uint8* data, sint32 maxLength, + MPTR callbackFuncMPTR, + MPTR callbackParamMPTR) { - sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidReadInternalSync(device, data, maxLength); sint32 errorCode = 0; if (returnCode < 0) - errorCode = returnCode; // dont return number of bytes in error code - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode>0)?returnCode:0); + errorCode = returnCode; // don't return number of bytes in error code + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, errorCode, + memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); } - sint32 _hidReadSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread) + sint32 _hidReadSync(std::shared_ptr device, + uint8* data, + sint32 maxLength, + OSThread_t* osThread) { - sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidReadInternalSync(device, data, maxLength); coreinit_resumeThread(osThread, 1000); return returnCode; } void export_HIDRead(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamUStr(data, 1); // r4 - ppcDefineParamU32(maxLength, 2); // r5 - ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamUStr(data, 1); // r4 + ppcDefineParamU32(maxLength, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); - if (hidDeviceInfo == nullptr) + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDRead(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); @@ -685,13 +586,14 @@ namespace nsyshid if (callbackFuncMPTR != MPTR_NULL) { // asynchronous transfer - std::thread(&_hidReadAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); + std::thread(&_hidReadAsync, device, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); returnCode = 0; } else { // synchronous transfer - std::future res = std::async(std::launch::async, &_hidReadSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU)); + std::future res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, + coreinitThread_getCurrentThreadDepr(hCPU)); coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); PPCCore_switchToScheduler(); returnCode = res.get(); @@ -700,81 +602,78 @@ namespace nsyshid osLib_returnFromFunction(hCPU, returnCode); } - sint32 _hidWriteInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength) + sint32 _hidWriteInternalSync(std::shared_ptr device, uint8* data, sint32 maxLength) { - DWORD bt; - OVERLAPPED ovlp = { 0 }; - ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - uint8* tempBuffer = (uint8*)malloc(maxLength + 1); - memcpy(tempBuffer + 1, data, maxLength); - tempBuffer[0] = 0; // report byte? - cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", maxLength); - BOOL WriteResult = WriteFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp); - if (WriteResult != FALSE) + if (!device->IsOpened()) { - // sometimes we get the result immediately - cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}", GetLastError()); + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); + return -1; } - else + sint32 bytesWritten = 0; + Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten); + switch (writeResult) { - // wait for result - cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError()); - // todo - check for error type - DWORD r = WaitForSingleObject(ovlp.hEvent, 2000); - if (r == WAIT_TIMEOUT) - { - cemuLog_logDebug(LogType::Force, "HidWrite internal timeout"); - // return -108 in case of timeout - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return -108; - } - - - cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete"); - GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false); - cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete"); + case Device::WriteResult::Success: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", bytesWritten, + maxLength); + return bytesWritten; } - sint32 returnCode = 0; - if (bt != 0) - returnCode = maxLength; - else - returnCode = -1; - - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return returnCode; + break; + case Device::WriteResult::Error: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error"); + return -1; + } + break; + case Device::WriteResult::ErrorTimeout: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error: timeout"); + return -108; + } + break; + } + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error: unknown"); + return -1; } - void _hidWriteAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidWriteAsync(std::shared_ptr device, + uint8* data, + sint32 maxLength, + MPTR callbackFuncMPTR, + MPTR callbackParamMPTR) { - sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); sint32 errorCode = 0; if (returnCode < 0) - errorCode = returnCode; // dont return number of bytes in error code - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); + errorCode = returnCode; // don't return number of bytes in error code + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, errorCode, + memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); } - sint32 _hidWriteSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread) + sint32 _hidWriteSync(std::shared_ptr device, + uint8* data, + sint32 maxLength, + OSThread_t* osThread) { - sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); coreinit_resumeThread(osThread, 1000); return returnCode; } void export_HIDWrite(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamUStr(data, 1); // r4 - ppcDefineParamU32(maxLength, 2); // r5 - ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamUStr(data, 1); // r4 + ppcDefineParamU32(maxLength, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); - if (hidDeviceInfo == nullptr) + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDWrite(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); @@ -784,13 +683,14 @@ namespace nsyshid if (callbackFuncMPTR != MPTR_NULL) { // asynchronous transfer - std::thread(&_hidWriteAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); + std::thread(&_hidWriteAsync, device, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); returnCode = 0; } else { // synchronous transfer - std::future res = std::async(std::launch::async, &_hidWriteSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU)); + std::future res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, + coreinitThread_getCurrentThreadDepr(hCPU)); coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); PPCCore_switchToScheduler(); returnCode = res.get(); @@ -804,7 +704,8 @@ namespace nsyshid ppcDefineParamU32(errorCode, 0); ppcDefineParamTypePtr(ukn0, uint32be, 1); ppcDefineParamTypePtr(ukn1, uint32be, 2); - cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5]); // todo *ukn0 = 0x3FF; @@ -813,6 +714,114 @@ namespace nsyshid osLib_returnFromFunction(hCPU, 0); } + void Backend::DetachAllDevices() + { + std::lock_guard lock(this->m_devicesMutex); + if (m_isAttached) + { + for (const auto& device : this->m_devices) + { + nsyshid::DetachDevice(device); + } + this->m_devices.clear(); + } + } + + bool Backend::AttachDevice(const std::shared_ptr& device) + { + std::lock_guard lock(this->m_devicesMutex); + if (m_isAttached && nsyshid::AttachDevice(device)) + { + this->m_devices.push_back(device); + return true; + } + return false; + } + + void Backend::DetachDevice(const std::shared_ptr& device) + { + std::lock_guard lock(this->m_devicesMutex); + if (m_isAttached) + { + nsyshid::DetachDevice(device); + this->m_devices.remove(device); + } + } + + std::shared_ptr Backend::FindDevice(std::function&)> isWantedDevice) + { + std::lock_guard lock(this->m_devicesMutex); + auto it = std::find_if(this->m_devices.begin(), this->m_devices.end(), std::move(isWantedDevice)); + if (it != this->m_devices.end()) + { + return *it; + } + return nullptr; + } + + bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) + { + return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); + } + + Backend::Backend() + : m_isAttached(false) + { + } + + void Backend::OnAttach() + { + std::lock_guard lock(this->m_devicesMutex); + m_isAttached = true; + AttachVisibleDevices(); + } + + void Backend::OnDetach() + { + std::lock_guard lock(this->m_devicesMutex); + DetachAllDevices(); + m_isAttached = false; + } + + bool Backend::IsBackendAttached() + { + std::lock_guard lock(this->m_devicesMutex); + return m_isAttached; + } + + Device::Device(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol) + : m_hid(nullptr), + m_vendorId(vendorId), + m_productId(productId), + m_interfaceIndex(interfaceIndex), + m_interfaceSubClass(interfaceSubClass), + m_protocol(protocol), + m_maxPacketSizeRX(0x20), + m_maxPacketSizeTX(0x20) + { + } + + void Device::AssignHID(HID_t* hid) + { + if (hid != nullptr) + { + hid->vendorId = this->m_vendorId; + hid->productId = this->m_productId; + hid->ifIndex = this->m_interfaceIndex; + hid->subClass = this->m_interfaceSubClass; + hid->protocol = this->m_protocol; + hid->ukn04 = 0x11223344; + hid->paddingGuessed0F = 0; + hid->maxPacketSizeRX = this->m_maxPacketSizeRX; + hid->maxPacketSizeTX = this->m_maxPacketSizeTX; + } + this->m_hid = hid; + } + void load() { osLib_addFunction("nsyshid", "HIDAddClient", export_HIDAddClient); @@ -826,19 +835,10 @@ namespace nsyshid osLib_addFunction("nsyshid", "HIDWrite", export_HIDWrite); osLib_addFunction("nsyshid", "HIDDecodeError", export_HIDDecodeError); - firstHIDClient = nullptr; + + // initialise whitelist + Whitelist::GetInstance(); + + AttachDefaultBackends(); } -} - -#else - -namespace nsyshid -{ - void load() - { - // unimplemented - }; -}; - - -#endif +} // namespace nsyshid diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.h b/src/Cafe/OS/libs/nsyshid/nsyshid.h index 051b4e7c..1478adf9 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.h +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.h @@ -1,5 +1,12 @@ #pragma once + namespace nsyshid { + class Backend; + + void AttachBackend(const std::shared_ptr& backend); + + void DetachBackend(const std::shared_ptr& backend); + void load(); -} \ No newline at end of file +} // namespace nsyshid diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index f39a24ea..128c19a5 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -13,6 +13,7 @@ #define WSAESHUTDOWN ESHUTDOWN #define WSAECONNABORTED ECONNABORTED #define WSAHOST_NOT_FOUND EAI_NONAME +#define WSAENOTCONN ENOTCONN #define GETLASTERR errno @@ -40,6 +41,7 @@ #define WU_SO_RCVBUF 0x1002 #define WU_SO_LASTERROR 0x1007 #define WU_SO_NBIO 0x1014 +#define WU_SO_BIO 0x1015 #define WU_SO_NONBLOCK 0x1016 #define WU_TCP_NODELAY 0x2004 @@ -53,6 +55,7 @@ #define WU_SO_SUCCESS 0x0000 #define WU_SO_EWOULDBLOCK 0x0006 #define WU_SO_ECONNRESET 0x0008 +#define WU_SO_ENOTCONN 0x0009 #define WU_SO_EINVAL 0x000B #define WU_SO_EINPROGRESS 0x0016 #define WU_SO_EAFNOSUPPORT 0x0021 @@ -85,7 +88,7 @@ void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU) uint32* __gh_errno_ptr() { - OSThread_t* osThread = coreinitThread_getCurrentThreadDepr(PPCInterpreter_getCurrentInstance()); + OSThread_t* osThread = coreinit::OSGetCurrentThread(); return &osThread->context.error; } @@ -148,8 +151,11 @@ sint32 _translateError(sint32 returnCode, sint32 wsaError, sint32 mode = _ERROR_ case WSAESHUTDOWN: _setSockError(WU_SO_ESHUTDOWN); break; + case WSAENOTCONN: + _setSockError(WU_SO_ENOTCONN); + break; default: - cemuLog_logDebug(LogType::Force, "Unhandled wsaError {}\n", wsaError); + cemuLog_logDebug(LogType::Force, "Unhandled wsaError {}", wsaError); _setSockError(99999); // unhandled error } return -1; @@ -157,7 +163,7 @@ sint32 _translateError(sint32 returnCode, sint32 wsaError, sint32 mode = _ERROR_ void nsysnetExport_socketlasterr(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::Socket, "socketlasterr() -> {}", _getSockError()); + cemuLog_logDebug(LogType::Socket, "socketlasterr() -> {}", _getSockError()); osLib_returnFromFunction(hCPU, _getSockError()); } @@ -485,9 +491,9 @@ void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) if (r != 0) cemu_assert_suspicious(); } - else if (optname == WU_SO_NBIO) + else if (optname == WU_SO_NBIO || optname == WU_SO_BIO) { - // similar to WU_SO_NONBLOCK but always sets non-blocking mode regardless of option value + // similar to WU_SO_NONBLOCK but always sets blocking (_BIO) or non-blocking (_NBIO) mode regardless of option value if (optlen == 4) { sint32 optvalLE = _swapEndianU32(*(uint32*)optval); @@ -498,9 +504,10 @@ void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) } else cemu_assert_suspicious(); - u_long mode = 1; + bool setNonBlocking = optname == WU_SO_NBIO; + u_long mode = setNonBlocking ? 1 : 0; _socket_nonblock(vs->s, mode); - vs->isNonBlocking = true; + vs->isNonBlocking = setNonBlocking; } else if (optname == WU_SO_NONBLOCK) { diff --git a/src/Cafe/OS/libs/snd_core/ax_aux.cpp b/src/Cafe/OS/libs/snd_core/ax_aux.cpp index 837adf06..a9176562 100644 --- a/src/Cafe/OS/libs/snd_core/ax_aux.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_aux.cpp @@ -218,9 +218,10 @@ namespace snd_core AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr(); cbStruct->numChannels = tvChannelCount; cbStruct->numSamples = sampleCount; - ppcInterpreterCurrentInstance->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); - ppcInterpreterCurrentInstance->gpr[4] = __AXAuxTVCallbackUserParam[auxBusIndex]; - ppcInterpreterCurrentInstance->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + hCPU->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); + hCPU->gpr[4] = __AXAuxTVCallbackUserParam[auxBusIndex]; + hCPU->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); PPCCore_executeCallbackInternal(auxCBFuncMPTR); } else @@ -255,9 +256,10 @@ namespace snd_core AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr(); cbStruct->numChannels = drcChannelCount; cbStruct->numSamples = sampleCount; - ppcInterpreterCurrentInstance->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); - ppcInterpreterCurrentInstance->gpr[4] = __AXAuxDRCCallbackUserParam[auxBusIndex + drcIndex * 3]; - ppcInterpreterCurrentInstance->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + hCPU->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); + hCPU->gpr[4] = __AXAuxDRCCallbackUserParam[auxBusIndex + drcIndex * 3]; + hCPU->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); PPCCore_executeCallbackInternal(auxCBFuncMPTR); } else diff --git a/src/Cafe/OS/libs/snd_core/ax_voice.cpp b/src/Cafe/OS/libs/snd_core/ax_voice.cpp index 8a49b0f4..877eecab 100644 --- a/src/Cafe/OS/libs/snd_core/ax_voice.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_voice.cpp @@ -3,6 +3,7 @@ #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "util/helpers/fspinlock.h" namespace snd_core @@ -120,7 +121,7 @@ namespace snd_core { return -2; } - MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) { if (__AXUserProtectionArray[i].threadMPTR == currentThreadMPTR) @@ -151,7 +152,7 @@ namespace snd_core PPCCore_deboostQuantum(10000); if (AXIst_IsFrameBeingProcessed()) return -2; - MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) { if (__AXUserProtectionArray[i].threadMPTR == currentThreadMPTR) @@ -206,7 +207,7 @@ namespace snd_core if (AXIst_IsFrameBeingProcessed()) isProtected = __AXVoiceProtection[index].threadMPTR != MPTR_NULL; else - isProtected = __AXVoiceProtection[index].threadMPTR != coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + isProtected = __AXVoiceProtection[index].threadMPTR != memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); return isProtected; } @@ -219,7 +220,7 @@ namespace snd_core return; if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) { - __AXVoiceProtection[index].threadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + __AXVoiceProtection[index].threadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); // does not set count? } } @@ -246,7 +247,7 @@ namespace snd_core } if (AXIst_IsFrameBeingProcessed()) return -2; - MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) { __AXVoiceProtection[index].threadMPTR = currentThreadMPTR; @@ -286,7 +287,7 @@ namespace snd_core } if (AXIst_IsFrameBeingProcessed()) return -2; - MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); if (__AXVoiceProtection[index].threadMPTR == currentThreadMPTR) { if (__AXVoiceProtection[index].count > 0) diff --git a/src/Cafe/OS/libs/swkbd/swkbd.cpp b/src/Cafe/OS/libs/swkbd/swkbd.cpp index 6cc88874..d30992b0 100644 --- a/src/Cafe/OS/libs/swkbd/swkbd.cpp +++ b/src/Cafe/OS/libs/swkbd/swkbd.cpp @@ -276,7 +276,6 @@ void swkbdExport_SwkbdDisappearKeyboard(PPCInterpreter_t* hCPU) void swkbdExport_SwkbdGetInputFormString(PPCInterpreter_t* hCPU) { - debug_printf("SwkbdGetInputFormString__3RplFv LR: %08x\n", hCPU->spr.LR); for(sint32 i=0; iformStringLength; i++) { swkbdInternalState->formStringBufferBE[i] = _swapEndianU16(swkbdInternalState->formStringBuffer[i]); @@ -287,7 +286,6 @@ void swkbdExport_SwkbdGetInputFormString(PPCInterpreter_t* hCPU) void swkbdExport_SwkbdIsDecideOkButton(PPCInterpreter_t* hCPU) { - debug_printf("SwkbdIsDecideOkButton__3RplFPb LR: %08x\n", hCPU->spr.LR); if (swkbdInternalState->decideButtonWasPressed) osLib_returnFromFunction(hCPU, 1); else diff --git a/src/Cafe/OS/libs/zlib125/zlib125.cpp b/src/Cafe/OS/libs/zlib125/zlib125.cpp index 72855c61..25df6a9d 100644 --- a/src/Cafe/OS/libs/zlib125/zlib125.cpp +++ b/src/Cafe/OS/libs/zlib125/zlib125.cpp @@ -28,8 +28,7 @@ static_assert(sizeof(z_stream_ppc2) == 0x38); voidpf zcallocWrapper(voidpf opaque, uInt items, uInt size) { z_stream_ppc2* zstream = (z_stream_ppc2*)opaque; - - PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = zstream->opaque.GetMPTR(); hCPU->gpr[4] = items; hCPU->gpr[5] = size; @@ -41,7 +40,7 @@ voidpf zcallocWrapper(voidpf opaque, uInt items, uInt size) void zcfreeWrapper(voidpf opaque, voidpf baseIndex) { z_stream_ppc2* zstream = (z_stream_ppc2*)opaque; - PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance; + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = zstream->opaque.GetMPTR(); hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(baseIndex); PPCCore_executeCallbackInternal(zstream->zfree.GetMPTR()); diff --git a/src/Cafe/TitleList/AppType.h b/src/Cafe/TitleList/AppType.h new file mode 100644 index 00000000..deca7560 --- /dev/null +++ b/src/Cafe/TitleList/AppType.h @@ -0,0 +1,23 @@ +#pragma once + +enum class APP_TYPE : uint32 +{ + GAME = 0x80000000, + GAME_UPDATE = 0x0800001B, + GAME_DLC = 0x0800000E, + // data titles + VERSION_DATA_TITLE = 0x10000015, + DRC_FIRMWARE = 0x10000013, + DRC_TEXTURE_ATLAS = 0x1000001A, +}; + +// allow direct comparison with uint32 +inline bool operator==(APP_TYPE lhs, uint32 rhs) +{ + return static_cast(lhs) == rhs; +} + +inline bool operator==(uint32 lhs, APP_TYPE rhs) +{ + return lhs == static_cast(rhs); +} diff --git a/src/Cafe/TitleList/GameInfo.h b/src/Cafe/TitleList/GameInfo.h index d1c557ab..6e922b93 100644 --- a/src/Cafe/TitleList/GameInfo.h +++ b/src/Cafe/TitleList/GameInfo.h @@ -27,17 +27,13 @@ public: void SetBase(const TitleInfo& titleInfo) { - m_base = titleInfo; + if (IsPrioritizedVersionOrFormat(m_base, titleInfo)) + m_base = titleInfo; } void SetUpdate(const TitleInfo& titleInfo) { - if (HasUpdate()) - { - if (titleInfo.GetAppTitleVersion() > m_update.GetAppTitleVersion()) - m_update = titleInfo; - } - else + if (IsPrioritizedVersionOrFormat(m_update, titleInfo)) m_update = titleInfo; } @@ -53,7 +49,7 @@ public: auto it = std::find_if(m_aoc.begin(), m_aoc.end(), [aocTitleId](const TitleInfo& rhs) { return rhs.GetAppTitleId() == aocTitleId; }); if (it != m_aoc.end()) { - if(it->GetAppTitleVersion() >= aocVersion) + if (!IsPrioritizedVersionOrFormat(*it, titleInfo)) return; m_aoc.erase(it); } @@ -126,6 +122,25 @@ public: } private: + bool IsPrioritizedVersionOrFormat(const TitleInfo& currentTitle, const TitleInfo& newTitle) + { + if (!currentTitle.IsValid()) + return true; // always prefer a valid title over an invalid one + // always prefer higher version + if (newTitle.GetAppTitleVersion() > currentTitle.GetAppTitleVersion()) + return true; + // never prefer lower version + if (newTitle.GetAppTitleVersion() < currentTitle.GetAppTitleVersion()) + return false; + // for users which have both NUS and non-NUS titles in their games folder we want to prioritize non-NUS formats + // this is to stay consistent with previous Cemu versions which did not support NUS format at all + TitleInfo::TitleDataFormat currentFormat = currentTitle.GetFormat(); + TitleInfo::TitleDataFormat newFormat = newTitle.GetFormat(); + if (currentFormat != TitleInfo::TitleDataFormat::NUS && newFormat == TitleInfo::TitleDataFormat::NUS) + return false; + return true; + }; + TitleInfo m_base; TitleInfo m_update; std::vector m_aoc; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 5f8ba8de..db9951d6 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -81,6 +81,7 @@ TitleInfo::TitleInfo(const fs::path& path, std::string_view subPath) if (!path.has_filename()) { m_isValid = false; + SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE); return; } m_isValid = true; @@ -103,6 +104,7 @@ TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo) if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS && cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE && cachedInfo.titleDataFormat != TitleDataFormat::WUD && + cachedInfo.titleDataFormat != TitleDataFormat::NUS && cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE) return; if (cachedInfo.path.empty()) @@ -201,13 +203,19 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF } } else if (boost::iends_with(filenameStr, ".wud") || - boost::iends_with(filenameStr, ".wux") || - boost::iends_with(filenameStr, ".iso")) + boost::iends_with(filenameStr, ".wux") || + boost::iends_with(filenameStr, ".iso")) { formatOut = TitleDataFormat::WUD; pathOut = path; return true; } + else if (boost::iequals(filenameStr, "title.tmd")) + { + formatOut = TitleDataFormat::NUS; + pathOut = path; + return true; + } else if (boost::iends_with(filenameStr, ".wua")) { formatOut = TitleDataFormat::WIIU_ARCHIVE; @@ -272,6 +280,7 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF return true; } } + SetInvalidReason(InvalidReason::UNKNOWN_FORMAT); return false; } @@ -324,6 +333,12 @@ uint64 TitleInfo::GetUID() return m_uid; } +void TitleInfo::SetInvalidReason(InvalidReason reason) +{ + if(m_invalidReason == InvalidReason::NONE) + m_invalidReason = reason; // only update reason when it hasn't been set before +} + std::mutex sZArchivePoolMtx; std::map> sZArchivePool; @@ -391,18 +406,29 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, if (!r) { cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE); return false; } } - else if (m_titleFormat == TitleDataFormat::WUD) + else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS) { + FSTVolume::ErrorCode fstError; if (m_mountpoints.empty()) { cemu_assert_debug(!m_wudVolume); - m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath); + if(m_titleFormat == TitleDataFormat::WUD) + m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath, &fstError); // open wud/wux + else + m_wudVolume = FSTVolume::OpenFromContentFolder(m_fullPath.parent_path(), &fstError); // open from .app files directory, the path points to /title.tmd } if (!m_wudVolume) + { + if (fstError == FSTVolume::ErrorCode::DISC_KEY_MISSING) + SetInvalidReason(InvalidReason::NO_DISC_KEY); + else if (fstError == FSTVolume::ErrorCode::TITLE_TIK_MISSING) + SetInvalidReason(InvalidReason::NO_TITLE_TIK); return false; + } bool r = FSCDeviceWUD_Mount(virtualPath, subfolder, m_wudVolume, mountPriority); cemu_assert_debug(r); if (!r) @@ -449,7 +475,7 @@ void TitleInfo::Unmount(std::string_view virtualPath) { if (m_wudVolume) { - cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD); + cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS); delete m_wudVolume; m_wudVolume = nullptr; } @@ -524,6 +550,7 @@ bool TitleInfo::ParseXmlInfo() m_parsedAppXml = nullptr; m_parsedCosXml = nullptr; m_isValid = false; + SetInvalidReason(InvalidReason::MISSING_XML_FILES); return false; } m_isValid = true; @@ -680,6 +707,9 @@ std::string TitleInfo::GetPrintPath() const case TitleDataFormat::WUD: tmp.append(" [WUD]"); break; + case TitleDataFormat::NUS: + tmp.append(" [NUS]"); + break; case TitleDataFormat::WIIU_ARCHIVE: tmp.append(" [WUA]"); break; diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index b8b781a4..eca6624d 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -3,6 +3,7 @@ #include "Cafe/Filesystem/fsc.h" #include "config/CemuConfig.h" // for CafeConsoleRegion. Move to NCrypto? #include "TitleId.h" +#include "AppType.h" #include "ParsedMetaXml.h" enum class CafeTitleFileType @@ -59,10 +60,21 @@ public: HOST_FS = 1, // host filesystem directory (fullPath points to root with content/code/meta subfolders) 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 // error INVALID_STRUCTURE = 0, }; + enum class InvalidReason : uint8 + { + NONE = 0, + BAD_PATH_OR_INACCESSIBLE = 1, + UNKNOWN_FORMAT = 2, + NO_DISC_KEY = 3, + NO_TITLE_TIK = 4, + MISSING_XML_FILES = 4, + }; + struct CachedInfo { TitleDataFormat titleDataFormat; @@ -99,6 +111,7 @@ public: CachedInfo MakeCacheEntry(); bool IsValid() const; + InvalidReason GetInvalidReason() const { return m_invalidReason; } uint64 GetUID(); // returns a unique identifier derived from the absolute canonical title location which can be used to identify this title by its location. May not persist across sessions, especially when Cemu is used portable fs::path GetPath() const; @@ -122,9 +135,7 @@ public: if(!IsValid()) return false; uint32 appType = GetAppType(); - if(appType == 0) - return false; // not a valid app_type, but handle this in case some users use placeholder .xml data with fields zeroed-out - return ((appType>>24)&0x80) == 0; + return appType == APP_TYPE::DRC_FIRMWARE || appType == APP_TYPE::DRC_TEXTURE_ATLAS || appType == APP_TYPE::VERSION_DATA_TITLE; } // API which requires parsed meta data or cached info @@ -149,7 +160,7 @@ public: return m_parsedMetaXml; } - std::string GetPrintPath() const; // formatted path for log writing + std::string GetPrintPath() const; // formatted path including type and WUA subpath. Intended for logging and user-facing information std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..." static std::string GetUniqueTempMountingPath(); @@ -182,7 +193,7 @@ private: bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut); void CalcUID(); - + void SetInvalidReason(InvalidReason reason); bool ParseAppXml(std::vector& appXmlData); bool m_isValid{ false }; @@ -190,6 +201,7 @@ private: fs::path m_fullPath; std::string m_subPath; // used for formats where fullPath isn't unique on its own (like WUA) uint64 m_uid{}; + InvalidReason m_invalidReason{ InvalidReason::NONE }; // if m_isValid == false, this contains a more detailed error code // mounting info std::vector> m_mountpoints; class FSTVolume* m_wudVolume{}; diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 6f39a760..61e29195 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -14,7 +14,7 @@ bool sTLInitialized{ false }; fs::path sTLCacheFilePath; // lists for tracking known titles -// note: The list may only contain titles with valid meta data. Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion +// note: The list may only contain titles with valid meta data (except for certain system titles). Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion std::mutex sTLMutex; std::vector sTLList; std::vector sTLListPending; @@ -335,17 +335,25 @@ bool CafeTitleList::RefreshWorkerThread() return true; } -bool _IsKnownFileExtension(std::string fileExtension) +bool _IsKnownFileNameOrExtension(const fs::path& path) { + std::string fileExtension = _pathToUtf8(path.extension()); for (auto& it : fileExtension) - if (it >= 'A' && it <= 'Z') - it -= ('A' - 'a'); + it = _ansiToLower(it); + if(fileExtension == ".tmd") + { + // must be "title.tmd" + std::string fileName = _pathToUtf8(path.filename()); + for (auto& it : fileName) + it = _ansiToLower(it); + return fileName == "title.tmd"; + } return fileExtension == ".wud" || fileExtension == ".wux" || fileExtension == ".iso" || fileExtension == ".wua"; - // note: To detect extracted titles with RPX we use the content/code/meta folder structure + // note: To detect extracted titles with RPX we rely on the presence of the content,code,meta directory structure } void CafeTitleList::ScanGamePath(const fs::path& path) @@ -393,7 +401,6 @@ void CafeTitleList::ScanGamePath(const fs::path& path) else if (it.is_directory(ec)) { dirsInDirectory.emplace_back(it.path()); - checkForTitleFolders(_pathToUtf8(it.path().filename())); } } @@ -402,10 +409,10 @@ void CafeTitleList::ScanGamePath(const fs::path& path) // always check individual files for (auto& it : filesInDirectory) { - // since checking files is slow, we only do it for known file extensions + // since checking individual files is slow, we limit it to known file names or extensions if (!it.has_extension()) continue; - if (!_IsKnownFileExtension(_pathToUtf8(it.extension()))) + if (!_IsKnownFileNameOrExtension(it)) continue; AddTitleFromPath(it); } @@ -662,8 +669,7 @@ GameInfo2 CafeTitleList::GetGameInfo(TitleId titleId) uint64 baseTitleId; if (!FindBaseTitleId(titleId, baseTitleId)) { - cemuLog_logDebug(LogType::Force, "Failed to translate title id in GetGameInfo()"); - return gameInfo; + cemu_assert_suspicious(); } // determine if an optional update title id exists TitleIdParser tip(baseTitleId); diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 559aa660..299ed62d 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -1,7 +1,7 @@ #include "CemuLogging.h" +#include "util/helpers/helpers.h" #include "config/CemuConfig.h" #include "config/ActiveSettings.h" -#include "util/helpers/helpers.h" #include #include @@ -9,6 +9,8 @@ #include +uint64 s_loggingFlagMask = cemuLog_getFlag(LogType::Force); + struct _LogContext { std::condition_variable_any log_condition; @@ -32,25 +34,26 @@ struct _LogContext const std::map g_logging_window_mapping { - {LogType::UnsupportedAPI, "Unsupported API calls"}, - {LogType::CoreinitLogging, "Coreinit Logging"}, - {LogType::CoreinitFile, "Coreinit File-Access"}, - {LogType::ThreadSync, "Coreinit Thread-Synchronization"}, - {LogType::CoreinitMem, "Coreinit Memory"}, - {LogType::CoreinitMP, "Coreinit MP"}, - {LogType::CoreinitThread, "Coreinit Thread"}, - {LogType::nn_nfp, "nn::nfp"}, - {LogType::GX2, "GX2"}, - {LogType::SoundAPI, "Audio"}, - {LogType::InputAPI, "Input"}, - {LogType::Socket, "Socket"}, - {LogType::Save, "Save"}, - {LogType::H264, "H264"}, - {LogType::Patches, "Graphic pack patches"}, - {LogType::TextureCache, "Texture cache"}, - {LogType::TextureReadback, "Texture readback"}, - {LogType::OpenGLLogging, "OpenGL debug output"}, - {LogType::VulkanValidation, "Vulkan validation layer"}, + {LogType::UnsupportedAPI, "Unsupported API calls"}, + {LogType::CoreinitLogging, "Coreinit Logging"}, + {LogType::CoreinitFile, "Coreinit File-Access"}, + {LogType::CoreinitThreadSync, "Coreinit Thread-Synchronization"}, + {LogType::CoreinitMem, "Coreinit Memory"}, + {LogType::CoreinitMP, "Coreinit MP"}, + {LogType::CoreinitThread, "Coreinit Thread"}, + {LogType::NN_NFP, "nn::nfp"}, + {LogType::NN_FP, "nn::fp"}, + {LogType::GX2, "GX2"}, + {LogType::SoundAPI, "Audio"}, + {LogType::InputAPI, "Input"}, + {LogType::Socket, "Socket"}, + {LogType::Save, "Save"}, + {LogType::H264, "H264"}, + {LogType::Patches, "Graphic pack patches"}, + {LogType::TextureCache, "Texture cache"}, + {LogType::TextureReadback, "Texture readback"}, + {LogType::OpenGLLogging, "OpenGL debug output"}, + {LogType::VulkanValidation, "Vulkan validation layer"}, }; LogCallbacks* g_logCallbacks = nullptr; @@ -66,27 +69,11 @@ void cemuLog_unregisterLogCallbacks() } -uint64 cemuLog_getFlag(LogType type) -{ - return type <= LogType::Force ? 0 : (1ULL << ((uint64)type - 1)); -} - bool cemuLog_advancedPPCLoggingEnabled() { return GetConfig().advanced_ppc_logging; } -bool cemuLog_isLoggingEnabled(LogType type) -{ - if (type == LogType::Placeholder) - return false; - - if (type == LogType::None) - return false; - - return (type == LogType::Force) || ((GetConfig().log_flag.GetValue() & cemuLog_getFlag(type)) != 0); -} - void cemuLog_thread() { SetThreadName("cemuLog_thread"); @@ -186,14 +173,6 @@ bool cemuLog_log(LogType type, std::u8string_view text) return cemuLog_log(type, s); } -bool cemuLog_log(LogType type, std::wstring_view text) -{ - if (!cemuLog_isLoggingEnabled(type)) - return false; - - return cemuLog_log(type, boost::nowide::narrow(text.data(), text.size())); -} - void cemuLog_waitForFlush() { cemuLog_createLogFile(false); @@ -213,12 +192,7 @@ std::unique_lock cemuLog_acquire() return std::unique_lock(LogContext.log_mutex); } -void cemuLog_setFlag(LogType loggingType, bool isEnable) +void cemuLog_setActiveLoggingFlags(uint64 flagMask) { - if (isEnable) - GetConfig().log_flag = GetConfig().log_flag.GetValue() | cemuLog_getFlag(loggingType); - else - GetConfig().log_flag = GetConfig().log_flag.GetValue() & ~cemuLog_getFlag(loggingType); - - g_config.Save(); -} \ No newline at end of file + s_loggingFlagMask = flagMask | cemuLog_getFlag(LogType::Force); +} diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index cee103b9..e7d9ba76 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -1,42 +1,46 @@ #pragma once +extern uint64 s_loggingFlagMask; + enum class LogType : sint32 { - // note: IDs must not exceed 63 - Placeholder = -2, - None = -1, - Force = 0, // this logging type is always on - CoreinitFile = 1, - GX2 = 2, - UnsupportedAPI = 3, - ThreadSync = 4, - SoundAPI = 5, // any audio related API - InputAPI = 6, // any input related API - Socket = 7, - Save = 8, - CoreinitMem = 9, // coreinit memory functions - H264 = 10, - OpenGLLogging = 11, // OpenGL debug logging - TextureCache = 12, // texture cache warnings and info - VulkanValidation = 13, // Vulkan validation layer - nn_nfp = 14, // nn_nfp (Amiibo) API - Patches = 15, - CoreinitMP = 16, - CoreinitThread = 17, - CoreinitLogging = 18, // OSReport, OSConsoleWrite etc. - CoreinitMemoryMapping = 19, // OSGetAvailPhysAddrRange, OSAllocVirtAddr, OSMapMemory etc. - CoreinitAlarm = 23, + // 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 - PPC_IPC = 20, - NN_AOC = 21, - NN_PDM = 22, - - TextureReadback = 30, + CoreinitFile = 0, + GX2 = 1, + UnsupportedAPI = 2, + SoundAPI = 4, // any audio related API + InputAPI = 5, // any input related API + Socket = 6, + Save = 7, + H264 = 9, + OpenGLLogging = 10, // OpenGL debug logging + TextureCache = 11, // texture cache warnings and info + VulkanValidation = 12, // Vulkan validation layer + Patches = 14, + CoreinitMem = 8, // coreinit memory functions + CoreinitMP = 15, + CoreinitThread = 16, + CoreinitLogging = 17, // OSReport, OSConsoleWrite etc. + CoreinitMemoryMapping = 18, // OSGetAvailPhysAddrRange, OSAllocVirtAddr, OSMapMemory etc. + CoreinitAlarm = 22, + CoreinitThreadSync = 3, + + PPC_IPC = 19, + NN_AOC = 20, + NN_PDM = 21, + NN_OLV = 23, + NN_NFP = 13, + NN_FP = 24, + + TextureReadback = 29, ProcUi = 40, Recompiler = 60, - APIErrors = 0, // alias for Force. Logs bad parameters or other API errors in OS libs }; template <> @@ -52,28 +56,22 @@ struct fmt::formatter : formatter { void cemuLog_writeLineToLog(std::string_view text, bool date = true, bool new_line = true); inline void cemuLog_writePlainToLog(std::string_view text) { cemuLog_writeLineToLog(text, false, false); } -bool cemuLog_isLoggingEnabled(LogType type); +void cemuLog_setActiveLoggingFlags(uint64 flagMask); + +inline uint64 cemuLog_getFlag(LogType type) +{ + return 1ULL << (uint64)type; +} + +inline bool cemuLog_isLoggingEnabled(LogType type) +{ + return (s_loggingFlagMask & cemuLog_getFlag(type)) != 0; +} bool cemuLog_log(LogType type, std::string_view text); bool cemuLog_log(LogType type, std::u8string_view text); -bool cemuLog_log(LogType type, std::wstring_view text); void cemuLog_waitForFlush(); // wait until all log lines are written -template -auto ForwardEnum(T t) -{ - if constexpr (std::is_enum_v) - return fmt::underlying(t); - else - return std::forward(t); -} - -template -auto ForwardEnum(std::tuple t) -{ - return std::apply([](auto... x) { return std::make_tuple(ForwardEnum(x)...); }, t); -} - template bool cemuLog_log(LogType type, std::basic_string formatStr, TArgs&&... args) { @@ -87,7 +85,7 @@ bool cemuLog_log(LogType type, std::basic_string formatStr, TArgs&&... args) else { const auto format_view = fmt::basic_string_view(formatStr); - const auto text = fmt::vformat(format_view, fmt::make_format_args>(ForwardEnum(args)...)); + const auto text = fmt::vformat(format_view, fmt::make_format_args>(args...)); cemuLog_log(type, std::basic_string_view(text.data(), text.size())); } return true; @@ -96,6 +94,8 @@ bool cemuLog_log(LogType type, std::basic_string formatStr, TArgs&&... args) template bool cemuLog_log(LogType type, const T* format, TArgs&&... args) { + if (!cemuLog_isLoggingEnabled(type)) + return false; auto format_str = std::basic_string(format); return cemuLog_log(type, format_str, std::forward(args)...); } @@ -125,7 +125,6 @@ void cemuLog_unregisterLogCallbacks(); bool cemuLog_advancedPPCLoggingEnabled(); uint64 cemuLog_getFlag(LogType type); -void cemuLog_setFlag(LogType type, bool enabled); fs::path cemuLog_GetLogFilePath(); void cemuLog_createLogFile(bool triggeredByCrash); diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp index d6f48718..7637ee01 100644 --- a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp @@ -375,7 +375,7 @@ bool DownloadManager::syncAccountTickets() for (auto& tiv : resultTicketIds.tivs) { index++; - std::string msg = _("Downloading account ticket"); + std::string msg = "Downloading account ticket"; msg.append(fmt::format(" {0}/{1}", index, count)); setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); // skip if already cached @@ -428,7 +428,7 @@ bool DownloadManager::syncAccountTickets() bool DownloadManager::syncSystemTitleTickets() { - setStatusMessage(std::string(_("Downloading system tickets...")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage("Downloading system tickets...", DLMGR_STATUS_CODE::CONNECTING); // todo - add GetAuth() function NAPI::AuthInfo authInfo; authInfo.accountId = m_authInfo.nnidAccountName; @@ -490,7 +490,7 @@ bool DownloadManager::syncSystemTitleTickets() // build list of updates for which either an installed game exists or the base title ticket is cached bool DownloadManager::syncUpdateTickets() { - setStatusMessage(std::string(_("Retrieving update information...")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage("Retrieving update information...", DLMGR_STATUS_CODE::CONNECTING); // download update version list downloadTitleVersionList(); if (!m_hasTitleVersionList) @@ -512,7 +512,7 @@ bool DownloadManager::syncUpdateTickets() if (titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) continue; - std::string msg = _("Downloading ticket"); + std::string msg = "Downloading ticket"; msg.append(fmt::format(" {0}/{1}", updateIndex, numUpdates)); updateIndex++; setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); @@ -565,12 +565,12 @@ bool DownloadManager::syncTicketCache() for (auto& ticketInfo : m_ticketCache) { index++; - std::string msg = _("Downloading meta data"); + std::string msg = "Downloading meta data"; msg.append(fmt::format(" {0}/{1}", index, count)); setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); prepareIDBE(ticketInfo.titleId); } - setStatusMessage(std::string(_("Connected. Right click entries in the list to start downloading")), DLMGR_STATUS_CODE::CONNECTED); + setStatusMessage("Connected. Right click entries in the list to start downloading", DLMGR_STATUS_CODE::CONNECTED); return true; } @@ -656,7 +656,7 @@ void DownloadManager::_handle_connect() // reset login state m_iasToken.serviceAccountId.clear(); m_iasToken.deviceToken.clear(); - setStatusMessage(std::string(_("Logging in..")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage("Logging in...", DLMGR_STATUS_CODE::CONNECTING); // retrieve ECS AccountId + DeviceToken from cache if (s_nupFileCache) { @@ -679,7 +679,7 @@ void DownloadManager::_handle_connect() cemuLog_log(LogType::Force, "Failed to request IAS token"); cemu_assert_debug(false); m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Login failed. Outdated or incomplete online files?")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage("Login failed. Outdated or incomplete online files?", DLMGR_STATUS_CODE::FAILED); return; } } @@ -687,16 +687,16 @@ void DownloadManager::_handle_connect() if (!_connect_queryAccountStatusAndServiceURLs()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Failed to query account status. Invalid account information?")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage("Failed to query account status. Invalid account information?", DLMGR_STATUS_CODE::FAILED); return; } // load ticket cache and sync - setStatusMessage(std::string(_("Updating ticket cache")), DLMGR_STATUS_CODE::CONNECTING); + setStatusMessage("Updating ticket cache", DLMGR_STATUS_CODE::CONNECTING); loadTicketCache(); if (!syncTicketCache()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Failed to request tickets (invalid NNID?)")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage("Failed to request tickets (invalid NNID?)", DLMGR_STATUS_CODE::FAILED); return; } searchForIncompleteDownloads(); @@ -720,7 +720,7 @@ void DownloadManager::connect( if (nnidAccountName.empty()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("This account is not linked with an NNID")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage("This account is not linked with an NNID", DLMGR_STATUS_CODE::FAILED); return; } runManager(); @@ -730,7 +730,7 @@ void DownloadManager::connect( { cemuLog_log(LogType::Force, "DLMgr: Invalid password hash"); m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(std::string(_("Failed. Account does not have password set")), DLMGR_STATUS_CODE::FAILED); + setStatusMessage("Failed. Account does not have password set", DLMGR_STATUS_CODE::FAILED); return; } m_authInfo.region = region; @@ -1058,7 +1058,7 @@ void DownloadManager::asyncPackageDownloadTMD(Package* package) std::unique_lock _l(m_mutex); if (!tmdResult.isValid) { - setPackageError(package, from_wxString(_("TMD download failed"))); + setPackageError(package, "TMD download failed"); package->state.isDownloadingTMD = false; return; } @@ -1067,7 +1067,7 @@ void DownloadManager::asyncPackageDownloadTMD(Package* package) NCrypto::TMDParser tmdParser; if (!tmdParser.parse(tmdResult.tmdData.data(), tmdResult.tmdData.size())) { - setPackageError(package, from_wxString(_("Invalid TMD"))); + setPackageError(package, "Invalid TMD"); package->state.isDownloadingTMD = false; return; } @@ -1176,7 +1176,7 @@ void DownloadManager::asyncPackageDownloadContentFile(Package* package, uint16 i size_t bytesWritten = callbackInfo->receiveBuffer.size(); if (callbackInfo->fileOutput->writeData(callbackInfo->receiveBuffer.data(), callbackInfo->receiveBuffer.size()) != (uint32)callbackInfo->receiveBuffer.size()) { - callbackInfo->downloadMgr->setPackageError(callbackInfo->package, from_wxString(_("Cannot write file. Disk full?"))); + callbackInfo->downloadMgr->setPackageError(callbackInfo->package, "Cannot write file. Disk full?"); return false; } callbackInfo->receiveBuffer.clear(); @@ -1197,12 +1197,12 @@ void DownloadManager::asyncPackageDownloadContentFile(Package* package, uint16 i callbackInfoData.fileOutput = FileStream::createFile2(packageDownloadPath / fmt::format("{:08x}.app", contentId)); if (!callbackInfoData.fileOutput) { - setPackageError(package, from_wxString(_("Cannot create file"))); + setPackageError(package, "Cannot create file"); return; } if (!NAPI::CCS_GetContentFile(titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) { - setPackageError(package, from_wxString(_("Download failed"))); + setPackageError(package, "Download failed"); delete callbackInfoData.fileOutput; return; } @@ -1290,7 +1290,11 @@ bool DownloadManager::asyncPackageInstallRecursiveExtractFiles(Package* package, setPackageError(package, "Internal error"); return false; } - + if (fstVolume->HasLinkFlag(dirItr.GetDirHandle())) + { + cemu_assert_suspicious(); + return true; + } FSTFileHandle itr; while (fstVolume->Next(dirItr, itr)) { @@ -1545,7 +1549,7 @@ void DownloadManager::runManager() auto cacheFilePath = ActiveSettings::GetMlcPath("usr/save/system/nim/nup/"); fs::create_directories(cacheFilePath); cacheFilePath /= "cemu_cache.dat"; - s_nupFileCache = FileCache::Open(cacheFilePath.generic_wstring(), true); + s_nupFileCache = FileCache::Open(cacheFilePath, true); // launch worker thread std::thread t(&DownloadManager::threadFunc, this); t.detach(); diff --git a/src/Cemu/napi/napi_act.cpp b/src/Cemu/napi/napi_act.cpp index b395e2b7..9716c41e 100644 --- a/src/Cemu/napi/napi_act.cpp +++ b/src/Cemu/napi/napi_act.cpp @@ -358,7 +358,7 @@ namespace NAPI std::string_view port = tokenNode.child_value("port"); std::string_view token = tokenNode.child_value("token"); - std::memset(&result.nexToken, 0, sizeof(result.nexToken)); + memset(&result.nexToken, 0, sizeof(ACTNexToken)); if (host.size() > 15) cemuLog_log(LogType::Force, "NexToken response: host field too long"); if (nex_password.size() > 64) diff --git a/src/Cemu/nex/nex.cpp b/src/Cemu/nex/nex.cpp index 317b3877..d0857507 100644 --- a/src/Cemu/nex/nex.cpp +++ b/src/Cemu/nex/nex.cpp @@ -160,11 +160,10 @@ bool nexService::isMarkedForDestruction() void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse), void* custom, bool callHandlerIfError) { - // add to queue queuedRequest_t queueRequest = { 0 }; queueRequest.protocolId = protocolId; queueRequest.methodId = methodId; - queueRequest.parameterData = std::vector(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); + queueRequest.parameterData.assign(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); queueRequest.nexServiceResponse = nexServiceResponse; queueRequest.custom = custom; queueRequest.callHandlerIfError = callHandlerIfError; @@ -175,11 +174,10 @@ void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, std::function cb, bool callHandlerIfError) { - // add to queue queuedRequest_t queueRequest = { 0 }; queueRequest.protocolId = protocolId; queueRequest.methodId = methodId; - queueRequest.parameterData = std::vector(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); + queueRequest.parameterData.assign(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); queueRequest.nexServiceResponse = nullptr; queueRequest.cb2 = cb; queueRequest.callHandlerIfError = callHandlerIfError; diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index cf169b72..4fae8143 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -274,8 +274,7 @@ void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response return; } NexFriends* session = (NexFriends*)nexFriends; - - nexPrincipalPreference preference(&response->data); + session->myPreference = nexPrincipalPreference(&response->data); nexComment comment(&response->data); if (response->data.hasReadOutOfBounds()) return; @@ -290,29 +289,21 @@ void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response uint32 friendCount = response->data.readU32(); session->list_friends.resize(friendCount); for (uint32 i = 0; i < friendCount; i++) - { session->list_friends[i].readData(&response->data); - } // friend requests (outgoing) uint32 friendRequestsOutCount = response->data.readU32(); if (response->data.hasReadOutOfBounds()) - { return; - } session->list_friendReqOutgoing.resize(friendRequestsOutCount); for (uint32 i = 0; i < friendRequestsOutCount; i++) - { session->list_friendReqOutgoing[i].readData(&response->data); - } // friend requests (incoming) uint32 friendRequestsInCount = response->data.readU32(); if (response->data.hasReadOutOfBounds()) return; session->list_friendReqIncoming.resize(friendRequestsInCount); for (uint32 i = 0; i < friendRequestsInCount; i++) - { session->list_friendReqIncoming[i].readData(&response->data); - } if (response->data.hasReadOutOfBounds()) return; // blacklist @@ -336,7 +327,7 @@ void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response if (isPreferenceInvalid) { cemuLog_log(LogType::Force, "NEX: First time login into friend account, setting up default preferences"); - session->updatePreferences(nexPrincipalPreference(1, 1, 0)); + session->updatePreferencesAsync(nexPrincipalPreference(1, 1, 0), [](RpcErrorCode err){}); } if (session->firstInformationRequest == false) @@ -377,20 +368,27 @@ bool NexFriends::requestGetAllInformation(std::function cb) return true; } -void NexFriends::handleResponse_updatePreferences(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb) -{ - // todo -} - -bool NexFriends::updatePreferences(const nexPrincipalPreference& newPreferences) +bool NexFriends::updatePreferencesAsync(nexPrincipalPreference newPreferences, std::function cb) { uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); newPreferences.writeData(&packetBuffer); - nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 16, &packetBuffer, std::bind(handleResponse_updatePreferences, std::placeholders::_1, this, nullptr), true); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 16, &packetBuffer, [this, cb, newPreferences](nexServiceResponse_t* response) -> void + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + this->myPreference = newPreferences; + return cb(NexFriends::ERR_NONE); + }, true); + // TEST return true; } +void NexFriends::getMyPreference(nexPrincipalPreference& preference) +{ + preference = myPreference; +} + bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId) { uint8 tempNexBufferArray[512]; @@ -401,6 +399,7 @@ bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId) return true; } +// returns true once connection is established and friend list data is available bool NexFriends::isOnline() { return isCurrentlyConnected && hasData; @@ -683,7 +682,7 @@ bool NexFriends::getFriendRequestByPID(nexFriendRequest& friendRequestData, bool { friendRequestData = it; if (isIncoming) - *isIncoming = false; + *isIncoming = false; return true; } } @@ -731,7 +730,7 @@ void addProvisionalFriendHandler(nexServiceResponse_t* nexResponse, std::functio } } -bool NexFriends::addProvisionalFriend(char* name, std::function cb) +bool NexFriends::addProvisionalFriend(char* name, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { @@ -754,12 +753,11 @@ void addFriendRequestHandler(nexServiceResponse_t* nexResponse, NexFriends* nexF { // todo: Properly handle returned error code cb(NexFriends::ERR_RPC_FAILED); - // refresh the list - nexFriends->requestGetAllInformation(); + nexFriends->requestGetAllInformation(); // refresh friend list and send add/remove notifications } } -void NexFriends::addFriendRequest(uint32 pid, const char* comment, std::function cb) +void NexFriends::addFriendRequest(uint32 pid, const char* comment, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { @@ -779,72 +777,31 @@ void NexFriends::addFriendRequest(uint32 pid, const char* comment, std::function nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 5, &packetBuffer, std::bind(addFriendRequestHandler, std::placeholders::_1, this, cb), true); } -typedef struct -{ - NEXFRIENDS_CALLBACK cb; - void* customParam; - NexFriends* nexFriends; - // command specific - struct - { - nexPrincipalBasicInfo* basicInfo; - sint32 count; - }principalBaseInfo; -}nexFriendsCallInfo_t; - -void NexFriends_handleResponse_requestPrincipleBaseInfoByPID(nexService* nex, nexServiceResponse_t* response) -{ - nexFriendsCallInfo_t* callInfo = (nexFriendsCallInfo_t*)response->custom; - if (response->isSuccessful == false) - { - // handle error case - callInfo->cb(callInfo->nexFriends, NexFriends::ERR_RPC_FAILED, callInfo->customParam); - free(callInfo); - return; - } - // process result - uint32 count = response->data.readU32(); - if (count != callInfo->principalBaseInfo.count) - { - callInfo->cb(callInfo->nexFriends, NexFriends::ERR_UNEXPECTED_RESULT, callInfo->customParam); - free(callInfo); - return; - } - for (uint32 i = 0; i < count; i++) - { - callInfo->principalBaseInfo.basicInfo[i].readData(&response->data); - } - if (response->data.hasReadOutOfBounds()) - { - callInfo->cb(callInfo->nexFriends, NexFriends::ERR_UNEXPECTED_RESULT, callInfo->customParam); - free(callInfo); - return; - } - // callback - callInfo->cb(callInfo->nexFriends, NexFriends::ERR_NONE, callInfo->customParam); - free(callInfo); -} - -void NexFriends::requestPrincipleBaseInfoByPID(nexPrincipalBasicInfo* basicInfo, uint32* pidList, sint32 count, NEXFRIENDS_CALLBACK cb, void* customParam) +void NexFriends::requestPrincipleBaseInfoByPID(uint32* pidList, sint32 count, const std::function basicInfo)>& cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) - { - // not connected - cb(this, ERR_NOT_CONNECTED, customParam); - return; - } + return cb(ERR_NOT_CONNECTED, {}); uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(count); for(sint32 i=0; iprincipalBaseInfo.basicInfo = basicInfo; - callInfo->principalBaseInfo.count = count; - callInfo->cb = cb; - callInfo->customParam = customParam; - callInfo->nexFriends = this; - nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 17, &packetBuffer, NexFriends_handleResponse_requestPrincipleBaseInfoByPID, callInfo, true); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 17, &packetBuffer, [cb, count](nexServiceResponse_t* response) + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED, {}); + // process result + uint32 resultCount = response->data.readU32(); + if (resultCount != count) + return cb(NexFriends::ERR_UNEXPECTED_RESULT, {}); + std::vector nexBasicInfo; + nexBasicInfo.resize(count); + for (uint32 i = 0; i < resultCount; i++) + nexBasicInfo[i].readData(&response->data); + if (response->data.hasReadOutOfBounds()) + return cb(NexFriends::ERR_UNEXPECTED_RESULT, {}); + return cb(NexFriends::ERR_NONE, nexBasicInfo); + }, true); } void genericFriendServiceNoResponseHandler(nexServiceResponse_t* nexResponse, std::function cb) @@ -858,7 +815,7 @@ void genericFriendServiceNoResponseHandler(nexServiceResponse_t* nexResponse, st } } -void NexFriends::removeFriend(uint32 pid, std::function cb) +void NexFriends::removeFriend(uint32 pid, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { @@ -869,10 +826,20 @@ void NexFriends::removeFriend(uint32 pid, std::function cb) uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(pid); - nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, std::bind(genericFriendServiceNoResponseHandler, std::placeholders::_1, cb), true); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, [this, cb](nexServiceResponse_t* response) -> void + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + else + { + cb(NexFriends::ERR_NONE); + this->requestGetAllInformation(); // refresh friend list and send add/remove notifications + return; + } + }, true); } -void NexFriends::cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb) +void NexFriends::cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { @@ -883,53 +850,63 @@ void NexFriends::cancelOutgoingProvisionalFriendRequest(uint32 pid, std::functio uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(pid); - nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, std::bind(genericFriendServiceNoResponseHandler, std::placeholders::_1, cb), true); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, [cb](nexServiceResponse_t* response) -> void + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + else + return cb(NexFriends::ERR_NONE); + }, true); } -void NexFriends::acceptFriendRequest(uint64 messageId, std::function cb) +void NexFriends::acceptFriendRequest(uint64 messageId, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) - { - // not connected - cb(ERR_NOT_CONNECTED); - return; - } + return cb(ERR_NOT_CONNECTED); uint8 tempNexBufferArray[128]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU64(messageId); - nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 7, &packetBuffer, std::bind(genericFriendServiceNoResponseHandler, std::placeholders::_1, cb), true); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 7, &packetBuffer, [cb](nexServiceResponse_t* response) -> void + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + else + return cb(NexFriends::ERR_NONE); + }, true); } -void markFriendRequestsAsReceivedHandler(nexServiceResponse_t* nexResponse, std::function cb) -{ - if (nexResponse->isSuccessful) - cb(0); - else - { - // todo: Properly handle returned error code - cb(NexFriends::ERR_RPC_FAILED); - } -} - -void NexFriends::markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb) +void NexFriends::deleteFriendRequest(uint64 messageId, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) - { - // not connected - cb(ERR_NOT_CONNECTED); - return; - } + return cb(ERR_NOT_CONNECTED); + uint8 tempNexBufferArray[128]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU64(messageId); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 8, &packetBuffer, [this, cb](nexServiceResponse_t* response) -> void + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + cb(NexFriends::ERR_NONE); + this->requestGetAllInformation(); // refresh friend list and send add/remove notifications + }, true); +} + +void NexFriends::markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + return cb(ERR_NOT_CONNECTED); uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(count); for(sint32 i=0; icallMethod(NEX_PROTOCOL_FRIENDS_WIIU, 10, &packetBuffer, std::bind(markFriendRequestsAsReceivedHandler, std::placeholders::_1, cb), true); -} - -void genericFriendServiceNoResponseHandlerWithoutCB(nexServiceResponse_t* nexResponse) -{ - + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 10, &packetBuffer, [cb](nexServiceResponse_t* response) -> void + { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + else + return cb(NexFriends::ERR_NONE); + }, true); } void NexFriends::updateMyPresence(nexPresenceV2& myPresence) @@ -943,7 +920,7 @@ void NexFriends::updateMyPresence(nexPresenceV2& myPresence) uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); myPresence.writeData(&packetBuffer); - nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 13, &packetBuffer, std::bind(genericFriendServiceNoResponseHandlerWithoutCB, std::placeholders::_1), false); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 13, &packetBuffer, +[](nexServiceResponse_t* nexResponse){}, false); } void NexFriends::update() diff --git a/src/Cemu/nex/nexFriends.h b/src/Cemu/nex/nexFriends.h index 16548bf3..06c75110 100644 --- a/src/Cemu/nex/nexFriends.h +++ b/src/Cemu/nex/nexFriends.h @@ -248,9 +248,9 @@ public: nexPrincipalPreference(uint8 ukn0, uint8 ukn1, uint8 ukn2) { - this->ukn0 = ukn0; - this->ukn1 = ukn1; - this->ukn2 = ukn2; + this->showOnline = ukn0; + this->showGame = ukn1; + this->blockFriendRequests = ukn2; } nexPrincipalPreference(nexPacketBuffer* pb) @@ -260,21 +260,21 @@ public: void writeData(nexPacketBuffer* pb) const override { - pb->writeU8(ukn0); - pb->writeU8(ukn1); - pb->writeU8(ukn2); + pb->writeU8(showOnline); + pb->writeU8(showGame); + pb->writeU8(blockFriendRequests); } void readData(nexPacketBuffer* pb) override { - ukn0 = pb->readU8(); - ukn1 = pb->readU8(); - ukn2 = pb->readU8(); + showOnline = pb->readU8(); + showGame = pb->readU8(); + blockFriendRequests = pb->readU8(); } public: - uint8 ukn0; - uint8 ukn1; - uint8 ukn2; + uint8 showOnline; + uint8 showGame; + uint8 blockFriendRequests; }; class nexComment : public nexType @@ -505,13 +505,12 @@ public: uint32 type; std::string msg; }; -class NexFriends; - -typedef void (*NEXFRIENDS_CALLBACK)(NexFriends* nexFriends, uint32 result, void* custom); class NexFriends { public: + using RpcErrorCode = int; // replace with enum class later + static const int ERR_NONE = 0; static const int ERR_RPC_FAILED = 1; static const int ERR_UNEXPECTED_RESULT = 2; @@ -544,27 +543,29 @@ public: int getPendingFriendRequestCount(); bool requestGetAllInformation(); - bool requestGetAllInformation(std::function cb); bool addProvisionalFriendByPidGuessed(uint32 principalId); - void acceptFriendRequest(uint64 messageId, std::function cb); - bool isOnline(); + // synchronous API (returns immediately) + bool requestGetAllInformation(std::function cb); void getFriendPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeFriendRequests); void getFriendRequestPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeIncoming, bool includeOutgoing); bool getFriendByPID(nexFriend& friendData, uint32 pid); bool getFriendRequestByPID(nexFriendRequest& friendRequestData, bool* isIncoming, uint32 searchedPid); bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId); + bool isOnline(); + void getMyPreference(nexPrincipalPreference& preference); - bool addProvisionalFriend(char* name, std::function cb); - void addFriendRequest(uint32 pid, const char* comment, std::function cb); - - void requestPrincipleBaseInfoByPID(nexPrincipalBasicInfo* basicInfo, uint32* pidList, sint32 count, NEXFRIENDS_CALLBACK cb, void* customParam); - void removeFriend(uint32 pid, std::function cb); - void cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb); - void markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb); - + // asynchronous API (data has to be requested) + bool addProvisionalFriend(char* name, std::function cb); + void addFriendRequest(uint32 pid, const char* comment, std::function cb); + void requestPrincipleBaseInfoByPID(uint32* pidList, sint32 count, const std::function basicInfo)>& cb); + void removeFriend(uint32 pid, std::function cb); + void cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb); + void markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb); + void acceptFriendRequest(uint64 messageId, std::function cb); + void deleteFriendRequest(uint64 messageId, std::function cb); // rejecting incoming friend request (differs from blocking friend requests) + bool updatePreferencesAsync(const nexPrincipalPreference newPreferences, std::function cb); void updateMyPresence(nexPresenceV2& myPresence); - bool updatePreferences(const nexPrincipalPreference& newPreferences); void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)); @@ -578,7 +579,6 @@ private: static void handleResponse_acceptFriendRequest(nexService* nex, nexServiceResponse_t* response); static void handleResponse_getAllInformation(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb); - static void handleResponse_updatePreferences(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb); void generateNotification(NOTIFICATION_TYPE notificationType, uint32 pid); void trackNotifications(); @@ -618,6 +618,7 @@ private: }auth; // local friend state nexPresenceV2 myPresence; + nexPrincipalPreference myPreference; std::recursive_mutex mtx_lists; std::vector list_friends; diff --git a/src/Cemu/nex/nexTypes.h b/src/Cemu/nex/nexTypes.h index 49edd3d3..f43a83f2 100644 --- a/src/Cemu/nex/nexTypes.h +++ b/src/Cemu/nex/nexTypes.h @@ -16,7 +16,9 @@ public: class nexType { -public: + public: + virtual ~nexType(){}; + virtual const char* getMetaName() { cemu_assert_unimplemented(); diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index 086e40dd..ad41e662 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(CemuCommon StackAllocator.h SysAllocator.cpp SysAllocator.h + CafeString.h version.h ) diff --git a/src/Common/CafeString.h b/src/Common/CafeString.h new file mode 100644 index 00000000..45a515b1 --- /dev/null +++ b/src/Common/CafeString.h @@ -0,0 +1,73 @@ +#pragma once +#include "betype.h" +#include "util/helpers/StringHelpers.h" + +/* Helper classes to represent CafeOS strings in emulated memory */ +template +class CafeString // fixed buffer size, null-terminated, PPC char +{ + public: + bool assign(std::string_view sv) + { + if (sv.size()+1 >= N) + { + memcpy(data, sv.data(), sv.size()-1); + data[sv.size()-1] = '\0'; + return false; + } + memcpy(data, sv.data(), sv.size()); + data[sv.size()] = '\0'; + return true; + } + + uint8be data[N]; +}; + +template +class CafeWideString // fixed buffer size, null-terminated, PPC wchar_t (16bit big-endian) +{ + public: + bool assign(const uint16be* input) + { + size_t i = 0; + while(input[i]) + { + if(i >= N-1) + { + data[N-1] = 0; + return false; + } + data[i] = input[i]; + i++; + } + data[i] = 0; + return true; + } + + bool assignFromUTF8(std::string_view sv) + { + std::basic_string beStr = StringHelpers::FromUtf8(sv); + if(beStr.length() > N-1) + { + memcpy(data, beStr.data(), (N-1)*sizeof(uint16be)); + data[N-1] = 0; + return false; + } + memcpy(data, beStr.data(), beStr.length()*sizeof(uint16be)); + data[beStr.length()] = '\0'; + return true; + } + + uint16be data[N]; +}; + +namespace CafeStringHelpers +{ + static uint32 Length(const uint16be* input, uint32 maxLength) + { + uint32 i = 0; + while(input[i] && i < maxLength) + i++; + return i; + } +}; diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp index 5308ea8e..5fefc8ca 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -73,16 +73,17 @@ void ExceptionHandler_LogGeneralInfo() // info about active PPC instance: CrashLog_WriteLine(""); CrashLog_WriteHeader("Active PPC instance"); - if (ppcInterpreterCurrentInstance) + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + if (hCPU) { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); uint32 threadPtr = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); - sprintf(dumpLine, "IP 0x%08x LR 0x%08x Thread 0x%08x", ppcInterpreterCurrentInstance->instructionPointer, ppcInterpreterCurrentInstance->spr.LR, threadPtr); + sprintf(dumpLine, "IP 0x%08x LR 0x%08x Thread 0x%08x", hCPU->instructionPointer, hCPU->spr.LR, threadPtr); CrashLog_WriteLine(dumpLine); // GPR info CrashLog_WriteLine(""); - auto gprs = ppcInterpreterCurrentInstance->gpr; + auto gprs = hCPU->gpr; sprintf(dumpLine, "r0 =%08x r1 =%08x r2 =%08x r3 =%08x r4 =%08x r5 =%08x r6 =%08x r7 =%08x", gprs[0], gprs[1], gprs[2], gprs[3], gprs[4], gprs[5], gprs[6], gprs[7]); CrashLog_WriteLine(dumpLine); sprintf(dumpLine, "r8 =%08x r9 =%08x r10=%08x r11=%08x r12=%08x r13=%08x r14=%08x r15=%08x", gprs[8], gprs[9], gprs[10], gprs[11], gprs[12], gprs[13], gprs[14], gprs[15]); @@ -93,7 +94,7 @@ void ExceptionHandler_LogGeneralInfo() CrashLog_WriteLine(dumpLine); // stack trace - MPTR currentStackVAddr = ppcInterpreterCurrentInstance->gpr[1]; + MPTR currentStackVAddr = hCPU->gpr[1]; CrashLog_WriteLine(""); CrashLog_WriteHeader("PPC stack trace"); DebugLogStackTrace(currentThread, currentStackVAddr); diff --git a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp index 9e418307..9cfce88d 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp @@ -6,6 +6,9 @@ #include "util/helpers/StringHelpers.h" #include "ExceptionHandler.h" +#include "Cafe/HW/Espresso/Debugger/GDBStub.h" +#include "Cafe/HW/Espresso/Debugger/GDBBreakpoints.h" + #if BOOST_OS_LINUX #include "ELFSymbolTable.h" #endif @@ -65,6 +68,16 @@ void DemangleAndPrintBacktrace(char** backtrace, size_t size) // handle signals that would dump core, print stacktrace and then dump depending on config void handlerDumpingSignal(int sig, siginfo_t *info, void *context) { +#if defined(ARCH_X86_64) && BOOST_OS_LINUX + // Check for hardware breakpoints + if (info->si_signo == SIGTRAP && info->si_code == TRAP_HWBKPT) + { + uint64 dr6 = _ReadDR6(); + g_gdbstub->HandleAccessException(dr6); + return; + } +#endif + if(!CrashLog_Create()) return; // give up if crashlog was already created diff --git a/src/Common/GLInclude/GLInclude.h b/src/Common/GLInclude/GLInclude.h index b68a7a7e..a41183f1 100644 --- a/src/Common/GLInclude/GLInclude.h +++ b/src/Common/GLInclude/GLInclude.h @@ -46,6 +46,177 @@ typedef struct __GLXFBConfigRec *GLXFBConfig; #undef GLFUNC #undef EGLFUNC +// DSA-style helpers with fallback to legacy API if DSA is not supported + +#define DSA_FORCE_DISABLE false // set to true to simulate DSA not being supported + +static GLenum GetGLBindingFromTextureTarget(GLenum texTarget) +{ + switch(texTarget) + { + case GL_TEXTURE_1D: return GL_TEXTURE_BINDING_1D; + case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D; + case GL_TEXTURE_3D: return GL_TEXTURE_BINDING_3D; + case GL_TEXTURE_2D_ARRAY: return GL_TEXTURE_BINDING_2D_ARRAY; + case GL_TEXTURE_CUBE_MAP: return GL_TEXTURE_BINDING_CUBE_MAP; + case GL_TEXTURE_CUBE_MAP_ARRAY: return GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; + default: + cemu_assert_unimplemented(); + return 0; + } +} + +static GLuint glCreateTextureWrapper(GLenum target) +{ + GLuint tex; + if (glCreateTextures && !DSA_FORCE_DISABLE) + { + glCreateTextures(target, 1, &tex); + return tex; + } + GLint originalTexture; + glGetIntegerv(GetGLBindingFromTextureTarget(target), &originalTexture); + glGenTextures(1, &tex); + glBindTexture(target, tex); + glBindTexture(target, originalTexture); + return tex; +} + +static void glTextureStorage1DWrapper(GLenum target, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) +{ + if (glTextureStorage1D && !DSA_FORCE_DISABLE) + { + glTextureStorage1D(texture, levels, internalformat, width); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glTexStorage1D(target, levels, internalformat, width); + glBindTexture(target, originalTexture); +} + +static void glTextureStorage2DWrapper(GLenum target, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) +{ + if (glTextureStorage2D && !DSA_FORCE_DISABLE) + { + glTextureStorage2D(texture, levels, internalformat, width, height); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glTexStorage2D(target, levels, internalformat, width, height); + glBindTexture(target, originalTexture); +} + +static void glTextureStorage3DWrapper(GLenum target, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) +{ + if (glTextureStorage3D && !DSA_FORCE_DISABLE) + { + glTextureStorage3D(texture, levels, internalformat, width, height, depth); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glTexStorage3D(target, levels, internalformat, width, height, depth); + glBindTexture(target, originalTexture); +} + +static void glTextureSubImage1DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) +{ + if (glTextureSubImage1D && !DSA_FORCE_DISABLE) + { + glTextureSubImage1D(texture, level, xoffset, width, format, type, pixels); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glTexSubImage1D(target, level, xoffset, width, format, type, pixels); + glBindTexture(target, originalTexture); +} + +static void glCompressedTextureSubImage1DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) +{ + if (glCompressedTextureSubImage1D && !DSA_FORCE_DISABLE) + { + glCompressedTextureSubImage1D(texture, level, xoffset, width, format, imageSize, data); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glCompressedTexSubImage1D(target, level, xoffset, width, format, imageSize, data); + glBindTexture(target, originalTexture); +} + +static void glTextureSubImage2DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) +{ + if (glTextureSubImage2D && !DSA_FORCE_DISABLE) + { + glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); + glBindTexture(target, originalTexture); +} + +static void glCompressedTextureSubImage2DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) +{ + if (glCompressedTextureSubImage2D && !DSA_FORCE_DISABLE) + { + glCompressedTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, imageSize, data); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data); + glBindTexture(target, originalTexture); +} + +static void glTextureSubImage3DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) +{ + if(glTextureSubImage3D && !DSA_FORCE_DISABLE) + { + glTextureSubImage3D(texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); + glBindTexture(target, originalTexture); +} + +static void glCompressedTextureSubImage3DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) +{ + if(glCompressedTextureSubImage3D && !DSA_FORCE_DISABLE) + { + glCompressedTextureSubImage3D(texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); + return; + } + GLenum binding = GetGLBindingFromTextureTarget(target); + GLint originalTexture; + glGetIntegerv(binding, &originalTexture); + glBindTexture(target, texture); + glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); + glBindTexture(target, originalTexture); +} + // this prevents Windows GL.h from being included: #define __gl_h_ #define __GL_H__ diff --git a/src/Common/GLInclude/glFunctions.h b/src/Common/GLInclude/glFunctions.h index 4cf25a6b..76308fdb 100644 --- a/src/Common/GLInclude/glFunctions.h +++ b/src/Common/GLInclude/glFunctions.h @@ -171,10 +171,13 @@ GLFUNC(PFNGLTEXSTORAGE2DPROC, glTexStorage2D) GLFUNC(PFNGLTEXSTORAGE3DPROC, glTexStorage3D) GLFUNC(PFNGLTEXIMAGE3DPROC, glTexImage3D) GLFUNC(PFNGLTEXSUBIMAGE3DPROC, glTexSubImage3D) +GLFUNC(PFNGLCOMPRESSEDTEXIMAGE1DPROC, glCompressedTexImage1D) GLFUNC(PFNGLCOMPRESSEDTEXIMAGE2DPROC, glCompressedTexImage2D) GLFUNC(PFNGLCOMPRESSEDTEXIMAGE3DPROC, glCompressedTexImage3D) +GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC, glCompressedTexSubImage1D) GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC, glCompressedTexSubImage2D) GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC, glCompressedTexSubImage3D) +GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC, glCompressedTextureSubImage1D) GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC, glCompressedTextureSubImage2D) GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC, glCompressedTextureSubImage3D) GLFUNC(PFNGLCOPYIMAGESUBDATAPROC, glCopyImageSubData) @@ -184,12 +187,17 @@ GLFUNC(PFNGLINVALIDATETEXIMAGEPROC, glInvalidateTexImage) // texture DSA +GLFUNC(PFNGLCREATETEXTURESPROC, glCreateTextures) GLFUNC(PFNGLBINDTEXTUREUNITPROC, glBindTextureUnit) GLFUNC(PFNGLGETTEXTURELEVELPARAMETERIVPROC, glGetTextureLevelParameteriv) GLFUNC(PFNGLTEXTUREPARAMETERIPROC, glTextureParameteri) GLFUNC(PFNGLGETTEXTURESUBIMAGEPROC, glGetTextureSubImage) +GLFUNC(PFNGLTEXTURESUBIMAGE1DPROC, glTextureSubImage1D) GLFUNC(PFNGLTEXTURESUBIMAGE2DPROC, glTextureSubImage2D); GLFUNC(PFNGLTEXTURESUBIMAGE3DPROC, glTextureSubImage3D) +GLFUNC(PFNGLTEXTURESTORAGE1DPROC, glTextureStorage1D) +GLFUNC(PFNGLTEXTURESTORAGE2DPROC, glTextureStorage2D) +GLFUNC(PFNGLTEXTURESTORAGE3DPROC, glTextureStorage3D) // instancing / draw diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index dc1ecd36..de787cc1 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -159,5 +159,5 @@ template struct fmt::formatter> : formatter { template - auto format(const MEMPTR& v, FormatContext& ctx) { return formatter::format(fmt::format("{:#x}", v.GetMPTR()), ctx); } + auto format(const MEMPTR& v, FormatContext& ctx) const -> format_context::iterator { return fmt::format_to(ctx.out(), "{:#x}", v.GetMPTR()); } }; diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h index 750db13f..a69b7aaa 100644 --- a/src/Common/StackAllocator.h +++ b/src/Common/StackAllocator.h @@ -29,9 +29,36 @@ public: T* GetPointer() const { return m_ptr; } uint32 GetMPTR() const { return MEMPTR(m_ptr).GetMPTR(); } uint32 GetMPTRBE() const { return MEMPTR(m_ptr).GetMPTRBE(); } - - operator T*() const { return GetPointer(); } + + T* operator&() { return GetPointer(); } + explicit operator T*() const { return GetPointer(); } explicit operator uint32() const { return GetMPTR(); } + explicit operator bool() const { return *m_ptr != 0; } + + // for arrays (count > 1) allow direct access via [] operator + template + requires (c > 1) + T& operator[](const uint32 index) + { + return m_ptr[index]; + } + + // if count is 1, then allow direct value assignment via = operator + template + requires (c == 1) + T& operator=(const T& rhs) + { + *m_ptr = rhs; + return *m_ptr; + } + + // if count is 1, then allow == and != operators + template + requires (c == 1) + bool operator==(const T& rhs) const + { + return *m_ptr == rhs; + } private: static const uint32 kStaticMemOffset = 64; diff --git a/src/Common/SysAllocator.h b/src/Common/SysAllocator.h index 7930a16b..7a11601e 100644 --- a/src/Common/SysAllocator.h +++ b/src/Common/SysAllocator.h @@ -1,10 +1,10 @@ #pragma once -#include - uint32 coreinit_allocFromSysArea(uint32 size, uint32 alignment); class SysAllocatorBase; +#define SYSALLOCATOR_GUARDS 0 // if 1, create a magic constant at the top of each memory allocation which is used to check for memory corruption + class SysAllocatorContainer { public: @@ -29,9 +29,7 @@ private: virtual void Initialize() = 0; }; - - -template +template class SysAllocator : public SysAllocatorBase { public: @@ -68,11 +66,17 @@ public: T* GetPtr() const { +#if SYSALLOCATOR_GUARDS + cemu_assert(*(uint32*)((uint8*)m_sysMem.GetPtr()+(sizeof(T) * count)) == 0x112A33C4); +#endif return m_sysMem.GetPtr(); } uint32 GetMPTR() const { +#if SYSALLOCATOR_GUARDS + cemu_assert(*(uint32*)((uint8*)m_sysMem.GetPtr()+(sizeof(T) * count)) == 0x112A33C4); +#endif return m_sysMem.GetMPTR(); } @@ -130,11 +134,17 @@ private: { if (m_sysMem.GetMPTR() != 0) return; - // alloc mem - m_sysMem = { coreinit_allocFromSysArea(sizeof(T) * count, alignment) }; + uint32 guardSize = 0; +#if SYSALLOCATOR_GUARDS + guardSize = 4; +#endif + m_sysMem = { coreinit_allocFromSysArea(sizeof(T) * count + guardSize, alignment) }; // copy temp buffer to mem and clear it memcpy(m_sysMem.GetPtr(), m_tempData.data(), sizeof(T)*count); +#if SYSALLOCATOR_GUARDS + *(uint32*)((uint8*)m_sysMem.GetPtr()+(sizeof(T) * count)) = 0x112A33C4; +#endif m_tempData.clear(); } @@ -197,9 +207,8 @@ private: { if (m_sysMem.GetMPTR() != 0) return; - // alloc mem - m_sysMem = { coreinit_allocFromSysArea(sizeof(T), 8) }; + m_sysMem = { coreinit_allocFromSysArea(sizeof(T), 32) }; // copy temp buffer to mem and clear it *m_sysMem = m_tempData; } diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 2dc133c9..63e5c4ce 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -292,10 +292,18 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor, #if defined(_MSC_VER) #define UNREACHABLE __assume(false) -#elif defined(__GNUC__) + #define ASSUME(__cond) __assume(__cond) + #define TLS_WORKAROUND_NOINLINE // no-op for MSVC as it has a flag for fiber-safe TLS optimizations +#elif defined(__GNUC__) && !defined(__llvm__) #define UNREACHABLE __builtin_unreachable() + #define ASSUME(__cond) __attribute__((assume(__cond))) + #define TLS_WORKAROUND_NOINLINE __attribute__((noinline)) +#elif defined(__clang__) + #define UNREACHABLE __builtin_unreachable() + #define ASSUME(__cond) __builtin_assume(__cond) + #define TLS_WORKAROUND_NOINLINE __attribute__((noinline)) #else - #define UNREACHABLE + #error Unknown compiler #endif #if defined(_MSC_VER) @@ -530,6 +538,14 @@ inline fs::path _utf8ToPath(std::string_view input) #endif // __ANDROID__ } +// locale-independent variant of tolower() which also matches Wii U behavior +inline char _ansiToLower(char c) +{ + if (c >= 'A' && c <= 'Z') + c -= ('A' - 'a'); + return c; +} + class RunAtCemuBoot // -> replaces this with direct function calls. Linkers other than MSVC may optimize way object files entirely if they are not referenced from outside. So a source file self-registering using this would be causing issues { public: @@ -550,6 +566,14 @@ bool future_is_ready(std::future& f) #endif } +// replace with std::scope_exit once available +struct scope_exit +{ + std::function f_; + explicit scope_exit(std::function f) noexcept : f_(std::move(f)) {} + ~scope_exit() { if (f_) f_(); } +}; + // helper function to cast raw pointers to std::atomic // this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members @@ -593,6 +617,29 @@ inline uint32 GetTitleIdLow(uint64 titleId) #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/PPCCallback.h" +// generic formatter for enums (to underlying) +template + requires std::is_enum_v +struct fmt::formatter : fmt::formatter> +{ + auto format(const Enum& e, format_context& ctx) const + { + //return fmt::format_to(ctx.out(), "{}", fmt::underlying(e)); + + return formatter>::format(fmt::underlying(e), ctx); + } +}; + +// formatter for betype +template +struct fmt::formatter> : fmt::formatter +{ + auto format(const betype& e, format_context& ctx) const + { + return formatter::format(static_cast(e), ctx); + } +}; + // useful C++23 stuff that isn't yet widely supported // std::to_underlying @@ -602,5 +649,4 @@ namespace stdx constexpr std::underlying_type_t to_underlying(EnumT e) noexcept { return static_cast>(e); }; -} - +} \ No newline at end of file diff --git a/src/android/app/build.gradle b/src/android/app/build.gradle index 6360ce1f..a3f90daa 100644 --- a/src/android/app/build.gradle +++ b/src/android/app/build.gradle @@ -5,7 +5,7 @@ plugins { android { namespace 'info.cemu.Cemu' compileSdk 34 - ndkVersion '25.2.9519653' + ndkVersion '26.1.10909125' defaultConfig { applicationId "info.cemu.Cemu" minSdk 30 @@ -18,12 +18,24 @@ android { } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + signingConfigs { + release { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + } + } buildTypes { release { + signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + debug { + applicationIdSuffix ".debug" + } } compileOptions { sourceCompatibility JavaVersion.VERSION_17 @@ -47,7 +59,9 @@ android { '-DENABLE_OPENGL=OFF', '-DBUNDLE_SPEEX=ON', '-DENABLE_DISCORD_RPC=OFF', - '-DENABLE_WAYLAND=OFF' + '-DENABLE_NSYSHID_LIBUSB=OFF', + '-DENABLE_WAYLAND=OFF', + '-DENABLE_HIDAPI=OFF', ) // abiFilters("x86_64", "arm64-v8a") abiFilters("arm64-v8a") diff --git a/src/android/app/src/debug/res/values/strings.xml b/src/android/app/src/debug/res/values/strings.xml new file mode 100644 index 00000000..5aa522b3 --- /dev/null +++ b/src/android/app/src/debug/res/values/strings.xml @@ -0,0 +1,3 @@ + + Cemu debug + \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java index 77e43e23..8c6c6629 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java @@ -23,10 +23,10 @@ public class InputSettingsFragment extends Fragment { GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter(); for (int index = 0; index < NativeLibrary.MAX_CONTROLLERS; index++) { int controllerIndex = index; - String controllerType = getString(NativeLibrary.controllerTypeToResourceNameId(NativeLibrary.getControllerType(controllerIndex))); + int controllerType = NativeLibrary.isControllerDisabled(controllerIndex) ? NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED : NativeLibrary.getControllerType(controllerIndex); ButtonRecyclerViewItem buttonRecyclerViewItem = new ButtonRecyclerViewItem( getString(R.string.controller_numbered, controllerIndex + 1), - getString(R.string.emulated_controller_with_type, controllerType), + getString(R.string.emulated_controller_with_type, getString(NativeLibrary.controllerTypeToResourceNameId(controllerType))), () -> { Bundle bundle = new Bundle(); bundle.putInt(ControllerInputsFragment.CONTROLLER_INDEX, controllerIndex); diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 382b6d48..a9da11da 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -40,7 +40,7 @@ ActiveSettings::LoadOnce( g_config.SetFilename(GetConfigPath("settings.xml").generic_wstring()); g_config.Load(); LaunchSettings::ChangeNetworkServiceURL(GetConfig().account.active_service); - std::wstring additionalErrorInfo; + 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/ActiveSettings.h b/src/config/ActiveSettings.h index 33a673ac..018e6d28 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -73,12 +73,12 @@ private: inline static fs::path s_executable_filename; // cemu.exe inline static fs::path s_mlc_path; -public: +public: // general [[nodiscard]] static bool LoadSharedLibrariesEnabled(); [[nodiscard]] static bool DisplayDRCEnabled(); [[nodiscard]] static bool FullscreenEnabled(); - + // cpu [[nodiscard]] static CPUMode GetCPUMode(); [[nodiscard]] static uint8 GetTimerShiftFactor(); diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 96ffc054..2ecc22b7 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -41,6 +41,7 @@ void CemuConfig::Load(XMLConfigParser& parser) // general settings log_flag = parser.get("logflag", log_flag.GetInitValue()); + cemuLog_setActiveLoggingFlags(GetConfig().log_flag.GetValue()); advanced_ppc_logging = parser.get("advanced_ppc_logging", advanced_ppc_logging.GetInitValue()); const char* mlc = parser.get("mlc_path", ""); @@ -51,7 +52,7 @@ void CemuConfig::Load(XMLConfigParser& parser) language = parser.get("language", 0); use_discord_presence = parser.get("use_discord_presence", true); fullscreen_menubar = parser.get("fullscreen_menubar", false); - feral_gamemode = parser.get("feral_gamemode", false); + feral_gamemode = parser.get("feral_gamemode", false); check_update = parser.get("check_update", check_update); save_screenshot = parser.get("save_screenshot", save_screenshot); did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); @@ -60,9 +61,6 @@ void CemuConfig::Load(XMLConfigParser& parser) fullscreen = parser.get("fullscreen", fullscreen); proxy_server = parser.get("proxy_server", ""); disable_screensaver = parser.get("disable_screensaver", disable_screensaver); - - // cpu_mode = parser.get("cpu_mode", cpu_mode.GetInitValue()); - //console_region = parser.get("console_region", console_region.GetInitValue()); console_language = parser.get("console_language", console_language.GetInitValue()); window_position.x = parser.get("window_position").get("x", -1); @@ -110,7 +108,7 @@ void CemuConfig::Load(XMLConfigParser& parser) try { - recent_launch_files.emplace_back(boost::nowide::widen(path)); + recent_launch_files.emplace_back(path); } catch (const std::exception&) { @@ -125,10 +123,9 @@ void CemuConfig::Load(XMLConfigParser& parser) const std::string path = element.value(""); if (path.empty()) continue; - try { - recent_nfc_files.emplace_back(boost::nowide::widen(path)); + recent_nfc_files.emplace_back(path); } catch (const std::exception&) { @@ -143,10 +140,9 @@ void CemuConfig::Load(XMLConfigParser& parser) const std::string path = element.value(""); if (path.empty()) continue; - try { - game_paths.emplace_back(boost::nowide::widen(path)); + game_paths.emplace_back(path); } catch (const std::exception&) { @@ -402,20 +398,20 @@ void CemuConfig::Save(XMLConfigParser& parser) auto launch_files_parser = config.set("RecentLaunchFiles"); for (const auto& entry : recent_launch_files) { - launch_files_parser.set("Entry", boost::nowide::narrow(entry).c_str()); + launch_files_parser.set("Entry", entry.c_str()); } auto nfc_files_parser = config.set("RecentNFCFiles"); for (const auto& entry : recent_nfc_files) { - nfc_files_parser.set("Entry", boost::nowide::narrow(entry).c_str()); + nfc_files_parser.set("Entry", entry.c_str()); } // game paths auto game_path_parser = config.set("GamePaths"); for (const auto& entry : game_paths) { - game_path_parser.set("Entry", boost::nowide::narrow(entry).c_str()); + game_path_parser.set("Entry", entry.c_str()); } // game list cache @@ -593,22 +589,18 @@ void CemuConfig::SetGameListCustomName(uint64 titleId, std::string customName) gameEntry->custom_name = std::move(customName); } -void CemuConfig::AddRecentlyLaunchedFile(std::wstring_view file) +void CemuConfig::AddRecentlyLaunchedFile(std::string_view file) { - // insert into front - recent_launch_files.insert(recent_launch_files.begin(), std::wstring{ file }); + recent_launch_files.insert(recent_launch_files.begin(), std::string(file)); RemoveDuplicatesKeepOrder(recent_launch_files); - // keep maximum of entries while(recent_launch_files.size() > kMaxRecentEntries) recent_launch_files.pop_back(); } -void CemuConfig::AddRecentNfcFile(std::wstring_view file) +void CemuConfig::AddRecentNfcFile(std::string_view file) { - // insert into front - recent_nfc_files.insert(recent_nfc_files.begin(), std::wstring{ file }); + recent_nfc_files.insert(recent_nfc_files.begin(), std::string(file)); RemoveDuplicatesKeepOrder(recent_nfc_files); - // keep maximum of entries while (recent_nfc_files.size() > kMaxRecentEntries) recent_nfc_files.pop_back(); } diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index ef223cd6..6ab86330 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -350,7 +350,6 @@ struct CemuConfig }; CemuConfig(const CemuConfig&) = delete; - // // sets mlc path, updates permanent config value, saves config void SetMLCPath(fs::path path, bool save = true); @@ -362,10 +361,10 @@ struct CemuConfig ConfigValue language{ 0 }; ConfigValue use_discord_presence{ true }; - ConfigValue mlc_path {}; + ConfigValue mlc_path{}; ConfigValue fullscreen_menubar{ false }; ConfigValue fullscreen{ false }; - ConfigValue feral_gamemode{false}; + ConfigValue feral_gamemode{false}; ConfigValue proxy_server{}; // temporary workaround because feature crashes on macOS @@ -377,7 +376,7 @@ struct CemuConfig ConfigValue disable_screensaver{DISABLE_SCREENSAVER_DEFAULT}; #undef DISABLE_SCREENSAVER_DEFAULT - std::vector game_paths; + std::vector game_paths; std::mutex game_cache_entries_mutex; std::vector game_cache_entries; @@ -397,8 +396,8 @@ struct CemuConfig // max 15 entries static constexpr size_t kMaxRecentEntries = 15; - std::vector recent_launch_files; - std::vector recent_nfc_files; + std::vector recent_launch_files; + std::vector recent_nfc_files; Vector2i window_position{-1,-1}; Vector2i window_size{ -1,-1 }; @@ -497,8 +496,8 @@ struct CemuConfig void Load(XMLConfigParser& parser); void Save(XMLConfigParser& parser); - void AddRecentlyLaunchedFile(std::wstring_view file); - void AddRecentNfcFile(std::wstring_view file); + void AddRecentlyLaunchedFile(std::string_view file); + void AddRecentNfcFile(std::string_view file); bool IsGameListFavorite(uint64 titleId); void SetGameListFavorite(uint64 titleId, bool isFavorite); diff --git a/src/config/ConfigValue.h b/src/config/ConfigValue.h index 11fc1e48..43e2ad3b 100644 --- a/src/config/ConfigValue.h +++ b/src/config/ConfigValue.h @@ -39,7 +39,7 @@ public: return *this; } - [[nodiscard]] TType GetValue() const { return m_value.load(); } + [[nodiscard]] inline TType GetValue() const { return m_value.load(); } void SetValue(const TType& v) { m_value = v; } [[nodiscard]] const TType& GetInitValue() const { return m_init_value; } @@ -232,19 +232,3 @@ private: const TType m_min_value; const TType m_max_value; }; - -template -struct fmt::formatter< ConfigValue > : formatter { - template - auto format(const ConfigValue& v, FormatContext& ctx) { - return formatter::format(v.GetValue(), ctx); - } -}; - -template -struct fmt::formatter< ConfigValueBounds > : formatter { - template - auto format(const ConfigValueBounds& v, FormatContext& ctx) { - return formatter::format(v.GetValue(), ctx); - } -}; \ No newline at end of file diff --git a/src/config/XMLConfig.h b/src/config/XMLConfig.h index 788dc9a7..2a32dc56 100644 --- a/src/config/XMLConfig.h +++ b/src/config/XMLConfig.h @@ -235,6 +235,12 @@ public: set(name, value.load()); } + template + void set(const char* name, const ConfigValue& value) + { + set(name, value.GetValue()); + } + void set(const char* name, uint64 value) { set(name, (sint64)value); @@ -462,4 +468,3 @@ public: private: T m_data; }; - diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4fe2d644..b207416d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -97,6 +97,8 @@ add_library(CemuGui MemorySearcherTool.h PadViewFrame.cpp PadViewFrame.h + PairingDialog.cpp + PairingDialog.h TitleManager.cpp TitleManager.h windows/PPCThreadsViewer @@ -149,7 +151,7 @@ target_link_libraries(CemuGui PRIVATE ZArchive::zarchive ) -if(UNIX AND NOT APPLE) +if(ENABLE_WXWIDGETS AND UNIX AND NOT APPLE) target_link_libraries(CemuGui PRIVATE GTK3::gtk) if (ENABLE_WAYLAND) target_link_libraries(CemuGui PRIVATE Wayland::Client CemuWaylandProtocols) @@ -165,6 +167,11 @@ if(UNIX AND NOT APPLE) target_link_libraries(CemuGui PRIVATE gamemode) endif() endif() +if (ENABLE_WXWIDGETS) + # PUBLIC because wx/app.h is included in CemuApp.h + target_link_libraries(CemuGui PUBLIC wx::base wx::core wx::gl wx::propgrid wx::xrc) +endif() -# PUBLIC because wx/app.h is included in CemuApp.h -target_link_libraries(CemuGui PUBLIC wx::base wx::core wx::gl wx::propgrid wx::xrc) +if(WIN32) + target_link_libraries(CemuGui PRIVATE bthprops) +endif() diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index f130ac67..8f76dd70 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -34,21 +34,6 @@ void unused_translation_dummy() void(_("Browse")); void(_("Select a file")); void(_("Select a directory")); - - void(_("base")); - void(_("update")); - void(_("dlc")); - void(_("save")); - - void(_("Japan")); - void(_("USA")); - void(_("Europe")); - void(_("Australia")); - void(_("China")); - void(_("Korea")); - void(_("Taiwan")); - void(_("Auto")); - void(_("many")); void(_("Japanese")); void(_("English")); @@ -63,13 +48,6 @@ void unused_translation_dummy() void(_("Russian")); void(_("Taiwanese")); void(_("unknown")); - - - // account.h - void(_("AccountId missing (The account is not connected to a NNID)")); - void(_("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)")); - void(_("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)")); - void(_("PrincipalId missing")); } bool CemuApp::OnInit() @@ -77,7 +55,7 @@ bool CemuApp::OnInit() GuiSystem::registerKeyCodeToStringCallback(rawKeyCodeToString); fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); - fs::path exePath(standardPaths.GetExecutablePath().ToStdString()); + fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); #ifdef PORTABLE #if MACOS_BUNDLE exePath = exePath.parent_path().parent_path().parent_path(); @@ -107,7 +85,8 @@ bool CemuApp::OnInit() #endif auto failed_write_access = ActiveSettings::LoadOnce(exePath, user_data_path, config_path, cache_path, data_path); for (auto&& path : failed_write_access) - wxMessageBox(fmt::format("Cemu can't write to {} !", path.generic_string()), _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); + wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), + _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); NetworkConfig::LoadOnce(); g_config.Load(); @@ -117,29 +96,7 @@ bool CemuApp::OnInit() wxInitAllImageHandlers(); - - m_languages = GetAvailableLanguages(); - - const sint32 language = GetConfig().language; - const auto it = std::find_if(m_languages.begin(), m_languages.end(), [language](const wxLanguageInfo* info) { return info->Language == language; }); - if (it != m_languages.end() && wxLocale::IsAvailable(language)) - { - if (m_locale.Init(language)) - { - m_locale.AddCatalogLookupPathPrefix(ActiveSettings::GetDataPath("resources").generic_string()); - m_locale.AddCatalog("cemu"); - } - } - - if (!m_locale.IsOk()) - { - if (!wxLocale::IsAvailable(wxLANGUAGE_DEFAULT) || !m_locale.Init(wxLANGUAGE_DEFAULT)) - { - m_locale.Init(wxLANGUAGE_ENGLISH); - m_locale.AddCatalogLookupPathPrefix(ActiveSettings::GetDataPath("resources").generic_string()); - m_locale.AddCatalog("cemu"); - } - } + LocalizeUI(); // fill colour db wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0)); @@ -186,7 +143,7 @@ bool CemuApp::OnInit() "Thank you for testing the in-development build of Cemu for macOS.\n \n" "The macOS port is currently purely experimental and should not be considered stable or ready for issue-free gameplay. " "There are also known issues with degraded performance due to the use of MoltenVk and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS."); - wxMessageDialog dialog(nullptr, message, "Preview version", wxCENTRE | wxOK | wxICON_WARNING); + wxMessageDialog dialog(nullptr, message, _("Preview version"), wxCENTRE | wxOK | wxICON_WARNING); dialog.SetOKLabel(_("I understand")); dialog.ShowModal(); GetConfig().did_show_macos_disclaimer = true; @@ -215,9 +172,9 @@ void CemuApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, { cemuLog_createLogFile(false); cemuLog_log(LogType::Force, "Encountered wxWidgets assert!"); - cemuLog_log(LogType::Force, fmt::format(L"File: {0} Line: {1}", std::wstring_view(file), line)); - cemuLog_log(LogType::Force, fmt::format(L"Func: {0} Cond: {1}", func, std::wstring_view(cond))); - cemuLog_log(LogType::Force, fmt::format(L"Message: {}", std::wstring_view(msg))); + cemuLog_log(LogType::Force, "File: {0} Line: {1}", wxString(file).utf8_string(), line); + cemuLog_log(LogType::Force, "Func: {0} Cond: {1}", wxString(func).utf8_string(), wxString(cond).utf8_string()); + cemuLog_log(LogType::Force, "Message: {}", wxString(msg).utf8_string()); #if BOOST_OS_WINDOWS DumpThreadStackTrace(); @@ -231,7 +188,6 @@ int CemuApp::FilterEvent(wxEvent& event) if(event.GetEventType() == wxEVT_KEY_DOWN) { const auto& key_event = (wxKeyEvent&)event; - wxGetKeyState(wxKeyCode::WXK_F17); windowInfo.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), true); auto platformKeyCode = rawKeyCodeToPlatformKeyCode(key_event.GetRawKeyCode()); if (platformKeyCode.has_value()) @@ -255,33 +211,44 @@ int CemuApp::FilterEvent(wxEvent& event) return wxApp::FilterEvent(event); } -std::vector CemuApp::GetAvailableLanguages() +std::vector CemuApp::GetLanguages() const { + std::vector availableLanguages(m_availableTranslations); + availableLanguages.insert(availableLanguages.begin(), wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); + return availableLanguages; +} + +void CemuApp::LocalizeUI() { - const auto path = ActiveSettings::GetDataPath("resources"); - if (!exists(path)) - return {}; - - std::vector result; - for (const auto& p : fs::directory_iterator(path)) + std::unique_ptr translationsMgr(new wxTranslations()); + m_availableTranslations = GetAvailableTranslationLanguages(translationsMgr.get()); + + const sint32 configuredLanguage = GetConfig().language; + bool isTranslationAvailable = std::any_of(m_availableTranslations.begin(), m_availableTranslations.end(), + [configuredLanguage](const wxLanguageInfo* info) { return info->Language == configuredLanguage; }); + if (configuredLanguage == wxLANGUAGE_DEFAULT || isTranslationAvailable) { - if (!fs::is_directory(p)) - continue; + translationsMgr->SetLanguage(static_cast(configuredLanguage)); + translationsMgr->AddCatalog("cemu"); - const auto& path = p.path(); - auto filename = path.filename(); + if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(configuredLanguage)) + m_locale.Init(configuredLanguage); - const auto* lang_info = wxLocale::FindLanguageInfo(filename.c_str()); - if (!lang_info) - continue; - - const auto language_file = path / "cemu.mo"; - if (!fs::exists(language_file)) - continue; - - result.emplace_back(lang_info); + // This must be run after wxLocale::Init, as the latter sets up its own wxTranslations instance which we want to override + wxTranslations::Set(translationsMgr.release()); } +} - return result; +std::vector CemuApp::GetAvailableTranslationLanguages(wxTranslations* translationsMgr) +{ + wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxHelper::FromPath(ActiveSettings::GetDataPath("resources"))); + std::vector languages; + for (const auto& langName : translationsMgr->GetAvailableTranslations("cemu")) + { + const auto* langInfo = wxLocale::FindLanguageInfo(langName); + if (langInfo) + languages.emplace_back(langInfo); + } + return languages; } void CemuApp::CreateDefaultFiles(bool first_start) @@ -291,9 +258,10 @@ void CemuApp::CreateDefaultFiles(bool first_start) // check for mlc01 folder missing if custom path has been set if (!fs::exists(mlc) && !first_start) { - const std::wstring message = fmt::format(fmt::runtime(_(L"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?").ToStdWstring()), mlc.wstring()); + 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)); - wxMessageDialog dialog(nullptr, message, "Error", wxCENTRE | wxYES_NO | wxCANCEL| wxICON_WARNING); + wxMessageDialog dialog(nullptr, message, _("Error"), wxCENTRE | wxYES_NO | wxCANCEL| wxICON_WARNING); dialog.SetYesNoCancelLabels(_("Yes"), _("No"), _("Select a custom path")); const auto dialogResult = dialog.ShowModal(); if (dialogResult == wxID_NO) @@ -365,16 +333,15 @@ void CemuApp::CreateDefaultFiles(bool first_start) } catch (const std::exception& ex) { - std::stringstream errorMsg; - errorMsg << fmt::format(fmt::runtime(_("Couldn't create a required mlc01 subfolder or file!\n\nError: {0}\nTarget path:\n{1}").ToStdString()), ex.what(), _pathToUtf8(mlc)); + wxString errorMsg = formatWxString(_("Couldn't create a required mlc01 subfolder or file!\n\nError: {0}\nTarget path:\n{1}"), ex.what(), _pathToUtf8(mlc)); #if BOOST_OS_WINDOWS const DWORD lastError = GetLastError(); if (lastError != ERROR_SUCCESS) errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError)); - - wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR); #endif + + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } @@ -391,17 +358,15 @@ void CemuApp::CreateDefaultFiles(bool first_start) } catch (const std::exception& ex) { - std::stringstream errorMsg; - errorMsg << fmt::format(fmt::runtime(_("Couldn't create a required cemu directory or file!\n\nError: {0}").ToStdString()), ex.what()); + wxString errorMsg = formatWxString(_("Couldn't create a required cemu directory or file!\n\nError: {0}"), ex.what()); #if BOOST_OS_WINDOWS const DWORD lastError = GetLastError(); if (lastError != ERROR_SUCCESS) errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError)); - - - wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR); #endif + + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } } diff --git a/src/gui/CemuApp.h b/src/gui/CemuApp.h index 1dac29f1..cfdab0a2 100644 --- a/src/gui/CemuApp.h +++ b/src/gui/CemuApp.h @@ -13,8 +13,7 @@ public: void OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) override; int FilterEvent(wxEvent& event) override; - const std::vector& GetLanguages() const { return m_languages; } - static std::vector GetAvailableLanguages(); + std::vector GetLanguages() const; static void CreateDefaultFiles(bool first_start = false); static bool TrySelectMLCPath(fs::path path); @@ -22,11 +21,13 @@ public: private: void ActivateApp(wxActivateEvent& event); + void LocalizeUI(); + static std::vector GetAvailableTranslationLanguages(wxTranslations* translationsMgr); MainWindow* m_mainFrame = nullptr; wxLocale m_locale; - std::vector m_languages; + std::vector m_availableTranslations; }; wxDECLARE_APP(CemuApp); diff --git a/src/gui/CemuUpdateWindow.cpp b/src/gui/CemuUpdateWindow.cpp index 2fc6d9aa..f85b13e2 100644 --- a/src/gui/CemuUpdateWindow.cpp +++ b/src/gui/CemuUpdateWindow.cpp @@ -24,7 +24,7 @@ wxDECLARE_EVENT(wxEVT_PROGRESS, wxCommandEvent); wxDEFINE_EVENT(wxEVT_PROGRESS, wxCommandEvent); CemuUpdateWindow::CemuUpdateWindow(wxWindow* parent) - : wxDialog(parent, wxID_ANY, "Cemu update", wxDefaultPosition, wxDefaultSize, + : wxDialog(parent, wxID_ANY, _("Cemu update"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) { auto* sizer = new wxBoxSizer(wxVERTICAL); @@ -35,7 +35,7 @@ CemuUpdateWindow::CemuUpdateWindow(wxWindow* parent) auto* rows = new wxFlexGridSizer(0, 2, 0, 0); rows->AddGrowableCol(1); - m_text = new wxStaticText(this, wxID_ANY, "Checking for latest version..."); + m_text = new wxStaticText(this, wxID_ANY, _("Checking for latest version...")); rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); { diff --git a/src/gui/ChecksumTool.cpp b/src/gui/ChecksumTool.cpp index 1993d70b..63c6d4d5 100644 --- a/src/gui/ChecksumTool.cpp +++ b/src/gui/ChecksumTool.cpp @@ -81,8 +81,8 @@ const char kSchema[] = R"( ChecksumTool::ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry) - : wxDialog(parent, wxID_ANY, - wxStringFormat2(_("Title checksum of {:08x}-{:08x}"), (uint32)(entry.title_id >> 32), (uint32)(entry.title_id & 0xFFFFFFFF)), + : wxDialog(parent, wxID_ANY, + formatWxString(_("Title checksum of {:08x}-{:08x}"), (uint32) (entry.title_id >> 32), (uint32) (entry.title_id & 0xFFFFFFFF)), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_entry(entry) { @@ -413,7 +413,7 @@ void ChecksumTool::OnExportChecksums(wxCommandEvent& event) } else { - wxMessageBox(wxStringFormat2(_("Can't write to file: {}"), target_file.string()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(formatWxString(_("Can't write to file: {}"), target_file.string()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } @@ -461,17 +461,17 @@ void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) if (m_json_entry.title_id != test_entry.title_id) { - wxMessageBox(wxStringFormat2(_("The file you are comparing with is for a different title.")), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(formatWxString(_("The file you are comparing with is for a different title.")), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if (m_json_entry.version != test_entry.version) { - wxMessageBox(wxStringFormat2(_("Wrong version: {}"), test_entry.version), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(formatWxString(_("Wrong version: {}"), test_entry.version), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if (m_json_entry.region != test_entry.region) { - wxMessageBox(wxStringFormat2(_("Wrong region: {}"), test_entry.region), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(formatWxString(_("Wrong region: {}"), test_entry.region), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if (!m_json_entry.wud_hash.empty()) @@ -483,7 +483,7 @@ void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) } if(!boost::iequals(test_entry.wud_hash, m_json_entry.wud_hash)) { - wxMessageBox(wxStringFormat2(_("Your game image is invalid!\n\nYour hash:\n{}\n\nExpected hash:\n{}"), m_json_entry.wud_hash, test_entry.wud_hash), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(formatWxString(_("Your game image is invalid!\n\nYour hash:\n{}\n\nExpected hash:\n{}"), m_json_entry.wud_hash, test_entry.wud_hash), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } @@ -563,7 +563,9 @@ void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) } else if (missing_files.empty() && !invalid_hashes.empty()) { - const int result = wxMessageBox(wxStringFormat2(_("{} files have an invalid hash!\nDo you want to export a list of them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); + const int result = wxMessageBox(formatWxString( + _("{} files have an invalid hash!\nDo you want to export a list of them to a file?"), + invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); if (result == wxYES) { writeMismatchInfoToLog(); @@ -572,7 +574,9 @@ void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) } else if (!missing_files.empty() && !invalid_hashes.empty()) { - const int result = wxMessageBox(wxStringFormat2(_("Multiple issues with your game files have been found!\nDo you want to export them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); + const int result = wxMessageBox(formatWxString( + _("Multiple issues with your game files have been found!\nDo you want to export them to a file?"), + invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); if (result == wxYES) { writeMismatchInfoToLog(); @@ -584,7 +588,7 @@ void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) } catch (const std::exception& ex) { - wxMessageBox(wxStringFormat2(_("JSON parse error: {}"), ex.what()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(formatWxString(_("JSON parse error: {}"), ex.what()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } @@ -610,7 +614,7 @@ void ChecksumTool::OnVerifyOnline(wxCommandEvent& event) d.ParseStream(str); if (d.HasParseError()) { - wxMessageBox(_("Can't parse json file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(_("Can't parse JSON file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -638,7 +642,7 @@ void ChecksumTool::OnVerifyLocal(wxCommandEvent& event) d.ParseStream(str); if (d.HasParseError()) { - wxMessageBox(_("Can't parse json file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + wxMessageBox(_("Can't parse JSON file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -680,7 +684,7 @@ void ChecksumTool::DoWork() case TitleInfo::TitleDataFormat::WUD: { const auto path = m_entry.path.string(); - wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, wxStringFormat2(_("Reading game image: {}"), path))); + wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, formatWxString(_("Reading game image: {}"), path))); wud_t* wud = wud_open(m_info.GetPath()); if (!wud) @@ -709,11 +713,11 @@ void ChecksumTool::DoWork() EVP_DigestUpdate(sha256, buffer.data(), read); - wxQueueEvent(this, new wxSetGaugeValue((int)((offset * 90) / wud_size), m_progress, m_status, wxStringFormat2(_("Reading game image: {0}/{1} kB"), offset / 1024, wud_size / 1024))); + wxQueueEvent(this, new wxSetGaugeValue((int)((offset * 90) / wud_size), m_progress, m_status, formatWxString(_("Reading game image: {0}/{1} kB"), offset / 1024, wud_size / 1024))); } while (read != 0 && size > 0); wud_close(wud); - wxQueueEvent(this, new wxSetGaugeValue(90, m_progress, m_status, wxStringFormat2(_("Generating checksum of game image: {}"), path))); + wxQueueEvent(this, new wxSetGaugeValue(90, m_progress, m_status, formatWxString(_("Generating checksum of game image: {}"), path))); if (!m_running.load(std::memory_order_relaxed)) return; @@ -729,7 +733,7 @@ void ChecksumTool::DoWork() m_json_entry.wud_hash = str.str(); - wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, wxStringFormat2(_("Generated checksum of game image: {}"), path))); + wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, formatWxString(_("Generated checksum of game image: {}"), path))); break; } default: @@ -765,7 +769,7 @@ void ChecksumTool::DoWork() m_json_entry.file_hashes[filename] = str.str(); ++counter; - wxQueueEvent(this, new wxSetGaugeValue((int)((counter * 100) / file_count), m_progress, m_status, wxStringFormat2(_("Hashing game file: {}/{}"), counter, file_count))); + wxQueueEvent(this, new wxSetGaugeValue((int)((counter * 100) / file_count), m_progress, m_status, formatWxString(_("Hashing game file: {}/{}"), counter, file_count))); if (!m_running.load(std::memory_order_relaxed)) { @@ -775,7 +779,7 @@ void ChecksumTool::DoWork() } m_info.Unmount(temporaryMountPath.c_str()); - wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, wxStringFormat2(_("Generated checksum of {} game files"), file_count))); + wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, formatWxString(_("Generated checksum of {} game files"), file_count))); break; } } diff --git a/src/gui/GameProfileWindow.cpp b/src/gui/GameProfileWindow.cpp index 4d572ac3..eb715c58 100644 --- a/src/gui/GameProfileWindow.cpp +++ b/src/gui/GameProfileWindow.cpp @@ -166,7 +166,7 @@ GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id) for (int i = 0; i < 8; ++i) { - profile_sizer->Add(new wxStaticText(panel, wxID_ANY, fmt::format("{} {}", _("Controller").ToStdString(), (i + 1))), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + profile_sizer->Add(new wxStaticText(panel, wxID_ANY, formatWxString(_("Controller {}"), i + 1)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_controller_profile[i] = new wxComboBox(panel, wxID_ANY,"", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_DROPDOWN| wxCB_READONLY); m_controller_profile[i]->SetMinSize(wxSize(250, -1)); @@ -244,7 +244,7 @@ void GameProfileWindow::SetProfileInt(gameProfileIntegerOption_t& option, wxChec void GameProfileWindow::ApplyProfile() { if(m_game_profile.m_gameName) - this->SetTitle(fmt::format("{} - {}", _("Edit game profile").ToStdString(), m_game_profile.m_gameName.value())); + this->SetTitle(_("Edit game profile") + " - " + m_game_profile.m_gameName.value()); // general m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value()); diff --git a/src/gui/GameUpdateWindow.cpp b/src/gui/GameUpdateWindow.cpp index 36afacc9..f6f90777 100644 --- a/src/gui/GameUpdateWindow.cpp +++ b/src/gui/GameUpdateWindow.cpp @@ -10,32 +10,30 @@ #include "helpers/wxHelpers.h" #include "wxHelper.h" -std::string _GetTitleIdTypeStr(TitleId titleId) +wxString _GetTitleIdTypeStr(TitleId titleId) { TitleIdParser tip(titleId); switch (tip.GetType()) { case TitleIdParser::TITLE_TYPE::AOC: - return _("DLC").ToStdString(); + return _("DLC"); case TitleIdParser::TITLE_TYPE::BASE_TITLE: - return _("Base game").ToStdString(); + return _("Base game"); case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO: - return _("Demo").ToStdString(); + return _("Demo"); case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE: case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE: - return _("System title").ToStdString(); + return _("System title"); case TitleIdParser::TITLE_TYPE::SYSTEM_DATA: - return _("System data title").ToStdString(); + return _("System data title"); case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE: - return _("Update").ToStdString(); + return _("Update"); default: break; } return "Unknown"; } -bool IsRunningInWine(); - bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) { m_title_info = TitleInfo(metaPath); @@ -59,11 +57,11 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) if (tip.GetType() != tipOther.GetType()) { - std::string typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId()); - std::string typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId()); + auto typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId()); + auto typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId()); - std::string wxMsg = wxHelper::MakeUTF8(_("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title.")); - wxMessageDialog dialog(this, fmt::format(fmt::runtime(wxMsg), typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); + auto wxMsg = _("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title."); + wxMessageDialog dialog(this, formatWxString(wxMsg, typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); if (dialog.ShowModal() != wxID_YES) return false; } @@ -92,7 +90,7 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) if (ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return false; } @@ -130,15 +128,11 @@ bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) } } - // checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step - if(!IsRunningInWine()) + const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath()); + if (targetSpace.free <= m_required_size) { - const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath()); - if (targetSpace.free <= m_required_size) - { - auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); - throw std::runtime_error(string); - } + auto string = formatWxString(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); + throw std::runtime_error(string.utf8_string()); } return true; @@ -250,7 +244,7 @@ void GameUpdateWindow::ThreadWork() error_msg << GetSystemErrorMessage(ex); if(currentDirEntry != fs::directory_entry{}) - error_msg << fmt::format("\n{}\n{}",_("Current file:").ToStdString(), _pathToUtf8(currentDirEntry.path())); + error_msg << fmt::format("\n{}\n{}",_("Current file:").utf8_string(), _pathToUtf8(currentDirEntry.path())); m_thread_exception = error_msg.str(); m_thread_state = ThreadCanceled; diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 6f344163..5dfa10b6 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -51,17 +51,17 @@ #include "util/ScreenSaver/ScreenSaver.h" -const wxString kDirectSound(wxT("DirectSound")); -const wxString kXAudio27(wxT("XAudio2.7")); -const wxString kXAudio2(wxT("XAudio2")); -const wxString kCubeb(wxT("Cubeb")); +const wxString kDirectSound("DirectSound"); +const wxString kXAudio27("XAudio2.7"); +const wxString kXAudio2("XAudio2"); +const wxString kCubeb("Cubeb"); -const wxString kPropertyPersistentId(wxT("PersistentId")); -const wxString kPropertyMiiName(wxT("MiiName")); -const wxString kPropertyBirthday(wxT("Birthday")); -const wxString kPropertyGender(wxT("Gender")); -const wxString kPropertyEmail(wxT("Email")); -const wxString kPropertyCountry(wxT("Country")); +const wxString kPropertyPersistentId("PersistentId"); +const wxString kPropertyMiiName("MiiName"); +const wxString kPropertyBirthday("Birthday"); +const wxString kPropertyGender("Gender"); +const wxString kPropertyEmail("Email"); +const wxString kPropertyCountry("Country"); wxDEFINE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent); @@ -123,13 +123,13 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) first_row->Add(new wxStaticText(box, wxID_ANY, _("Language"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - wxString language_choices[] = { _("Default"), _("English") }; + wxString language_choices[] = { _("Default") }; m_language = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); m_language->SetSelection(0); m_language->SetToolTip(_("Changes the interface language of Cemu\nAvailable languages are stored in the translation directory\nA restart will be required after changing the language")); for (const auto& language : wxGetApp().GetLanguages()) { - m_language->Append(language->Description); + m_language->Append(language->DescriptionNative); } first_row->Add(m_language, 0, wxALL | wxEXPAND, 5); @@ -166,6 +166,9 @@ 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 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")); @@ -208,7 +211,7 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) box_sizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); - auto* change_path = new wxButton(box, wxID_ANY, wxT("...")); + auto* change_path = new wxButton(box, wxID_ANY, "..."); change_path->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); change_path->SetToolTip(_("Select a custom mlc path\nThe mlc path is used to store Wii U related files like save games, game updates and dlc data")); box_sizer->Add(change_path, 0, wxALL, 5); @@ -366,7 +369,7 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) m_audio_latency = new wxSlider(box, wxID_ANY, 2, 0, IAudioAPI::kBlockCount - 1, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); m_audio_latency->SetToolTip(_("Controls the amount of buffered audio data\nHigher values will create a delay in audio playback, but may avoid audio problems when emulation is too slow")); audio_general_row->Add(m_audio_latency, 0, wxEXPAND | wxALL, 5); - auto latency_text = new wxStaticText(box, wxID_ANY, wxT("24ms")); + auto latency_text = new wxStaticText(box, wxID_ANY, "24ms"); audio_general_row->Add(latency_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnLatencySliderChanged, this, wxID_ANY, wxID_ANY, new wxControlObject(latency_text)); m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnAudioLatencyChanged, this); @@ -405,7 +408,7 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_volume = new wxSlider(box, wxID_ANY, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); audio_tv_row->Add(m_tv_volume, 0, wxEXPAND | wxALL, 5); - auto audio_tv_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%")); + auto audio_tv_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_tv_row->Add(audio_tv_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_tv_volume_text)); @@ -446,7 +449,7 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); audio_pad_row->Add(m_pad_volume, 0, wxEXPAND | wxALL, 5); - auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%")); + auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_pad_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text)); @@ -487,7 +490,7 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5); - auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%")); + auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text)); @@ -744,7 +747,7 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) m_account_grid->SetMinSize({ 300, -1 }); //m_account_grid->Append(new wxPropertyCategory("Main")); - auto* persistent_id_gprop = m_account_grid->Append(new wxStringProperty(wxT("PersistentId"), kPropertyPersistentId)); + auto* persistent_id_gprop = m_account_grid->Append(new wxStringProperty("PersistentId", kPropertyPersistentId)); persistent_id_gprop->SetHelpString(_("The persistent id is the internal folder name used for your saves")); m_account_grid->SetPropertyReadOnly(persistent_id_gprop); @@ -754,7 +757,7 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) wxPGChoices gender; gender.Add(_("Female"), 0); gender.Add(_("Male"), 1); - m_account_grid->Append(new wxEnumProperty("Gender", kPropertyGender, gender)); + m_account_grid->Append(new wxEnumProperty(_("Gender"), kPropertyGender, gender)); m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail)); @@ -818,7 +821,7 @@ wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook) debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GDB Stub port"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - m_gdb_port = new wxSpinCtrl(panel, wxID_ANY, wxT("1337"), wxDefaultPosition, wxDefaultSize, 0, 1000, 65535); + m_gdb_port = new wxSpinCtrl(panel, wxID_ANY, "1337", wxDefaultPosition, wxDefaultSize, 0, 1000, 65535); m_gdb_port->SetToolTip(_("Changes the port that the GDB stub will use, which you can use by either starting Cemu with the --enable-gdbstub option or by enabling it the Debug tab.")); debug_row->Add(m_gdb_port, 0, wxALL | wxEXPAND, 5); @@ -920,19 +923,17 @@ void GeneralSettings2::StoreConfig() config.game_paths.clear(); for (auto& path : m_game_paths->GetStrings()) - config.game_paths.emplace_back(path); + config.game_paths.emplace_back(path.utf8_string()); auto selection = m_language->GetSelection(); if (selection == 0) GetConfig().language = wxLANGUAGE_DEFAULT; - else if (selection == 1) - GetConfig().language = wxLANGUAGE_ENGLISH; else { const auto language = m_language->GetStringSelection(); for (const auto& lang : app->GetLanguages()) { - if (lang->Description == language) + if (lang->DescriptionNative == language) { GetConfig().language = lang->Language; break; @@ -1527,7 +1528,7 @@ void GeneralSettings2::ApplyConfig() for (auto& path : config.game_paths) { - m_game_paths->Append(path); + m_game_paths->Append(to_wxString(path)); } const auto app = (CemuApp*)wxTheApp; @@ -1535,7 +1536,7 @@ void GeneralSettings2::ApplyConfig() { if (config.language == language->Language) { - m_language->SetStringSelection(language->Description); + m_language->SetStringSelection(language->DescriptionNative); break; } } @@ -1998,44 +1999,60 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) if (validator) // everything valid? shouldn't happen return; - std::wstringstream err; - err << L"The following error(s) have been found:" << std::endl; + wxString err; + err << _("The following error(s) have been found:") << '\n'; if (validator.otp == OnlineValidator::FileState::Missing) - err << L"otp.bin missing in cemu root directory" << std::endl; + err << _("otp.bin missing in Cemu root directory") << '\n'; else if(validator.otp == OnlineValidator::FileState::Corrupted) - err << L"otp.bin is invalid" << std::endl; + err << _("otp.bin is invalid") << '\n'; if (validator.seeprom == OnlineValidator::FileState::Missing) - err << L"seeprom.bin missing in cemu root directory" << std::endl; + err << _("seeprom.bin missing in Cemu root directory") << '\n'; else if(validator.seeprom == OnlineValidator::FileState::Corrupted) - err << L"seeprom.bin is invalid" << std::endl; + err << _("seeprom.bin is invalid") << '\n'; if(!validator.missing_files.empty()) { - err << L"Missing certificate and key files:" << std::endl; + err << _("Missing certificate and key files:") << '\n'; int counter = 0; for (const auto& f : validator.missing_files) { - err << f << std::endl; + err << f << '\n'; ++counter; if(counter > 10) { - err << L"..." << std::endl; + err << "..." << '\n'; break; } } - err << std::endl; + err << '\n'; } if (!validator.valid_account) { - err << L"The currently selected account is not a valid or dumped online account:\n" << boost::nowide::widen(fmt::format("{}", validator.account_error)); + err << _("The currently selected account is not a valid or dumped online account:") << '\n'; + err << GetOnlineAccountErrorMessage(validator.account_error); } - - - wxMessageBox(err.str(), _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION); + + wxMessageBox(err, _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION); } + +wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) +{ + switch (error) { + case OnlineAccountError::kNoAccountId: + return _("AccountId missing (The account is not connected to a NNID)"); + 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: + return _("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)"); + case OnlineAccountError::kNoPrincipalId: + return _("PrincipalId missing"); + default: + return "no error"; + } +} \ No newline at end of file diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index a6136abf..2846af38 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include class wxColourPickerCtrl; @@ -100,6 +101,7 @@ private: void OnShowOnlineValidator(wxCommandEvent& event); void OnOnlineEnable(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); + static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); // updates cemu audio devices void UpdateAudioDevice(); diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp index 999d760f..7a4c817c 100644 --- a/src/gui/GettingStartedDialog.cpp +++ b/src/gui/GettingStartedDialog.cpp @@ -146,6 +146,9 @@ 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(); +#endif sizer->Add(option_sizer, 1, wxEXPAND, 5); page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); @@ -226,7 +229,7 @@ void GettingStartedDialog::OnClose(wxCloseEvent& event) const auto it = std::find(config.game_paths.cbegin(), config.game_paths.cend(), gamePath); if (it == config.game_paths.cend()) { - config.game_paths.emplace_back(gamePath.generic_wstring()); + config.game_paths.emplace_back(_pathToUtf8(gamePath)); m_game_path_changed = true; } } @@ -245,7 +248,7 @@ void GettingStartedDialog::OnClose(wxCloseEvent& event) CafeTitleList::ClearScanPaths(); for (auto& it : GetConfig().game_paths) - CafeTitleList::AddScanPath(it); + CafeTitleList::AddScanPath(_utf8ToPath(it)); CafeTitleList::Refresh(); } diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index 5f03e444..a76260b7 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -64,7 +64,7 @@ void GraphicPacksWindow2::FillGraphicPackList() const { bool found = false; - if (boost::icontains(p->GetPath(), m_filter)) + if (boost::icontains(p->GetVirtualPath(), m_filter)) found = true; else { @@ -82,7 +82,7 @@ void GraphicPacksWindow2::FillGraphicPackList() const continue; } - const auto& path = p->GetPath(); + const auto& path = p->GetVirtualPath(); auto tokens = TokenizeView(path, '/'); auto node = root; for(size_t i=0; iGetFilename()).lexically_normal(); + auto filename = _utf8ToPath(gp->GetNormalizedPathString()); if (gp->IsEnabled()) { data.graphic_pack_entries.try_emplace(filename); @@ -445,7 +445,7 @@ void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event) m_graphic_pack_name->SetLabel(wxHelper::FromUtf8(m_gp_name)); if (gp->GetDescription().empty()) - m_gp_description = _("This graphic pack has no description"); + m_gp_description = _("This graphic pack has no description").utf8_string(); else m_gp_description = gp->GetDescription(); @@ -570,8 +570,8 @@ void GraphicPacksWindow2::OnActivePresetChanged(wxCommandEvent& event) wxASSERT(obj); const auto string_data = dynamic_cast(obj->GetClientObject()); wxASSERT(string_data); - const auto preset = wxHelper::MakeUTF8(obj->GetStringSelection()); - if(m_shown_graphic_pack->SetActivePreset(wxHelper::MakeUTF8(string_data->GetData()), preset)) + const auto preset = obj->GetStringSelection().utf8_string(); + if(m_shown_graphic_pack->SetActivePreset(string_data->GetData().utf8_string(), preset)) { wxWindowUpdateLocker lock(this); ClearPresets(); @@ -603,34 +603,30 @@ void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event) { if (!CafeSystem::IsTitleRunning()) { - std::vector old_packs = GraphicPack2::GetGraphicPacks(); + // remember virtual paths of all the enabled packs + std::map previouslyEnabledPacks; + for(auto& it : GraphicPack2::GetGraphicPacks()) + { + if(it->IsEnabled()) + previouslyEnabledPacks.emplace(it->GetNormalizedPathString(), it->GetVirtualPath()); + } + // reload graphic packs RefreshGraphicPacks(); FillGraphicPackList(); - - // check if enabled graphic packs are lost: - const auto& new_packs = GraphicPack2::GetGraphicPacks(); - std::stringstream str; - for(const auto& p : old_packs) + // remove packs which are still present + for(auto& it : GraphicPack2::GetGraphicPacks()) + previouslyEnabledPacks.erase(it->GetNormalizedPathString()); + if(!previouslyEnabledPacks.empty()) { - if (!p->IsEnabled()) - continue; - - const auto it = std::find_if(new_packs.cbegin(), new_packs.cend(), [&p](const auto& gp) - { - return gp->GetFilename() == p->GetFilename(); - }); - - if(it == new_packs.cend()) + std::string lost_packs; + for(auto& it : previouslyEnabledPacks) { - str << p->GetPath() << std::endl; + lost_packs.append(it.second); + lost_packs.push_back('\n'); } - } - - const auto packs = str.str(); - if(!packs.empty()) - { - wxMessageBox(fmt::format("{}\n \n{} \n{}", _("This update removed or renamed the following graphic packs:").ToStdString(), packs, _("You may need to set them up again.").ToStdString()), - _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + wxString message = _("This update removed or renamed the following graphic packs:"); + message << "\n \n" << wxString::FromUTF8(lost_packs) << " \n" << _("You may need to set them up again."); + wxMessageBox(message, _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this); } } } @@ -668,7 +664,7 @@ void GraphicPacksWindow2::SashPositionChanged(wxEvent& event) void GraphicPacksWindow2::OnFilterUpdate(wxEvent& event) { - m_filter = wxHelper::MakeUTF8(m_filter_text->GetValue()); + m_filter = m_filter_text->GetValue().utf8_string(); FillGraphicPackList(); event.Skip(); } diff --git a/src/gui/LoggingWindow.cpp b/src/gui/LoggingWindow.cpp index 90d3acd8..b644ff09 100644 --- a/src/gui/LoggingWindow.cpp +++ b/src/gui/LoggingWindow.cpp @@ -70,7 +70,7 @@ void LoggingWindow::OnLogMessage(wxLogEvent& event) void LoggingWindow::OnFilterChange(wxCommandEvent& event) { - m_log_list->SetActiveFilter(from_wxString(m_filter->GetValue())); + m_log_list->SetActiveFilter(m_filter->GetValue().utf8_string()); event.Skip(); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0fe0d9bd..d5c9d46c 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -12,8 +12,6 @@ #include "canvas/VulkanCanvas.h" #include "Cafe/OS/libs/nn_nfp/nn_nfp.h" #include "Cafe/OS/libs/swkbd/swkbd.h" -#include "Cafe/IOSU/legacy/iosu_crypto.h" -#include "Cafe/GameProfile/GameProfile.h" #include "debugger/DebuggerWindow2.h" #include "debugger/AudioDebuggerWindow.h" #include "util/helpers/helpers.h" @@ -36,9 +34,7 @@ #include "TitleManager.h" #include "Cafe/CafeSystem.h" -#include "Cafe/TitleList/GameInfo.h" -#include #include "util/helpers/SystemException.h" #include "DownloadGraphicPacksWindow.h" #include "GettingStartedDialog.h" @@ -80,6 +76,7 @@ enum MAINFRAME_MENU_ID_FILE_LOAD = 20100, MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, + MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MAINFRAME_MENU_ID_FILE_EXIT, MAINFRAME_MENU_ID_FILE_END_EMULATION, MAINFRAME_MENU_ID_FILE_RECENT_0, @@ -149,8 +146,7 @@ enum MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, // help - MAINFRAME_MENU_ID_HELP_WEB = 21700, - MAINFRAME_MENU_ID_HELP_ABOUT, + MAINFRAME_MENU_ID_HELP_ABOUT = 21700, MAINFRAME_MENU_ID_HELP_UPDATE, MAINFRAME_MENU_ID_HELP_GETTING_STARTED, @@ -172,7 +168,8 @@ EVT_MOVE(MainWindow::OnMove) // file menu EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu) EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate) -EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenCemuFolder) +EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder) +EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) @@ -224,7 +221,6 @@ EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MainWindow::OnDebugViewPPCDe EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MainWindow::OnDebugViewAudioDebugger) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugViewTextureRelations) // help menu -EVT_MENU(MAINFRAME_MENU_ID_HELP_WEB, MainWindow::OnHelpVistWebpage) EVT_MENU(MAINFRAME_MENU_ID_HELP_ABOUT, MainWindow::OnHelpAbout) EVT_MENU(MAINFRAME_MENU_ID_HELP_UPDATE, MainWindow::OnHelpUpdate) EVT_MENU(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, MainWindow::OnHelpGettingStarted) @@ -247,7 +243,7 @@ public: bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override { if(!m_window->IsGameLaunched() && filenames.GetCount() == 1) - return m_window->FileLoad(filenames[0].wc_str(), wxLaunchGameEvent::INITIATED_BY::DRAG_AND_DROP); + return m_window->FileLoad(_utf8ToPath(filenames[0].utf8_string()), wxLaunchGameEvent::INITIATED_BY::DRAG_AND_DROP); return false; } @@ -264,11 +260,11 @@ public: { if (!m_window->IsGameLaunched() || filenames.GetCount() != 1) return false; - uint32 nfcError; - if (nnNfp_touchNfcTagFromFile(filenames[0].wc_str(), &nfcError)) + std::string path = filenames[0].utf8_string(); + if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError)) { - GetConfig().AddRecentNfcFile((wchar_t*)filenames[0].wc_str()); + GetConfig().AddRecentNfcFile(path); m_window->UpdateNFCMenu(); return true; } @@ -582,9 +578,8 @@ bool MainWindow::InstallUpdate(const fs::path& metaFilePath) return false; } -bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY initiatedBy) +bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATED_BY initiatedBy) { - const fs::path launchPath = fs::path(fileName); TitleInfo launchTitle{ launchPath }; if (launchTitle.IsValid()) { @@ -607,22 +602,22 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY else if (r == CafeSystem::STATUS_CODE::UNABLE_TO_MOUNT) { wxString t = _("Unable to mount title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nFile which failed to load:\n"); - t.append(fileName); + t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } else if (r != CafeSystem::STATUS_CODE::SUCCESS) { wxString t = _("Failed to launch game."); - t.append(fileName); + t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } else //if (launchTitle.GetFormat() == TitleInfo::TitleDataFormat::INVALID_STRUCTURE ) { - // title is invalid, if its an RPX/ELF we can launch it directly - // otherwise its an error + // title is invalid, if it's an RPX/ELF we can launch it directly + // otherwise it's an error CafeTitleFileType fileType = DetermineCafeSystemFileType(launchPath); if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF) { @@ -631,7 +626,7 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY { cemu_assert_debug(false); // todo wxString t = _("Failed to launch executable. Path: "); - t.append(fileName); + t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } @@ -639,7 +634,7 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::GAME_LIST) { wxString t = _("Unable to launch title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nPath which failed to load:\n"); - t.append(fileName); + t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } @@ -647,23 +642,33 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY initiatedBy == wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE) { wxString t = _("Unable to launch game\nPath:\n"); - t.append(fileName); + t.append(_pathToUtf8(launchPath)); + if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_DISC_KEY) + { + t.append("\n\n"); + t.append(_("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.")); + } + if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_TITLE_TIK) + { + t.append("\n\n"); + t.append(_("Could not decrypt title because title.tik is missing.")); + } wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } else { wxString t = _("Unable to launch game\nPath:\n"); - t.append(fileName); + t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } if(launchTitle.IsValid()) - GetConfig().AddRecentlyLaunchedFile(launchTitle.GetPath().generic_wstring()); + GetConfig().AddRecentlyLaunchedFile(_pathToUtf8(launchTitle.GetPath())); else - GetConfig().AddRecentlyLaunchedFile(fileName); + GetConfig().AddRecentlyLaunchedFile(_pathToUtf8(launchPath)); wxWindowUpdateLocker lock(this); @@ -719,7 +724,7 @@ void MainWindow::OnLaunchFromFile(wxLaunchGameEvent& event) { if (event.GetPath().empty()) return; - FileLoad(event.GetPath().generic_wstring(), event.GetInitiatedBy()); + FileLoad(event.GetPath(), event.GetInitiatedBy()); } void MainWindow::OnFileMenu(wxCommandEvent& event) @@ -727,17 +732,19 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) const auto menuId = event.GetId(); if (menuId == MAINFRAME_MENU_ID_FILE_LOAD) { - const auto wildcard = wxStringFormat2( - "{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf" + const auto wildcard = formatWxString( + "{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf;title.tmd" "|{}|*.wud;*.wux;*.iso" + "|{}|title.tmd" "|{}|*.wua" "|{}|*.rpx;*.elf" "|{}|*", _("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"), _("Wii U image (*.wud, *.wux, *.iso, *.wad)"), + _("Wii U NUS content"), _("Wii U archive (*.wua)"), _("Wii U executable (*.rpx, *.elf)"), - _("All files (*.*)") + _("All files (*.*)") ); wxFileDialog openFileDialog(this, _("Open file to launch"), wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); @@ -746,7 +753,7 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) return; const wxString wxStrFilePath = openFileDialog.GetPath(); - FileLoad(wxStrFilePath.wc_str(), wxLaunchGameEvent::INITIATED_BY::MENU); + FileLoad(_utf8ToPath(wxStrFilePath.utf8_string()), wxLaunchGameEvent::INITIATED_BY::MENU); } else if (menuId >= MAINFRAME_MENU_ID_FILE_RECENT_0 && menuId <= MAINFRAME_MENU_ID_FILE_RECENT_LAST) { @@ -754,7 +761,7 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) const size_t index = menuId - MAINFRAME_MENU_ID_FILE_RECENT_0; if (index < config.recent_launch_files.size()) { - const auto& path = config.recent_launch_files[index]; + fs::path path = _utf8ToPath(config.recent_launch_files[index]); if (!path.empty()) FileLoad(path, wxLaunchGameEvent::INITIATED_BY::MENU); } @@ -770,9 +777,12 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) } } -void MainWindow::OnOpenCemuFolder(wxCommandEvent& event) +void MainWindow::OnOpenFolder(wxCommandEvent& event) { - wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath())); + if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER) + wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath())); + else if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER) + wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath())); } void MainWindow::OnInstallUpdate(wxCommandEvent& event) @@ -795,7 +805,7 @@ void MainWindow::OnInstallUpdate(wxCommandEvent& event) { if (!fs::exists(dirPath.parent_path() / "code") || !fs::exists(dirPath.parent_path() / "content") || !fs::exists(dirPath.parent_path() / "meta")) { - wxMessageBox(wxStringFormat2(_("The (parent) folder of the title you selected is missing at least one of the required subfolders (\"code\", \"content\" and \"meta\")\nMake sure that the files are complete."), dirPath.filename().string())); + wxMessageBox(formatWxString(_("The (parent) folder of the title you selected is missing at least one of the required subfolders (\"code\", \"content\" and \"meta\")\nMake sure that the files are complete."), dirPath.filename().string())); continue; } else @@ -826,7 +836,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) return; wxString wxStrFilePath = openFileDialog.GetPath(); uint32 nfcError; - if (nnNfp_touchNfcTagFromFile(wxStrFilePath.wc_str(), &nfcError) == false) + if (nnNfp_touchNfcTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { if (nfcError == NFC_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); @@ -835,7 +845,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) } else { - GetConfig().AddRecentNfcFile((wchar_t*)wxStrFilePath.wc_str()); + GetConfig().AddRecentNfcFile(wxStrFilePath.utf8_string()); UpdateNFCMenu(); } } @@ -849,7 +859,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) if (!path.empty()) { uint32 nfcError = 0; - if (nnNfp_touchNfcTagFromFile(path.c_str(), &nfcError) == false) + if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError) == false) { if (nfcError == NFC_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); @@ -1192,7 +1202,14 @@ void MainWindow::OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event) sint32 id = event.GetId(); if (id >= loggingIdBase && id < (MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 64)) { - cemuLog_setFlag(static_cast(id - loggingIdBase), event.IsChecked()); + bool isEnable = event.IsChecked(); + LogType loggingType = static_cast(id - loggingIdBase); + if (isEnable) + GetConfig().log_flag = GetConfig().log_flag.GetValue() | cemuLog_getFlag(loggingType); + else + GetConfig().log_flag = GetConfig().log_flag.GetValue() & ~cemuLog_getFlag(loggingType); + cemuLog_setActiveLoggingFlags(GetConfig().log_flag.GetValue()); + g_config.Save(); } } @@ -1929,13 +1946,13 @@ void MainWindow::UpdateNFCMenu() if (entry.empty()) continue; - if (!fs::exists(entry)) + if (!fs::exists(_utf8ToPath(entry))) continue; if (recentFileIndex == 0) m_nfcMenuSeparator0 = m_nfcMenu->AppendSeparator(); - m_nfcMenu->Append(MAINFRAME_MENU_ID_NFC_RECENT_0 + i, fmt::format(L"{}. {}", recentFileIndex, entry )); + m_nfcMenu->Append(MAINFRAME_MENU_ID_NFC_RECENT_0 + i, to_wxString(fmt::format("{}. {}", recentFileIndex, entry))); recentFileIndex++; if (recentFileIndex >= 12) @@ -1985,8 +2002,6 @@ void MainWindow::OnTimer(wxTimerEvent& event) } -void MainWindow::OnHelpVistWebpage(wxCommandEvent& event) {} - #define BUILD_DATE __DATE__ " " __TIME__ class CemuAboutDialog : public wxDialog @@ -2025,7 +2040,7 @@ public: void AddHeaderInfo(wxWindow* parent, wxSizer* sizer) { - auto versionString = fmt::format(fmt::runtime(_("Cemu\nVersion {0}\nCompiled on {1}\nOriginal authors: {2}").ToStdString()), BUILD_VERSION_STRING, BUILD_DATE, "Exzap, Petergov"); + auto versionString = formatWxString(_("Cemu\nVersion {0}\nCompiled on {1}\nOriginal authors: {2}"), BUILD_VERSION_STRING, BUILD_DATE, "Exzap, Petergov"); sizer->Add(new wxStaticText(parent, wxID_ANY, versionString), wxSizerFlags().Border(wxALL, 3).Border(wxTOP, 10)); sizer->Add(new wxHyperlinkCtrl(parent, -1, "https://cemu.info", "https://cemu.info"), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 3)); @@ -2049,7 +2064,7 @@ public: { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, -1, "zLib ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "http://www.zlib.net", "http://www.zlib.net"), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.zlib.net", "https://www.zlib.net"), 0); lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); sizer->Add(lineSizer); } @@ -2195,7 +2210,7 @@ public: , "Faris Leonhart", "MahvZero", "PlaguedGuardian", "Stuffie", "CaptainLester", "Qtech", "Zaurexus", "Leonidas", "Artifesto" , "Alca259", "SirWestofAsh", "Loli Co.", "The Technical Revolutionary", "MegaYama", "mitori", "Seymordius", "Adrian Josh Cruz", "Manuel Hoenings", "Just A Jabb" , "pgantonio", "CannonXIII", "Lonewolf00708", "AlexsDesign.com", "NoskLo", "MrSirHaku", "xElite_V AKA William H. Johnson", "Zalnor", "Pig", "James \"SE4LS\"", "DairyOrange", "Horoko Lawrence", "bloodmc", "Officer Jenny", "Quasar", "Postposterous", "Jake Jackson", "Kaydax", "CthePredatorG" - , "Hengi", "Pyrochaser"}; + , "Hengi", "Pyrochaser", "luma.x3"}; wxString nameListLeft, nameListRight; for (size_t i = 0; i < patreonSupporterNames.size(); i++) @@ -2265,17 +2280,12 @@ void MainWindow::RecreateMenu() m_fileMenuSeparator1 = nullptr; for (size_t i = 0; i < config.recent_launch_files.size(); i++) { - const auto& entry = config.recent_launch_files[i]; - if (entry.empty()) + const std::string& pathStr = config.recent_launch_files[i]; + if (pathStr.empty()) continue; - - if (!fs::exists(entry)) - continue; - if (recentFileIndex == 0) m_fileMenuSeparator0 = m_fileMenu->AppendSeparator(); - - m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, fmt::format(L"{}. {}", recentFileIndex, entry)); + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, to_wxString(fmt::format("{}. {}", recentFileIndex, pathStr))); recentFileIndex++; if (recentFileIndex >= 8) @@ -2292,6 +2302,7 @@ void MainWindow::RecreateMenu() } m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder")); + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder")); m_fileMenu->AppendSeparator(); m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); @@ -2348,6 +2359,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")); + m_menuBar->Append(toolsMenu, _("&Tools")); // cpu timer speed menu @@ -2377,11 +2389,12 @@ void MainWindow::RecreateMenu() 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::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::ThreadSync), _("&Coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::ThreadSync)); + 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_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::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)); @@ -2439,9 +2452,10 @@ void MainWindow::RecreateMenu() m_menuBar->Append(debugMenu, _("&Debug")); // help menu wxMenu* helpMenu = new wxMenu(); - //helpMenu->Append(MAINFRAME_MENU_ID_HELP_WEB, wxT("&Visit website")); - //helpMenu->AppendSeparator(); m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates")); +#if BOOST_OS_LINUX || BOOST_OS_MACOS + 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")); @@ -2471,57 +2485,6 @@ void MainWindow::RecreateMenu() SetMenuVisible(false); } -void MainWindow::OnAfterCallShowErrorDialog() -{ - //wxMessageBox((const wxString&)dialogText, (const wxString&)dialogTitle, wxICON_INFORMATION); - //wxDialog* dialog = new wxDialog(NULL,wxID_ANY,(const wxString&)dialogTitle,wxDefaultPosition,wxSize(310,170)); - //dialog->ShowModal(); - //dialogState = 1; -} - -bool MainWindow::EnableOnlineMode() const -{ - // TODO: not used anymore - // - // if enabling online mode, check if all requirements are met - std::wstring additionalErrorInfo; - const sint32 onlineReqError = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo); - - bool enableOnline = false; - if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_OTP_MISSING) - { - wxMessageBox(_("otp.bin could not be found"), _("Error"), wxICON_ERROR); - } - else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED) - { - wxMessageBox(_("otp.bin is corrupted or has invalid size"), _("Error"), wxICON_ERROR); - } - else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING) - { - wxMessageBox(_("seeprom.bin could not be found"), _("Error"), wxICON_ERROR); - } - else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED) - { - wxMessageBox(_("seeprom.bin is corrupted or has invalid size"), _("Error"), wxICON_ERROR); - } - else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_MISSING_FILE) - { - std::wstring errorMessage = fmt::format(L"Unable to load a necessary file:\n{}", additionalErrorInfo); - wxMessageBox(errorMessage.c_str(), _("Error"), wxICON_ERROR); - } - else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_OK) - { - enableOnline = true; - } - else - { - wxMessageBox(_("Unknown error occured"), _("Error"), wxICON_ERROR); - } - - //config_get()->enableOnlineMode = enableOnline; - return enableOnline; -} - void MainWindow::RestoreSettingsAfterGameExited() { RecreateMenu(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index ef7d0ae2..95564025 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -69,7 +69,7 @@ public: void UpdateSettingsAfterGameLaunch(); void RestoreSettingsAfterGameExited(); - bool FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY initiatedBy); + bool FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATED_BY initiatedBy); [[nodiscard]] bool IsGameLaunched() const { return m_game_launched; } @@ -96,7 +96,7 @@ public: void OnMouseWheel(wxMouseEvent& event); void OnClose(wxCloseEvent& event); void OnFileMenu(wxCommandEvent& event); - void OnOpenCemuFolder(wxCommandEvent& event); + void OnOpenFolder(wxCommandEvent& event); void OnLaunchFromFile(wxLaunchGameEvent& event); void OnInstallUpdate(wxCommandEvent& event); void OnFileExit(wxCommandEvent& event); @@ -104,11 +104,9 @@ public: void OnOptionsInput(wxCommandEvent& event); void OnAccountSelect(wxCommandEvent& event); void OnConsoleLanguage(wxCommandEvent& event); - void OnHelpVistWebpage(wxCommandEvent& event); void OnHelpAbout(wxCommandEvent& event); void OnHelpGettingStarted(wxCommandEvent& event); void OnHelpUpdate(wxCommandEvent& event); - void OnAfterCallShowErrorDialog(); void OnDebugSetting(wxCommandEvent& event); void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); void OnPPCInfoToggle(wxCommandEvent& event); @@ -153,7 +151,6 @@ private: void RecreateMenu(); static wxString GetInitialWindowTitle(); void ShowGettingStartedDialog(); - bool EnableOnlineMode() const; bool InstallUpdate(const fs::path& metaFilePath); diff --git a/src/gui/MemorySearcherTool.cpp b/src/gui/MemorySearcherTool.cpp index 5f791f3f..18734536 100644 --- a/src/gui/MemorySearcherTool.cpp +++ b/src/gui/MemorySearcherTool.cpp @@ -472,9 +472,7 @@ bool MemorySearcherTool::VerifySearchValue() const void MemorySearcherTool::FillResultList() { - //char text[128]; - //sprintf(text, "Results (%u)", (uint32)m_searchBuffer.size()); - auto text = wxStringFormat(_("Results ({0})"), L"%llu", m_searchBuffer.size()); + auto text = formatWxString(_("Results ({0})"), m_searchBuffer.size()); m_textEntryTable->SetLabelText(text); m_listResults->DeleteAllItems(); @@ -664,30 +662,6 @@ void MemorySearcherTool::SetSearchDataType() m_searchDataType = SearchDataType_None; } -std::string MemorySearcherTool::GetSearchTypeName() const -{ - switch (m_searchDataType) - { - case SearchDataType_String: - return from_wxString(kDatatypeString); - case SearchDataType_Float: - return from_wxString(kDatatypeFloat); - case SearchDataType_Double: - return from_wxString(kDatatypeDouble); - case SearchDataType_Int8: - return from_wxString(kDatatypeInt8); - case SearchDataType_Int16: - return from_wxString(kDatatypeInt16); - case SearchDataType_Int32: - return from_wxString(kDatatypeInt32); - case SearchDataType_Int64: - return from_wxString(kDatatypeInt64); - default: - return ""; - } - -} - template <> bool MemorySearcherTool::ConvertStringToType(const char* inValue, sint8& outValue) const { diff --git a/src/gui/MemorySearcherTool.h b/src/gui/MemorySearcherTool.h index 98e2bc0b..d87bfada 100644 --- a/src/gui/MemorySearcherTool.h +++ b/src/gui/MemorySearcherTool.h @@ -56,8 +56,6 @@ private: void RefreshResultList(); void RefreshStashList(); void SetSearchDataType(); - std::string GetSearchTypeName() const; - void CreateRightClickPopupMenu(); void Load(); void Save(); diff --git a/src/gui/PairingDialog.cpp b/src/gui/PairingDialog.cpp new file mode 100644 index 00000000..f90e6d13 --- /dev/null +++ b/src/gui/PairingDialog.cpp @@ -0,0 +1,236 @@ +#include "gui/wxgui.h" +#include "gui/PairingDialog.h" + +#if BOOST_OS_WINDOWS +#include +#endif + +wxDECLARE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); + +PairingDialog::PairingDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Pairing..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 20), wxGA_HORIZONTAL); + m_gauge->SetValue(0); + sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); + + auto* rows = new wxFlexGridSizer(0, 2, 0, 0); + rows->AddGrowableCol(1); + + m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); + rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + { + auto* right_side = new wxBoxSizer(wxHORIZONTAL); + + m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); + right_side->Add(m_cancelButton, 0, wxALL, 5); + + rows->Add(right_side, 1, wxALIGN_RIGHT, 5); + } + + sizer->Add(rows, 0, wxALL | wxEXPAND, 5); + + SetSizerAndFit(sizer); + Centre(wxBOTH); + + Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); + Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); + + m_thread = std::thread(&PairingDialog::WorkerThread, this); +} + +PairingDialog::~PairingDialog() +{ + Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); +} + +void PairingDialog::OnClose(wxCloseEvent& event) +{ + event.Skip(); + + m_threadShouldQuit = true; + if (m_thread.joinable()) + m_thread.join(); +} + +void PairingDialog::OnCancelButton(const wxCommandEvent& event) +{ + Close(); +} + +void PairingDialog::OnGaugeUpdate(wxCommandEvent& event) +{ + PairingState state = (PairingState)event.GetInt(); + + switch (state) + { + case PairingState::Pairing: + { + m_text->SetLabel(_("Found controller. Pairing...")); + m_gauge->SetValue(50); + break; + } + + case PairingState::Finished: + { + m_text->SetLabel(_("Successfully paired the controller.")); + m_gauge->SetValue(100); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::NoBluetoothAvailable: + { + m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::BluetoothFailed: + { + m_text->SetLabel(_("Failed to search for controllers.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::PairingFailed: + { + m_text->SetLabel(_("Failed to pair with the found controller.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::BluetoothUnusable: + { + m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + + default: + { + break; + } + } +} + +void PairingDialog::WorkerThread() +{ + const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; + const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; + +#if BOOST_OS_WINDOWS + const GUID bthHidGuid = {0x00001124,0x0000,0x1000,{0x80,0x00,0x00,0x80,0x5F,0x9B,0x34,0xFB}}; + + const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = + { + .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) + }; + + HANDLE radio = INVALID_HANDLE_VALUE; + HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); + if (radioFind == nullptr) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + BluetoothFindRadioClose(radioFind); + + BLUETOOTH_RADIO_INFO radioInfo = + { + .dwSize = sizeof(BLUETOOTH_RADIO_INFO) + }; + + DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); + if (result != ERROR_SUCCESS) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), + + .fReturnAuthenticated = FALSE, + .fReturnRemembered = FALSE, + .fReturnUnknown = TRUE, + .fReturnConnected = FALSE, + + .fIssueInquiry = TRUE, + .cTimeoutMultiplier = 5, + + .hRadio = radio + }; + + BLUETOOTH_DEVICE_INFO info = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_INFO) + }; + + while (!m_threadShouldQuit) + { + HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); + if (deviceFind == nullptr) + { + UpdateCallback(PairingState::BluetoothFailed); + return; + } + + while (!m_threadShouldQuit) + { + if (info.szName == wiimoteName || info.szName == wiiUProControllerName) + { + BluetoothFindDeviceClose(deviceFind); + + UpdateCallback(PairingState::Pairing); + + wchar_t passwd[6] = { radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5] }; + DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } + + bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } + + UpdateCallback(PairingState::Finished); + return; + } + + BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); + if (nextDevResult == FALSE) + { + break; + } + } + + BluetoothFindDeviceClose(deviceFind); + } +#else + UpdateCallback(PairingState::BluetoothUnusable); +#endif +} + +void PairingDialog::UpdateCallback(PairingState state) +{ + auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); + event->SetInt((int)state); + wxQueueEvent(this, event); +} \ No newline at end of file diff --git a/src/gui/PairingDialog.h b/src/gui/PairingDialog.h new file mode 100644 index 00000000..6c7612d1 --- /dev/null +++ b/src/gui/PairingDialog.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +class PairingDialog : public wxDialog +{ +public: + PairingDialog(wxWindow* parent); + ~PairingDialog(); + +private: + enum class PairingState + { + Pairing, + Finished, + NoBluetoothAvailable, + BluetoothFailed, + PairingFailed, + BluetoothUnusable + }; + + void OnClose(wxCloseEvent& event); + void OnCancelButton(const wxCommandEvent& event); + void OnGaugeUpdate(wxCommandEvent& event); + + void WorkerThread(); + void UpdateCallback(PairingState state); + + wxStaticText* m_text; + wxGauge* m_gauge; + wxButton* m_cancelButton; + + std::thread m_thread; + bool m_threadShouldQuit = false; +}; diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index 3eff5e97..a3551342 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -70,7 +70,7 @@ wxPanel* TitleManager::CreateTitleManagerPage() row->Add(m_refresh_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* help_button = new wxStaticBitmap(panel, wxID_ANY, wxBITMAP_PNG_FROM_DATA(PNG_HELP)); - help_button->SetToolTip(wxStringFormat2(_("The following prefixes are supported:\n{0}\n{1}\n{2}\n{3}\n{4}"), + help_button->SetToolTip(formatWxString(_("The following prefixes are supported:\n{0}\n{1}\n{2}\n{3}\n{4}"), "titleid:", "name:", "type:", "version:", "region:")); row->Add(help_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); @@ -328,7 +328,7 @@ void TitleManager::OnTitleSearchComplete(wxCommandEvent& event) } // update status bar text m_title_list->SortEntries(-1); - m_status_bar->SetStatusText(wxStringFormat2(_("Found {0} games, {1} updates, {2} DLCs and {3} save entries"), + m_status_bar->SetStatusText(formatWxString(_("Found {0} games, {1} updates, {2} DLCs and {3} save entries"), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Base) + m_title_list->GetCountByType(wxTitleManagerList::EntryType::System), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Update), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Dlc), @@ -494,7 +494,7 @@ void TitleManager::OnSaveDelete(wxCommandEvent& event) if (selection.IsEmpty()) return; - const auto msg = wxStringFormat2(_("Are you really sure that you want to delete the save entry for {}"), selection); + const auto msg = formatWxString(_("Are you really sure that you want to delete the save entry for {}"), selection); const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); if (result == wxNO) return; @@ -545,7 +545,7 @@ void TitleManager::OnSaveDelete(wxCommandEvent& event) fs::remove_all(target, ec); if (ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to delete the save directory:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to delete the save directory:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } @@ -622,7 +622,8 @@ void TitleManager::OnSaveExport(wxCommandEvent& event) const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); - wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), entry->path.string(), wxEmptyString, "Exported save entry (*.zip)|*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), entry->path.string(), wxEmptyString, + fmt::format("{}|*.zip", _("Exported save entry (*.zip)")), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().IsEmpty()) return; @@ -633,7 +634,7 @@ void TitleManager::OnSaveExport(wxCommandEvent& event) { zip_error_t ziperror; zip_error_init_with_code(&ziperror, ze); - const auto error_msg = wxStringFormat2(_("Error when creating the zip for the save entry:\n{}"), zip_error_strerror(&ziperror)); + const auto error_msg = formatWxString(_("Error when creating the zip for the save entry:\n{}"), zip_error_strerror(&ziperror)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -651,7 +652,7 @@ void TitleManager::OnSaveExport(wxCommandEvent& event) { if(zip_dir_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), ZIP_FL_ENC_UTF_8) < 0 ) { - const auto error_msg = wxStringFormat2(_("Error when trying to add a directory to the zip:\n{}"), zip_strerror(zip)); + const auto error_msg = formatWxString(_("Error when trying to add a directory to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } @@ -660,13 +661,13 @@ void TitleManager::OnSaveExport(wxCommandEvent& event) auto* source = zip_source_file(zip, (const char*)entryname.c_str(), 0, 0); if(!source) { - const auto error_msg = wxStringFormat2(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); + const auto error_msg = formatWxString(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } if (zip_file_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), source, ZIP_FL_ENC_UTF_8) < 0) { - const auto error_msg = wxStringFormat2(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); + const auto error_msg = formatWxString(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); zip_source_free(source); @@ -679,7 +680,7 @@ void TitleManager::OnSaveExport(wxCommandEvent& event) auto* metabuff = zip_source_buffer(zip, metacontent.data(), metacontent.size(), 0); if(zip_file_add(zip, "cemu_meta", metabuff, ZIP_FL_ENC_UTF_8) < 0) { - const auto error_msg = wxStringFormat2(_("Error when trying to add cemu_meta file to the zip:\n{}"), zip_strerror(zip)); + const auto error_msg = formatWxString(_("Error when trying to add cemu_meta file to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); zip_source_free(metabuff); @@ -730,11 +731,11 @@ void TitleManager::InitiateConnect() if (!NCrypto::SEEPROM_IsPresent()) { - SetDownloadStatusText("Dumped online files not found"); + SetDownloadStatusText(_("Dumped online files not found")); return; } - SetDownloadStatusText("Connecting..."); + SetDownloadStatusText(_("Connecting...")); // begin async connect dlMgr->setUserData(this); dlMgr->registerCallbacks( @@ -798,7 +799,7 @@ void TitleManager::SetConnected(bool state) void TitleManager::Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode) { TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); - titleManager->SetDownloadStatusText(statusText); + titleManager->SetDownloadStatusText(wxString::FromUTF8(statusText)); if (statusCode == DLMGR_STATUS_CODE::FAILED) { auto* evt = new wxCommandEvent(wxEVT_DL_DISCONNECT_COMPLETE); diff --git a/src/gui/TitleManager.h b/src/gui/TitleManager.h index 17a48976..2973618f 100644 --- a/src/gui/TitleManager.h +++ b/src/gui/TitleManager.h @@ -86,7 +86,6 @@ private: void OnDisconnect(wxCommandEvent& event); void OnDlFilterCheckbox(wxCommandEvent& event); - void OnDlCheckboxShowUpdates(wxCommandEvent& event); void SetConnected(bool state); diff --git a/src/gui/canvas/VulkanCanvas.cpp b/src/gui/canvas/VulkanCanvas.cpp index fed18279..649072f9 100644 --- a/src/gui/canvas/VulkanCanvas.cpp +++ b/src/gui/canvas/VulkanCanvas.cpp @@ -8,6 +8,7 @@ #endif #include +#include VulkanCanvas::VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) : IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxNO_FULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) @@ -37,8 +38,8 @@ VulkanCanvas::VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_wi } catch(const std::exception& ex) { - const auto msg = fmt::format(fmt::runtime(_("Error when initializing Vulkan renderer:\n{}").ToStdString()), ex.what()); - cemuLog_log(LogType::Force, msg); + cemuLog_log(LogType::Force, "Error when initializing Vulkan renderer: {}", ex.what()); + auto msg = formatWxString(_("Error when initializing Vulkan renderer:\n{}"), ex.what()); wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); dialog.ShowModal(); exit(0); @@ -66,6 +67,10 @@ void VulkanCanvas::OnPaint(wxPaintEvent& event) void VulkanCanvas::OnResize(wxSizeEvent& event) { + const wxSize size = GetSize(); + if (size.GetWidth() == 0 || size.GetHeight() == 0) + return; + #if BOOST_OS_LINUX && HAS_WAYLAND if(m_subsurface) { @@ -73,9 +78,6 @@ void VulkanCanvas::OnResize(wxSizeEvent& event) m_subsurface->setSize(sRect.GetX(), sRect.GetY(), sRect.GetWidth(), sRect.GetHeight()); } #endif - const wxSize size = GetSize(); - if (size.GetWidth() == 0 || size.GetHeight() == 0) - return; const wxRect refreshRect(size); RefreshRect(refreshRect, false); diff --git a/src/gui/components/wxDownloadManagerList.cpp b/src/gui/components/wxDownloadManagerList.cpp index b8676bc4..e1e347d8 100644 --- a/src/gui/components/wxDownloadManagerList.cpp +++ b/src/gui/components/wxDownloadManagerList.cpp @@ -432,13 +432,11 @@ wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemC switch (column) { case ColumnTitleId: - return wxStringFormat2("{:08x}-{:08x}", (uint32)(entry.titleId >> 32), (uint32)(entry.titleId & 0xFFFFFFFF)); + return formatWxString("{:08x}-{:08x}", (uint32) (entry.titleId >> 32), (uint32) (entry.titleId & 0xFFFFFFFF)); case ColumnName: - { return entry.name; - } case ColumnType: - return wxStringFormat2("{}", entry.type); + return GetTranslatedTitleEntryType(entry.type); case ColumnVersion: { // dont show version for base game unless it is not v0 @@ -446,7 +444,7 @@ wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemC return ""; if (entry.type == EntryType::DLC && entry.version == 0) return ""; - return wxStringFormat2("v{}", entry.version); + return formatWxString("v{}", entry.version); } case ColumnProgress: { @@ -454,11 +452,11 @@ wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemC { if (entry.progress >= 1000) return "100%"; - return wxStringFormat2("{:.1f}%", (float)entry.progress / 10.0f); // one decimal + return formatWxString("{:.1f}%", (float) entry.progress / 10.0f); // one decimal } else if (entry.status == TitleDownloadStatus::Installing || entry.status == TitleDownloadStatus::Checking || entry.status == TitleDownloadStatus::Verifying) { - return wxStringFormat2("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files + return formatWxString("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files } return ""; } @@ -503,6 +501,21 @@ wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemC return wxEmptyString; } +wxString wxDownloadManagerList::GetTranslatedTitleEntryType(EntryType type) +{ + switch (type) + { + case EntryType::Base: + return _("base"); + case EntryType::Update: + return _("update"); + case EntryType::DLC: + return _("DLC"); + default: + return std::to_string(static_cast>(type)); + } +} + void wxDownloadManagerList::AddOrUpdateTitle(TitleEntryData_t* obj) { const auto& data = obj->GetData(); diff --git a/src/gui/components/wxDownloadManagerList.h b/src/gui/components/wxDownloadManagerList.h index eed8c716..ae2c0d6a 100644 --- a/src/gui/components/wxDownloadManagerList.h +++ b/src/gui/components/wxDownloadManagerList.h @@ -150,25 +150,6 @@ private: bool SortFunc(std::span sortColumnOrder, const Type_t& v1, const Type_t& v2); static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); + static wxString GetTranslatedTitleEntryType(EntryType entryType); std::future m_context_worker; }; - -template <> -struct fmt::formatter : formatter -{ - using base = fmt::formatter; - template - auto format(const wxDownloadManagerList::EntryType& type, FormatContext& ctx) - { - switch (type) - { - case wxDownloadManagerList::EntryType::Base: - return base::format("base", ctx); - case wxDownloadManagerList::EntryType::Update: - return base::format("update", ctx); - case wxDownloadManagerList::EntryType::DLC: - return base::format("DLC", ctx); - } - return base::format(std::to_string(static_cast>(type)), ctx); - } -}; \ No newline at end of file diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 3b24bac6..8142107b 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -633,7 +633,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) if(dialog.ShowModal() == wxID_OK) { const auto custom_name = dialog.GetValue(); - GetConfig().SetGameListCustomName(title_id, wxHelper::MakeUTF8(custom_name)); + GetConfig().SetGameListCustomName(title_id, custom_name.utf8_string()); m_name_cache.clear(); g_config.Save(); // update list entry @@ -1009,20 +1009,25 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat)) { // time played - uint32 timePlayed = playTimeStat.numMinutesPlayed * 60; - if (timePlayed == 0) + uint32 minutesPlayed = playTimeStat.numMinutesPlayed; + if (minutesPlayed == 0) SetItem(index, ColumnGameTime, wxEmptyString); - else if (timePlayed < 60) - SetItem(index, ColumnGameTime, fmt::format("{} seconds", timePlayed)); - else if (timePlayed < 60 * 60) - SetItem(index, ColumnGameTime, fmt::format("{} minutes", timePlayed / 60)); + else if (minutesPlayed < 60) + SetItem(index, ColumnGameTime, formatWxString(wxPLURAL("{} minute", "{} minutes", minutesPlayed), minutesPlayed)); else - SetItem(index, ColumnGameTime, fmt::format("{} hours {} minutes", timePlayed / 3600, (timePlayed / 60) % 60)); + { + uint32 hours = minutesPlayed / 60; + uint32 minutes = minutesPlayed % 60; + wxString hoursText = formatWxString(wxPLURAL("{} hour", "{} hours", hours), hours); + wxString minutesText = formatWxString(wxPLURAL("{} minute", "{} minutes", minutes), minutes); + SetItem(index, ColumnGameTime, hoursText + " " + minutesText); + } + // last played if (playTimeStat.last_played.year != 0) { const wxDateTime tmp((wxDateTime::wxDateTime_t)playTimeStat.last_played.day, (wxDateTime::Month)playTimeStat.last_played.month, (wxDateTime::wxDateTime_t)playTimeStat.last_played.year, 0, 0, 0, 0); - SetItem(index, ColumnGameStarted, tmp.FormatISODate()); + SetItem(index, ColumnGameStarted, tmp.FormatDate()); } else SetItem(index, ColumnGameStarted, _("never")); @@ -1036,8 +1041,8 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) const auto region_text = fmt::format("{}", gameInfo.GetRegion()); - SetItem(index, ColumnRegion, _(region_text)); - SetItem(index, ColumnTitleID, _(fmt::format("{:016x}", titleId))); + SetItem(index, ColumnRegion, wxGetTranslation(region_text)); + SetItem(index, ColumnTitleID, fmt::format("{:016x}", titleId)); } else if (m_style == Style::kIcons) { @@ -1124,7 +1129,7 @@ void wxGameList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) void wxGameList::RemoveCache(const std::list& cachePaths, const std::string& titleName) { - wxMessageDialog dialog(this, fmt::format(fmt::runtime(_("Remove the shader caches for {}?").ToStdString()), titleName), _("Remove shader caches"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); + wxMessageDialog dialog(this, formatWxString(_("Remove the shader caches for {}?"), titleName), _("Remove shader caches"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); dialog.SetYesNoLabels(_("Yes"), _("No")); const auto dialogResult = dialog.ShowModal(); @@ -1139,7 +1144,7 @@ void wxGameList::RemoveCache(const std::list& cachePaths, const std::s if (errs.empty()) wxMessageDialog(this, _("The shader caches were removed!"), _("Shader caches removed"), wxCENTRE | wxOK | wxICON_INFORMATION).ShowModal(); else - wxMessageDialog(this, fmt::format(fmt::runtime(_("Failed to remove the shader caches:\n{}").ToStdString()), fmt::join(errs, "\n")), _("Error"), wxCENTRE | wxOK | wxICON_ERROR).ShowModal(); + wxMessageDialog(this, formatWxString(_("Failed to remove the shader caches:\n{}"), fmt::join(errs, "\n")), _("Error"), wxCENTRE | wxOK | wxICON_ERROR).ShowModal(); } void wxGameList::AsyncWorkerThread() @@ -1230,6 +1235,7 @@ 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; @@ -1265,13 +1271,13 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { // 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); + wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); } else { const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons"); if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){ - wxMessageBox("Cannot access the icon directory, the shortcut will have no icon", "Warning", wxOK | wxCENTRE | wxICON_WARNING); + 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()); @@ -1282,30 +1288,40 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { 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); + wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); } } } } - const auto desktop_entry_string = + + 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); + + // '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={}\n" - "Comment=Play {} on Cemu\n" - "Exec={} --title-id {:016x}\n" - "Icon={}\n" + "Name={0}\n" + "Comment=Play {0} on Cemu\n" + "Exec={1}\n" + "Icon={2}\n" "Terminal=false\n" "Type=Application\n" - "Categories=Game;", + "Categories=Game;\n", title_name, - title_name, - _pathToUtf8(exe_path), - title_id, + desktop_exec_entry, _pathToUtf8(icon_path.value_or(""))); + if (flatpak_id) + desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id); + std::ofstream output_stream(output_path); if (!output_stream.good()) { - const wxString errorMsg = fmt::format("Failed to save desktop entry to {}", output_path.utf8_string()); + auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return; } @@ -1339,4 +1355,4 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { } #endif } -#endif \ No newline at end of file +#endif diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index 1b3d2143..4472a4b8 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -242,7 +242,7 @@ boost::optional wxTitleManagerList::GetTi return {}; } -void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) +void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID) { TitleInfo titleInfo_base; TitleInfo titleInfo_update; @@ -269,22 +269,29 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) { if (!titleInfo_base.IsValid()) { - titleInfo_base = TitleInfo(data->entry.path); - } - else - { - // duplicate entry + titleInfo_base = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; // prefer the users selection } } + } + for (const auto& data : m_data) + { if (hasUpdateTitleId && data->entry.title_id == updateTitleId) { if (!titleInfo_update.IsValid()) { - titleInfo_update = TitleInfo(data->entry.path); + titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; } else { - // duplicate entry + // if multiple updates are present use the newest one + if (titleInfo_update.GetAppTitleVersion() < data->entry.version) + titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; } } } @@ -293,33 +300,35 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) { if (data->entry.title_id == aocTitleId) { - titleInfo_aoc = TitleInfo(data->entry.path); + titleInfo_aoc = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); + if(data->entry.location_uid == rightClickedUID) + break; } } - std::string msg = wxHelper::MakeUTF8(_("The following content will be converted to a compressed Wii U archive file (.wua):")); + wxString msg = _("The following content will be converted to a compressed Wii U archive file (.wua):"); msg.append("\n \n"); if (titleInfo_base.IsValid()) - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game: {}"))), _pathToUtf8(titleInfo_base.GetPath()))); + msg.append(formatWxString(_("Base game:\n{}"), titleInfo_base.GetPrintPath())); else - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Base game: Not installed"))))); + msg.append(_("Base game:\nNot installed")); - msg.append("\n"); + msg.append("\n\n"); if (titleInfo_update.IsValid()) - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update: {}"))), _pathToUtf8(titleInfo_update.GetPath()))); + msg.append(formatWxString(_("Update:\n{}"), titleInfo_update.GetPrintPath())); else - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("Update: Not installed"))))); + msg.append(_("Update:\nNot installed")); - msg.append("\n"); + msg.append("\n\n"); if (titleInfo_aoc.IsValid()) - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC: {}"))), _pathToUtf8(titleInfo_aoc.GetPath()))); + msg.append(formatWxString(_("DLC:\n{}"), titleInfo_aoc.GetPrintPath())); else - msg.append(fmt::format(fmt::runtime(wxHelper::MakeUTF8(_("DLC: Not installed"))))); + msg.append(_("DLC:\nNot installed")); - const int answer = wxMessageBox(wxString::FromUTF8(msg), _("Confirmation"), wxOK | wxCANCEL | wxCENTRE | wxICON_QUESTION, this); + const int answer = wxMessageBox(msg, _("Confirmation"), wxOK | wxCANCEL | wxCENTRE | wxICON_QUESTION, this); if (answer != wxOK) return; std::vector titlesToConvert; @@ -346,7 +355,7 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) boost::replace_all(shortName, ":", ""); } // for the default output directory we use the first game path configured by the user - std::wstring defaultDir = L""; + std::string defaultDir = ""; if (!GetConfig().game_paths.empty()) defaultDir = GetConfig().game_paths.front(); // get the short name, which we will use as a suggested default file name @@ -726,7 +735,7 @@ void wxTitleManagerList::OnItemSelected(wxListEvent& event) // return;; //} - //m_tooltip_text->SetLabel(wxStringFormat2("{}\n{}", msg, _("You can use the context menu to fix it."))); + //m_tooltip_text->SetLabel(formatWxString("{}\n{}", msg, _("You can use the context menu to fix it."))); //m_tooltip_window->Fit(); //m_tooltip_timer->StartOnce(250); } @@ -786,9 +795,9 @@ bool wxTitleManagerList::DeleteEntry(long index, const TitleEntry& entry) wxString msg; const bool is_directory = fs::is_directory(entry.path); if(is_directory) - msg = wxStringFormat2(_("Are you really sure that you want to delete the following folder:\n{}"), wxHelper::FromUtf8(_pathToUtf8(entry.path))); + msg = formatWxString(_("Are you really sure that you want to delete the following folder:\n{}"), _pathToUtf8(entry.path)); else - msg = wxStringFormat2(_("Are you really sure that you want to delete the following file:\n{}"), wxHelper::FromUtf8(_pathToUtf8(entry.path))); + msg = formatWxString(_("Are you really sure that you want to delete the following file:\n{}"), _pathToUtf8(entry.path)); const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); if (result == wxNO) @@ -829,7 +838,7 @@ bool wxTitleManagerList::DeleteEntry(long index, const TitleEntry& entry) if(ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to delete the entry:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to delete the entry:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK|wxCENTRE, this); return false; } @@ -884,7 +893,7 @@ void wxTitleManagerList::OnContextMenuSelected(wxCommandEvent& event) break; case kContextMenuConvertToWUA: - OnConvertToCompressedFormat(entry.value().title_id); + OnConvertToCompressedFormat(entry.value().title_id, entry.value().location_uid); break; } } @@ -916,15 +925,15 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu switch (column) { case ColumnTitleId: - return wxStringFormat2("{:08x}-{:08x}", (uint32)(entry.title_id >> 32), (uint32)(entry.title_id & 0xFFFFFFFF)); + return formatWxString("{:08x}-{:08x}", (uint32) (entry.title_id >> 32), (uint32) (entry.title_id & 0xFFFFFFFF)); case ColumnName: return entry.name; case ColumnType: - return wxStringFormat2("{}", entry.type); + return GetTranslatedTitleEntryType(entry.type); case ColumnVersion: - return wxStringFormat2("{}", entry.version); + return formatWxString("{}", entry.version); case ColumnRegion: - return wxStringFormat2("{}", entry.region); // TODO its a flag so formatter is currently not correct + return wxGetTranslation(fmt::format("{}", entry.region)); case ColumnFormat: { if (entry.type == EntryType::Save) @@ -935,17 +944,16 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu return _("Folder"); case wxTitleManagerList::EntryFormat::WUD: return _("WUD"); + case wxTitleManagerList::EntryFormat::NUS: + return _("NUS"); case wxTitleManagerList::EntryFormat::WUA: return _("WUA"); } return ""; - //return wxStringFormat2("{}", entry.format); } case ColumnLocation: { - const auto relative_mlc_path = - entry.path.lexically_relative(ActiveSettings::GetMlcPath()).string(); - + const auto relative_mlc_path = _pathToUtf8(entry.path.lexically_relative(ActiveSettings::GetMlcPath())); if (relative_mlc_path.starts_with("usr") || relative_mlc_path.starts_with("sys")) return _("MLC"); else @@ -958,6 +966,25 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu return wxEmptyString; } +wxString wxTitleManagerList::GetTranslatedTitleEntryType(EntryType type) +{ + switch (type) + { + case EntryType::Base: + return _("base"); + case EntryType::Update: + return _("update"); + case EntryType::Dlc: + return _("DLC"); + case EntryType::Save: + return _("save"); + case EntryType::System: + return _("system"); + default: + return std::to_string(static_cast>(type)); + } +} + void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) { if (evt->eventType != CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED && @@ -986,16 +1013,19 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt wxTitleManagerList::EntryFormat entryFormat; switch (titleInfo.GetFormat()) { - case TitleInfo::TitleDataFormat::HOST_FS: - default: - entryFormat = EntryFormat::Folder; - break; case TitleInfo::TitleDataFormat::WUD: entryFormat = EntryFormat::WUD; break; + case TitleInfo::TitleDataFormat::NUS: + entryFormat = EntryFormat::NUS; + break; case TitleInfo::TitleDataFormat::WIIU_ARCHIVE: entryFormat = EntryFormat::WUA; break; + case TitleInfo::TitleDataFormat::HOST_FS: + default: + entryFormat = EntryFormat::Folder; + break; } if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED) @@ -1005,7 +1035,8 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath()); ParsedMetaXml* metaInfo = titleInfo.GetMetaInfo(); - + if(titleInfo.IsSystemDataTitle()) + return; // dont show system data titles for now entry.location_uid = titleInfo.GetUID(); entry.title_id = titleInfo.GetAppTitleId(); std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue()); diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 5de63ec1..584f7414 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -42,6 +42,7 @@ public: { Folder, WUD, + NUS, WUA, }; @@ -106,9 +107,8 @@ private: [[nodiscard]] boost::optional GetTitleEntry(long item); [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path) const; [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path); - - bool VerifyEntryFiles(TitleEntry& entry); - void OnConvertToCompressedFormat(uint64 titleId); + + void OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID); bool DeleteEntry(long index, const TitleEntry& entry); void RemoveItem(long item); @@ -132,32 +132,9 @@ private: bool SortFunc(int column, const Type_t& v1, const Type_t& v2); static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); + static wxString GetTranslatedTitleEntryType(EntryType entryType); std::future m_context_worker; uint64 m_callbackIdTitleList; uint64 m_callbackIdSaveList; }; - -template <> -struct fmt::formatter : formatter -{ - using base = fmt::formatter; - template - auto format(const wxTitleManagerList::EntryType& type, FormatContext& ctx) - { - switch (type) - { - case wxTitleManagerList::EntryType::Base: - return base::format("base", ctx); - case wxTitleManagerList::EntryType::Update: - return base::format("update", ctx); - case wxTitleManagerList::EntryType::Dlc: - return base::format("DLC", ctx); - case wxTitleManagerList::EntryType::Save: - return base::format("save", ctx); - case wxTitleManagerList::EntryType::System: - return base::format("system", ctx); - } - return base::format(std::to_string(static_cast>(type)), ctx); - } -}; \ No newline at end of file diff --git a/src/gui/debugger/AudioDebuggerWindow.cpp b/src/gui/debugger/AudioDebuggerWindow.cpp index 7463f7dc..01761dca 100644 --- a/src/gui/debugger/AudioDebuggerWindow.cpp +++ b/src/gui/debugger/AudioDebuggerWindow.cpp @@ -33,93 +33,93 @@ AudioDebuggerWindow::AudioDebuggerWindow(wxFrame& parent) // add columns wxListItem col0; col0.SetId(0); - col0.SetText(wxT("idx")); + col0.SetText("idx"); col0.SetWidth(40); voiceListbox->InsertColumn(0, col0); wxListItem col1; col1.SetId(1); - col1.SetText(wxT("state")); + col1.SetText("state"); col1.SetWidth(48); voiceListbox->InsertColumn(1, col1); //wxListItem col2; // format col1.SetId(2); - col1.SetText(wxT("fmt")); + col1.SetText("fmt"); col1.SetWidth(52); voiceListbox->InsertColumn(2, col1); // sample base addr col1.SetId(3); - col1.SetText(wxT("base")); + col1.SetText("base"); col1.SetWidth(70); voiceListbox->InsertColumn(3, col1); // current offset col1.SetId(4); - col1.SetText(wxT("current")); + col1.SetText("current"); col1.SetWidth(70); voiceListbox->InsertColumn(4, col1); // loop offset col1.SetId(5); - col1.SetText(wxT("loop")); + col1.SetText("loop"); col1.SetWidth(70); voiceListbox->InsertColumn(5, col1); // end offset col1.SetId(6); - col1.SetText(wxT("end")); + col1.SetText("end"); col1.SetWidth(70); voiceListbox->InsertColumn(6, col1); // volume col1.SetId(7); - col1.SetText(wxT("vol")); + col1.SetText("vol"); col1.SetWidth(46); voiceListbox->InsertColumn(7, col1); // volume delta col1.SetId(8); - col1.SetText(wxT("volD")); + col1.SetText("volD"); col1.SetWidth(46); voiceListbox->InsertColumn(8, col1); // src col1.SetId(9); - col1.SetText(wxT("src")); + col1.SetText("src"); col1.SetWidth(70); voiceListbox->InsertColumn(9, col1); // low-pass filter coef a0 col1.SetId(10); - col1.SetText(wxT("lpa0")); + col1.SetText("lpa0"); col1.SetWidth(46); voiceListbox->InsertColumn(10, col1); // low-pass filter coef b0 col1.SetId(11); - col1.SetText(wxT("lpb0")); + col1.SetText("lpb0"); col1.SetWidth(46); voiceListbox->InsertColumn(11, col1); // biquad filter coef b0 col1.SetId(12); - col1.SetText(wxT("bqb0")); + col1.SetText("bqb0"); col1.SetWidth(46); voiceListbox->InsertColumn(12, col1); // biquad filter coef b0 col1.SetId(13); - col1.SetText(wxT("bqb1")); + col1.SetText("bqb1"); col1.SetWidth(46); voiceListbox->InsertColumn(13, col1); // biquad filter coef b0 col1.SetId(14); - col1.SetText(wxT("bqb2")); + col1.SetText("bqb2"); col1.SetWidth(46); voiceListbox->InsertColumn(14, col1); // biquad filter coef a0 col1.SetId(15); - col1.SetText(wxT("bqa1")); + col1.SetText("bqa1"); col1.SetWidth(46); voiceListbox->InsertColumn(15, col1); // biquad filter coef a1 col1.SetId(16); - col1.SetText(wxT("bqa2")); + col1.SetText("bqa2"); col1.SetWidth(46); voiceListbox->InsertColumn(16, col1); // device mix col1.SetId(17); - col1.SetText(wxT("deviceMix")); + col1.SetText("deviceMix"); col1.SetWidth(186); voiceListbox->InsertColumn(17, col1); diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp index 32bf3385..b53f612d 100644 --- a/src/gui/debugger/BreakpointWindow.cpp +++ b/src/gui/debugger/BreakpointWindow.cpp @@ -10,9 +10,11 @@ enum { - MENU_ID_CREATE_MEM_BP_READ = 1, + MENU_ID_CREATE_CODE_BP_EXECUTION = 1, + MENU_ID_CREATE_CODE_BP_LOGGING, + MENU_ID_CREATE_MEM_BP_READ, MENU_ID_CREATE_MEM_BP_WRITE, - + MENU_ID_DELETE_BP, }; enum ItemColumns @@ -117,6 +119,8 @@ void BreakpointWindow::OnUpdateView() const char* typeName = "UKN"; if (bp->bpType == DEBUGGER_BP_T_NORMAL) typeName = "X"; + else if (bp->bpType == DEBUGGER_BP_T_LOGGING) + typeName = "LOG"; else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT) typeName = "XS"; else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ) @@ -210,31 +214,56 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event) void BreakpointWindow::OnRightDown(wxMouseEvent& event) { - wxMenu menu; + const auto position = event.GetPosition(); + const sint32 index = (position.y / m_breakpoints->GetCharHeight()) - 2; + if (index < 0 || index >= m_breakpoints->GetItemCount()) + { + wxMenu menu; + menu.Append(MENU_ID_CREATE_CODE_BP_EXECUTION, _("Create execution breakpoint")); + menu.Append(MENU_ID_CREATE_CODE_BP_LOGGING, _("Create logging breakpoint")); + menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); + menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); - menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); - menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this); + PopupMenu(&menu); + } + else + { + m_breakpoints->SetItemState(index, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); + m_breakpoints->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this); - PopupMenu(&menu); + wxMenu menu; + menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint")); + + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClickSelected), nullptr, this); + PopupMenu(&menu); + } +} + +void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt) +{ + if (evt.GetId() == MENU_ID_DELETE_BP) + { + long sel = m_breakpoints->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (sel != -1) + { + if (sel >= debuggerState.breakpoints.size()) + return; + + auto it = debuggerState.breakpoints.begin(); + std::advance(it, sel); + + debugger_deleteBreakpoint(*it); + + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); + wxPostEvent(this->m_parent, evt); + } + } } void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt) { - switch (evt.GetId()) - { - case MENU_ID_CREATE_MEM_BP_READ: - MemoryBreakpointDialog(false); - return; - case MENU_ID_CREATE_MEM_BP_WRITE: - MemoryBreakpointDialog(true); - return; - } -} - -void BreakpointWindow::MemoryBreakpointDialog(bool isWrite) -{ - wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Memory breakpoint"), wxEmptyString); + wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Set breakpoint"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { ExpressionParser parser; @@ -242,22 +271,34 @@ void BreakpointWindow::MemoryBreakpointDialog(bool isWrite) auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); - + uint32_t newBreakpointAddress = 0; try { debugger_addParserSymbols(parser); - const auto result = (uint32)parser.Evaluate(value); - debug_printf("goto eval result: %x\n", result); - - debugger_createMemoryBreakpoint(result, isWrite == false, isWrite == true); - this->OnUpdateView(); + newBreakpointAddress = parser.IsConstantExpression("0x"+value) ? (uint32)parser.Evaluate("0x"+value) : (uint32)parser.Evaluate(value); } - catch (const std::exception& e) + catch (const std::exception& ex) { - //ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString)); - //return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR; - wxMessageBox(e.what(), "Invalid expression"); + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; } - + + switch (evt.GetId()) + { + case MENU_ID_CREATE_CODE_BP_EXECUTION: + debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_NORMAL); + break; + case MENU_ID_CREATE_CODE_BP_LOGGING: + debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_LOGGING); + break; + case MENU_ID_CREATE_MEM_BP_READ: + debugger_createMemoryBreakpoint(newBreakpointAddress, true, false); + break; + case MENU_ID_CREATE_MEM_BP_WRITE: + debugger_createMemoryBreakpoint(newBreakpointAddress, false, true); + break; + } + + this->OnUpdateView(); } -} \ No newline at end of file +} diff --git a/src/gui/debugger/BreakpointWindow.h b/src/gui/debugger/BreakpointWindow.h index add75ad6..7d941c6c 100644 --- a/src/gui/debugger/BreakpointWindow.h +++ b/src/gui/debugger/BreakpointWindow.h @@ -19,8 +19,7 @@ private: void OnRightDown(wxMouseEvent& event); void OnContextMenuClick(wxCommandEvent& evt); - - void MemoryBreakpointDialog(bool isWrite); + void OnContextMenuClickSelected(wxCommandEvent& evt); wxCheckedListCtrl* m_breakpoints; }; \ No newline at end of file diff --git a/src/gui/debugger/DebuggerWindow2.cpp b/src/gui/debugger/DebuggerWindow2.cpp index c9b968b0..93ad155f 100644 --- a/src/gui/debugger/DebuggerWindow2.cpp +++ b/src/gui/debugger/DebuggerWindow2.cpp @@ -116,7 +116,7 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser) const auto comment = element.get("Comment", ""); // calculate absolute address - uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data); + uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data; uint32 address = module_base_address + relative_address; // don't change anything if there's already a breakpoint @@ -125,7 +125,9 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser) // register breakpoints in debugger if (type == DEBUGGER_BP_T_NORMAL) - debugger_createExecuteBreakpoint(address); + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); + else if (type == DEBUGGER_BP_T_LOGGING) + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING); else if (type == DEBUGGER_BP_T_MEMORY_READ) debugger_createMemoryBreakpoint(address, true, false); else if (type == DEBUGGER_BP_T_MEMORY_WRITE) @@ -171,7 +173,7 @@ void DebuggerModuleStorage::Save(XMLConfigParser& parser) // check whether the breakpoint is part of the current module being saved RPLModule* address_module; - if (bp->bpType == DEBUGGER_BP_T_NORMAL) address_module = RPLLoader_FindModuleByCodeAddr(bp->address); + if (bp->bpType == DEBUGGER_BP_T_NORMAL || bp->bpType == DEBUGGER_BP_T_LOGGING) address_module = RPLLoader_FindModuleByCodeAddr(bp->address); else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address); else continue; @@ -257,7 +259,7 @@ void DebuggerWindow2::LoadModuleStorage(const RPLModule* module) bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr& debug) { return debug->GetFilename() == path; }); if (!path.empty() && !already_loaded) { - m_modules_storage.emplace_back(std::move(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))); + m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load(); } } @@ -552,6 +554,7 @@ void DebuggerWindow2::OnToolClicked(wxCommandEvent& event) void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event) { m_breakpoint_window->OnUpdateView(); + m_disasm_ctrl->RefreshControl(); UpdateModuleLabel(); } diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp index 884779d7..1aa8b16b 100644 --- a/src/gui/debugger/DisasmCtrl.cpp +++ b/src/gui/debugger/DisasmCtrl.cpp @@ -146,7 +146,7 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR else if (is_active_bp) background_colour = wxColour(0xFF80A0FF); else if (bp != nullptr) - background_colour = wxColour(0xFF8080FF); + background_colour = wxColour(bp->bpType == DEBUGGER_BP_T_NORMAL ? 0xFF8080FF : 0x80FFFFFF); else if(virtualAddress == m_lastGotoTarget) background_colour = wxColour(0xFFE0E0E0); else @@ -255,7 +255,7 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR { sint32 sImm = disasmInstr.operand[o].immS32; if (disasmInstr.operand[o].immWidth == 16 && (sImm & 0x8000)) - sImm |= 0xFFFF0000; + sImm |= (sint32)0xFFFF0000; if ((sImm > -10 && sImm < 10) || forceDecDisplay) string = wxString::Format("%d", sImm); @@ -539,8 +539,6 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) { debugger_toggleExecuteBreakpoint(*optVirtualAddress); - RefreshControl(); - wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); } @@ -766,40 +764,32 @@ void DisasmCtrl::GoToAddressDialog() auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); - const auto module_count = RPLLoader_GetModuleCount(); - const auto module_list = RPLLoader_GetModuleList(); + debugger_addParserSymbols(parser); - std::vector module_tmp(module_count); - for (int i = 0; i < module_count; i++) + // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) + if (parser.IsConstantExpression("0x"+value)) { - const auto module = module_list[i]; - if (module) - { - module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); - parser.AddConstant(module->moduleName2, module_tmp[i]); - } + const auto result = (uint32)parser.Evaluate("0x"+value); + m_lastGotoTarget = result; + CenterOffset(result); } - - double grp_tmp[32]; - PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot; - for (int i = 0; i < 32; i++) - { - char var_name[32]; - sprintf(var_name, "r%d", i); - grp_tmp[i] = ppc_snapshot.gpr[i]; - parser.AddConstant(var_name, grp_tmp[i]); - } - - try + else if (parser.IsConstantExpression(value)) { const auto result = (uint32)parser.Evaluate(value); - debug_printf("goto eval result: %x\n", result); m_lastGotoTarget = result; CenterOffset(result); debugger_getDebuggerCallbacks()->updateViewThreadsafe(); } - catch (const std::exception& ) + else { + try + { + const auto _ = (uint32)parser.Evaluate(value); + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } } } } \ No newline at end of file diff --git a/src/gui/debugger/DumpCtrl.cpp b/src/gui/debugger/DumpCtrl.cpp index c40ffaf5..4f3c9fa0 100644 --- a/src/gui/debugger/DumpCtrl.cpp +++ b/src/gui/debugger/DumpCtrl.cpp @@ -214,46 +214,36 @@ void DumpCtrl::GoToAddressDialog() wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { - try + ExpressionParser parser; + + auto value = goto_dialog.GetValue().ToStdString(); + std::transform(value.begin(), value.end(), value.begin(), tolower); + + debugger_addParserSymbols(parser); + + // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) + if (parser.IsConstantExpression("0x"+value)) { - ExpressionParser parser; - - auto value = goto_dialog.GetValue().ToStdString(); - std::transform(value.begin(), value.end(), value.begin(), tolower); - //parser.SetExpr(value); - - const auto module_count = RPLLoader_GetModuleCount(); - const auto module_list = RPLLoader_GetModuleList(); - - std::vector module_tmp(module_count); - for (int i = 0; i < module_count; i++) - { - const auto module = module_list[i]; - if (module) - { - module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); - parser.AddConstant(module->moduleName2, module_tmp[i]); - } - } - - double grp_tmp[32]; - PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot; - for (int i = 0; i < 32; i++) - { - char var_name[32]; - sprintf(var_name, "r%d", i); - grp_tmp[i] = ppc_snapshot.gpr[i]; - parser.AddConstant(var_name, grp_tmp[i]); - } - - const auto result = (uint32)parser.Evaluate(value); - debug_printf("goto eval result: %x\n", result); + const auto result = (uint32)parser.Evaluate("0x"+value); m_lastGotoOffset = result; CenterOffset(result); } - catch (const std::exception& ex) + else if (parser.IsConstantExpression(value)) { - wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + const auto result = (uint32)parser.Evaluate(value); + m_lastGotoOffset = result; + CenterOffset(result); + } + else + { + try + { + const auto _ = (uint32)parser.Evaluate(value); + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } } } } diff --git a/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp b/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp index a8a3cf67..2bea9a9c 100644 --- a/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp +++ b/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "util/helpers/helpers.h" wxCreateAccountDialog::wxCreateAccountDialog(wxWindow* parent) @@ -17,13 +18,13 @@ wxCreateAccountDialog::wxCreateAccountDialog(wxWindow* parent) main_sizer->SetFlexibleDirection(wxBOTH); main_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); - main_sizer->Add(new wxStaticText(this, wxID_ANY, wxT("PersistentId")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + main_sizer->Add(new wxStaticText(this, wxID_ANY, "PersistentId"), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_persistent_id = new wxTextCtrl(this, wxID_ANY, fmt::format("{:x}", Account::GetNextPersistentId())); m_persistent_id->SetToolTip(_("The persistent id is the internal folder name used for your saves. Only change this if you are importing saves from a Wii U with a specific id")); main_sizer->Add(m_persistent_id, 1, wxALL | wxEXPAND, 5); - main_sizer->Add(new wxStaticText(this, wxID_ANY, wxT("Mii name")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + main_sizer->Add(new wxStaticText(this, wxID_ANY, _("Mii name")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_mii_name = new wxTextCtrl(this, wxID_ANY); m_mii_name->SetFocus(); @@ -71,7 +72,7 @@ void wxCreateAccountDialog::OnOK(wxCommandEvent& event) const auto id = GetPersistentId(); if(id < Account::kMinPersistendId) { - wxMessageBox(fmt::format(fmt::runtime(_("The persistent id must be greater than {:x}!").ToStdString()), Account::kMinPersistendId), + wxMessageBox(formatWxString(_("The persistent id must be greater than {:x}!"), Account::kMinPersistendId), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } diff --git a/src/gui/dialogs/SaveImport/SaveImportWindow.cpp b/src/gui/dialogs/SaveImport/SaveImportWindow.cpp index 7cdf77b9..55db6ac2 100644 --- a/src/gui/dialogs/SaveImport/SaveImportWindow.cpp +++ b/src/gui/dialogs/SaveImport/SaveImportWindow.cpp @@ -30,8 +30,8 @@ SaveImportWindow::SaveImportWindow(wxWindow* parent, uint64 title_id) row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_source_selection = new wxFilePickerCtrl(this, wxID_ANY, wxEmptyString, - _("Select a zipped save file"), - wxStringFormat2("{}|*.zip", _("Save entry (*.zip)"))); + _("Select a zipped save file"), + formatWxString("{}|*.zip", _("Save entry (*.zip)"))); m_source_selection->SetMinSize({ 270, -1 }); row1->Add(m_source_selection, 1, wxALL | wxEXPAND, 5); @@ -118,7 +118,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) const uint64_t titleId = ConvertString(str.substr(sizeof("titleId = ") + 1), 16); if(titleId != 0 && titleId != m_title_id) { - const auto msg = wxStringFormat2(_("You are trying to import a savegame for a different title than your currently selected one: {:016x} vs {:016x}\nAre you sure that you want to continue?"), titleId, m_title_id); + const auto msg = formatWxString(_("You are trying to import a savegame for a different title than your currently selected one: {:016x} vs {:016x}\nAre you sure that you want to continue?"), titleId, m_title_id); const auto res = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); if(res == wxNO) { @@ -143,7 +143,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) //auto tmp_source = fs::temp_directory_path(ec); //if(ec) //{ - // const auto error_msg = wxStringFormat2(_("Error when getting the temp directory path:\n{}"), GetSystemErrorMessage(ec)); + // const auto error_msg = formatWxString(_("Error when getting the temp directory path:\n{}"), GetSystemErrorMessage(ec)); // wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); // return; //} @@ -158,7 +158,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) target_id = ConvertString(m_target_selection->GetValue().ToStdString(), 16); if (target_id < Account::kMinPersistendId) { - const auto msg = wxStringFormat2(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); + const auto msg = formatWxString(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -170,7 +170,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) { if (!fs::is_directory(target_path)) { - const auto msg = wxStringFormat2(_("There's already a file at the target directory:\n{}"), _pathToUtf8(target_path)); + const auto msg = formatWxString(_("There's already a file at the target directory:\n{}"), _pathToUtf8(target_path)); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); m_return_code = wxCANCEL; Close(); @@ -193,7 +193,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) if (ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } @@ -213,7 +213,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) fs::create_directories(tmp_source, ec); if (ec) { - const auto error_msg = wxStringFormat2(_("Error when creating the extraction path:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when creating the extraction path:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -221,7 +221,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr); if (!zip) { - const auto error_msg = wxStringFormat2(_("Error when opening the import zip file:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when opening the import zip file:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -319,7 +319,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) fs::rename(tmp_source, target_path, ec); if (ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to move the extracted save game:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to move the extracted save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; }*/ diff --git a/src/gui/dialogs/SaveImport/SaveTransfer.cpp b/src/gui/dialogs/SaveImport/SaveTransfer.cpp index f26daa8a..ef503989 100644 --- a/src/gui/dialogs/SaveImport/SaveTransfer.cpp +++ b/src/gui/dialogs/SaveImport/SaveTransfer.cpp @@ -92,7 +92,7 @@ void SaveTransfer::OnTransfer(wxCommandEvent& event) target_id = ConvertString(m_target_selection->GetValue().ToStdString(), 16); if(target_id < Account::kMinPersistendId) { - const auto msg = wxStringFormat2(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); + const auto msg = formatWxString(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } @@ -108,7 +108,7 @@ void SaveTransfer::OnTransfer(wxCommandEvent& event) { if(!fs::is_directory(target_path)) { - const auto msg = wxStringFormat2(_("There's already a file at the target directory:\n{}"), _pathToUtf8(target_path)); + const auto msg = formatWxString(_("There's already a file at the target directory:\n{}"), _pathToUtf8(target_path)); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); m_return_code = wxCANCEL; Close(); @@ -131,7 +131,7 @@ void SaveTransfer::OnTransfer(wxCommandEvent& event) if (ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } @@ -187,7 +187,7 @@ void SaveTransfer::OnTransfer(wxCommandEvent& event) fs::rename(source_path, target_path, ec); if (ec) { - const auto error_msg = wxStringFormat2(_("Error when trying to move the save game:\n{}"), GetSystemErrorMessage(ec)); + const auto error_msg = formatWxString(_("Error when trying to move the save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } diff --git a/src/gui/helpers/wxHelpers.h b/src/gui/helpers/wxHelpers.h index ab15a411..a69988a3 100644 --- a/src/gui/helpers/wxHelpers.h +++ b/src/gui/helpers/wxHelpers.h @@ -47,16 +47,9 @@ public: }; template -wxString wxStringFormat2(const wxString& format, TArgs&&...args) +wxString formatWxString(const wxString& format, TArgs&&...args) { - // ignores locale? - return fmt::format(fmt::runtime(format.ToStdString()), std::forward(args)...); -} - -template -wxString wxStringFormat2W(const wxString& format, TArgs&&...args) -{ - return fmt::format(fmt::runtime(format.ToStdWstring()), std::forward(args)...); + return wxString::FromUTF8(fmt::format(fmt::runtime(format.utf8_string()), std::forward(args)...)); } // executes a function when destroying the obj @@ -88,14 +81,6 @@ inline wxString to_wxString(std::string_view str) return wxString::FromUTF8(str.data(), str.size()); } -// creates utf8 std::string from wxString -inline std::string from_wxString(const wxString& str) -{ - const auto tmp = str.ToUTF8(); - return std::string{ tmp.data(), tmp.length() }; -} - - template T get_next_sibling(const T element) { diff --git a/src/gui/input/InputAPIAddWindow.cpp b/src/gui/input/InputAPIAddWindow.cpp index 8b9bea68..29ec5436 100644 --- a/src/gui/input/InputAPIAddWindow.cpp +++ b/src/gui/input/InputAPIAddWindow.cpp @@ -23,7 +23,7 @@ using wxControllerData = wxCustomData; InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector& controllers) - : wxDialog(parent, wxID_ANY, _("Add input API"), position, wxDefaultSize, 0), m_controllers(controllers) + : wxDialog(parent, wxID_ANY, "Add input API", position, wxDefaultSize, 0), m_controllers(controllers) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); @@ -90,11 +90,11 @@ InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position, auto* row = new wxBoxSizer(wxHORIZONTAL); // we only have dsu settings atm, so add elements now row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("IP")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - m_ip = new wxTextCtrl(m_settings_panel, wxID_ANY, wxT("127.0.0.1")); + m_ip = new wxTextCtrl(m_settings_panel, wxID_ANY, "127.0.0.1"); row->Add(m_ip, 0, wxALL, 5); row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("Port")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - m_port = new wxTextCtrl(m_settings_panel, wxID_ANY, wxT("26760")); + m_port = new wxTextCtrl(m_settings_panel, wxID_ANY, "26760"); row->Add(m_port, 0, wxALL, 5); panel_sizer->Add(row, 0, wxEXPAND); diff --git a/src/gui/input/InputSettings2.cpp b/src/gui/input/InputSettings2.cpp index 9d19581a..6426561d 100644 --- a/src/gui/input/InputSettings2.cpp +++ b/src/gui/input/InputSettings2.cpp @@ -79,7 +79,7 @@ InputSettings2::InputSettings2(wxWindow* parent) { auto* page = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); page->SetClientObject(nullptr); // force internal type to client object - m_notebook->AddPage(page, wxStringFormat2(_("Controller {}"), i + 1)); + m_notebook->AddPage(page, formatWxString(_("Controller {}"), i + 1)); } m_notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &InputSettings2::on_controller_page_changed, this); @@ -238,11 +238,11 @@ wxWindow* InputSettings2::initialize_page(size_t index) // add/remove buttons auto* bttn_sizer = new wxBoxSizer(wxHORIZONTAL); - auto* add_api = new wxButton(page, wxID_ANY, wxT(" + "), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto* add_api = new wxButton(page, wxID_ANY, " + ", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); add_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_add, this); bttn_sizer->Add(add_api, 0, wxALL, 5); - auto* remove_api = new wxButton(page, wxID_ANY, wxT(" - "), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto* remove_api = new wxButton(page, wxID_ANY, " - ", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); remove_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_remove, this); bttn_sizer->Add(remove_api, 0, wxALL, 5); @@ -585,9 +585,7 @@ void InputSettings2::on_profile_text_changed(wxCommandEvent& event) // load_bttn, save_bttn, delete_bttn, profile_status const auto text = event.GetString(); - const auto text_str = from_wxString(text); - - const bool valid_name = InputManager::is_valid_profilename(text_str); + const bool valid_name = InputManager::is_valid_profilename(text.utf8_string()); const bool name_exists = profile_names->FindString(text) != wxNOT_FOUND; page_data.m_profile_load->Enable(name_exists); @@ -603,7 +601,7 @@ void InputSettings2::on_profile_load(wxCommandEvent& event) auto* profile_names = page_data.m_profiles; auto* text = page_data.m_profile_status; - const auto selection = from_wxString(profile_names->GetValue()); + const auto selection = profile_names->GetValue().utf8_string(); text->Show(); if (selection.empty() || !InputManager::is_valid_profilename(selection)) { @@ -639,7 +637,7 @@ void InputSettings2::on_profile_save(wxCommandEvent& event) auto* profile_names = page_data.m_profiles; auto* text = page_data.m_profile_status; - const auto selection = from_wxString(profile_names->GetValue()); + const auto selection = profile_names->GetValue().utf8_string(); text->Show(); if (selection.empty() || !InputManager::is_valid_profilename(selection)) { @@ -670,7 +668,7 @@ void InputSettings2::on_profile_delete(wxCommandEvent& event) auto* profile_names = page_data.m_profiles; auto* text = page_data.m_profile_status; - const auto selection = from_wxString(profile_names->GetStringSelection()); + const auto selection = profile_names->GetStringSelection().utf8_string(); text->Show(); if (selection.empty() || !InputManager::is_valid_profilename(selection)) @@ -725,10 +723,9 @@ void InputSettings2::on_emulated_controller_selected(wxCommandEvent& event) } else { - const auto type_str = from_wxString(event.GetString()); try { - const auto type = EmulatedController::type_from_string(type_str); + const auto type = EmulatedController::type_from_string(event.GetString().utf8_string()); // same has already been selected if (page_data.m_controller && page_data.m_controller->type() == type) return; @@ -976,7 +973,7 @@ void InputSettings2::on_controller_settings(wxCommandEvent& event) case InputAPI::Keyboard: break; - #if BOOST_OS_WINDOWS + #ifdef SUPPORTS_WIIMOTE case InputAPI::Wiimote: { const auto wiimote = std::dynamic_pointer_cast(controller); wxASSERT(wiimote); diff --git a/src/gui/input/panels/InputPanel.cpp b/src/gui/input/panels/InputPanel.cpp index 96aa785e..adac9bc0 100644 --- a/src/gui/input/panels/InputPanel.cpp +++ b/src/gui/input/panels/InputPanel.cpp @@ -26,7 +26,7 @@ void InputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, cons const auto mapping = reinterpret_cast(element->GetClientData()); // reset mapping - if(std::exchange(m_right_down, false) || wxGetKeyState(WXK_ESCAPE)) + if(std::exchange(m_right_down, false)) { element->SetBackgroundColour(kKeyColourNormalMode); m_color_backup[element->GetId()] = kKeyColourNormalMode; diff --git a/src/gui/input/panels/WiimoteInputPanel.cpp b/src/gui/input/panels/WiimoteInputPanel.cpp index 1dffff60..36c4599b 100644 --- a/src/gui/input/panels/WiimoteInputPanel.cpp +++ b/src/gui/input/panels/WiimoteInputPanel.cpp @@ -1,5 +1,6 @@ #include "input/panels/WiimoteInputPanel.h" +#include #include #include #include @@ -11,6 +12,7 @@ #include "input/emulated/WiimoteController.h" #include "helpers/wxHelpers.h" #include "components/wxInputDraw.h" +#include "gui/PairingDialog.h" constexpr WiimoteController::ButtonId g_kFirstColumnItems[] = { @@ -36,10 +38,18 @@ WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) bold_font.MakeBold(); auto* main_sizer = new wxBoxSizer(wxVERTICAL); + auto* horiz_main_sizer = new wxBoxSizer(wxHORIZONTAL); - auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); - extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:"))); - extensions_sizer->AddSpacer(10); + auto* pair_button = new wxButton(this, wxID_ANY, _("Pair a Wii or Wii U controller")); + pair_button->Bind(wxEVT_BUTTON, &WiimoteInputPanel::on_pair_button, this); + horiz_main_sizer->Add(pair_button); + horiz_main_sizer->AddSpacer(10); + + auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); + horiz_main_sizer->Add(extensions_sizer, wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL)); + + extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:"))); + extensions_sizer->AddSpacer(10); m_motion_plus = new wxCheckBox(this, wxID_ANY, _("MotionPlus")); m_motion_plus->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); @@ -54,7 +64,7 @@ WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) m_classic->Hide(); extensions_sizer->Add(m_classic); - main_sizer->Add(extensions_sizer, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(horiz_main_sizer, 0, wxEXPAND | wxALL, 5); main_sizer->Add(new wxStaticLine(this), 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 5); m_item_sizer = new wxGridBagSizer(); @@ -254,3 +264,9 @@ void WiimoteInputPanel::load_controller(const EmulatedControllerPtr& emulated_co set_active_device_type(wiimote->get_device_type()); } } + +void WiimoteInputPanel::on_pair_button(wxCommandEvent& event) +{ + PairingDialog pairing_dialog(this); + pairing_dialog.ShowModal(); +} diff --git a/src/gui/input/panels/WiimoteInputPanel.h b/src/gui/input/panels/WiimoteInputPanel.h index cd8f4ba4..91a53e19 100644 --- a/src/gui/input/panels/WiimoteInputPanel.h +++ b/src/gui/input/panels/WiimoteInputPanel.h @@ -25,6 +25,7 @@ private: void on_volume_change(wxCommandEvent& event); void on_extension_change(wxCommandEvent& event); + void on_pair_button(wxCommandEvent& event); wxGridBagSizer* m_item_sizer; diff --git a/src/gui/input/settings/WiimoteControllerSettings.cpp b/src/gui/input/settings/WiimoteControllerSettings.cpp index c8e2f82f..92d2215c 100644 --- a/src/gui/input/settings/WiimoteControllerSettings.cpp +++ b/src/gui/input/settings/WiimoteControllerSettings.cpp @@ -14,7 +14,7 @@ #include "components/wxInputDraw.h" #include "input/InputAPIAddWindow.h" -#if BOOST_OS_WINDOWS +#ifdef SUPPORTS_WIIMOTE WiimoteControllerSettings::WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller) : wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize, diff --git a/src/gui/input/settings/WiimoteControllerSettings.h b/src/gui/input/settings/WiimoteControllerSettings.h index b519b9e5..d5214efe 100644 --- a/src/gui/input/settings/WiimoteControllerSettings.h +++ b/src/gui/input/settings/WiimoteControllerSettings.h @@ -1,6 +1,6 @@ #pragma once -#if BOOST_OS_WINDOWS +#ifdef SUPPORTS_WIIMOTE #include #include diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index 6cc3ecc5..3e8108fa 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -8,6 +8,7 @@ #include "gui/components/wxProgressDialogManager.h" #include +#include enum { @@ -280,9 +281,7 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp); void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread) { - cemuLog_log(LogType::Force, fmt::format("Dumping stack trace for thread {0:08x} LR: {1:08x}", - memory_getVirtualOffsetFromPointer(thread), - _swapEndianU32(thread->context.lr))); + cemuLog_log(LogType::Force, "Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr)); DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1])); } @@ -335,7 +334,7 @@ void DebugPPCThreadsWindow::PresentProfileResults(OSThread_t* thread, const std: void DebugPPCThreadsWindow::ProfileThreadWorker(OSThread_t* thread) { wxProgressDialogManager progressDialog(this); - progressDialog.Create("Profiling thread", + progressDialog.Create(_("Profiling thread"), _("Capturing samples..."), 1000, // range wxPD_CAN_SKIP); @@ -366,8 +365,7 @@ void DebugPPCThreadsWindow::ProfileThreadWorker(OSThread_t* thread) totalSampleCount++; if ((totalSampleCount % 50) == 0) { - wxString msg = fmt::format("Capturing samples... ({:})\nResults will be written to log.txt\n", - totalSampleCount); + wxString msg = formatWxString(_("Capturing samples... ({:})\nResults will be written to log.txt\n"), totalSampleCount); if (totalSampleCount < 30000) msg.Append(_("Click Skip button for early results with lower accuracy")); else diff --git a/src/gui/wxHelper.h b/src/gui/wxHelper.h index ac959755..468651ac 100644 --- a/src/gui/wxHelper.h +++ b/src/gui/wxHelper.h @@ -3,13 +3,6 @@ namespace wxHelper { - // wxString to utf8 std::string - inline std::string MakeUTF8(const wxString& str) - { - auto tmpUtf8 = str.ToUTF8(); - return std::string(tmpUtf8.data(), tmpUtf8.length()); - } - inline fs::path MakeFSPath(const wxString& str) { auto tmpUtf8 = str.ToUTF8(); diff --git a/src/gui/wxgui.h b/src/gui/wxgui.h index b2f5fdb2..4d80f5a5 100644 --- a/src/gui/wxgui.h +++ b/src/gui/wxgui.h @@ -1,5 +1,7 @@ #pragma once +#define wxNO_UNSAFE_WXSTRING_CONV 1 + #include #ifndef WX_PRECOMP #include @@ -34,66 +36,6 @@ #include #include -// wx helper functions -#include -struct wxStringFormatParameters -{ - sint32 parameter_index; - sint32 parameter_count; - - wchar_t* token_buffer; - wchar_t* substitude_parameter; -}; - -template -wxString wxStringFormat(std::wstring& format, wxStringFormatParameters& parameters) -{ - return format; -} - -template -wxString wxStringFormat(std::wstring& format, wxStringFormatParameters& parameters, T arg, Args... args) -{ - wchar_t tmp[64]; - swprintf(tmp, 64, LR"(\{[%d]+\})", parameters.parameter_index); - const std::wregex placeholder_regex(tmp); - - auto result = format; - while (std::regex_search(result, placeholder_regex)) - { - result = std::regex_replace(result, placeholder_regex, parameters.substitude_parameter, std::regex_constants::format_first_only); - result = wxString::Format(wxString(result), arg); - } - - parameters.parameter_index++; - if (parameters.parameter_index == parameters.parameter_count) - return result; - - parameters.substitude_parameter = std::wcstok(nullptr, LR"( )", ¶meters.token_buffer); - return wxStringFormat(result, parameters, args...); -} - -template -wxString wxStringFormat(const wxString& format, const wchar_t* parameters, T... args) -{ - const auto parameter_count = std::count(parameters, parameters + wcslen(parameters), '%'); - if (parameter_count == 0) - return format; - - const auto copy = wcsdup(parameters); - - wxStringFormatParameters para; - para.substitude_parameter = std::wcstok(copy, LR"( )", ¶.token_buffer); - para.parameter_count = parameter_count; - para.parameter_index = 0; - - auto tmp_string = format.ToStdWstring(); - auto result = wxStringFormat(tmp_string, para, args...); - - free(copy); - - return result; -} inline bool SendSliderEvent(wxSlider* slider, int new_value) { diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 1ee2fcc3..b6a502ec 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -40,18 +40,6 @@ add_library(CemuInput set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") if(WIN32) - # Native wiimote (Win32 only for now) - target_sources(CemuInput PRIVATE - api/Wiimote/WiimoteControllerProvider.h - api/Wiimote/windows/WinWiimoteDevice.cpp - api/Wiimote/windows/WinWiimoteDevice.h - api/Wiimote/WiimoteControllerProvider.cpp - api/Wiimote/WiimoteMessages.h - api/Wiimote/NativeWiimoteController.h - api/Wiimote/NativeWiimoteController.cpp - api/Wiimote/WiimoteDevice.h - ) - # XInput target_sources(CemuInput PRIVATE api/XInput/XInputControllerProvider.cpp @@ -69,6 +57,21 @@ if(WIN32) ) endif() +if (ENABLE_WIIMOTE) + target_compile_definitions(CemuInput PUBLIC SUPPORTS_WIIMOTE) + target_sources(CemuInput PRIVATE + api/Wiimote/WiimoteControllerProvider.h + api/Wiimote/WiimoteControllerProvider.cpp + api/Wiimote/WiimoteMessages.h + api/Wiimote/NativeWiimoteController.h + api/Wiimote/NativeWiimoteController.cpp + api/Wiimote/WiimoteDevice.h + api/Wiimote/hidapi/HidapiWiimote.cpp + api/Wiimote/hidapi/HidapiWiimote.h + ) +endif () + + if(ANDROID) target_sources(CemuInput PRIVATE api/Android/AndroidControllerProvider.cpp @@ -98,6 +101,9 @@ target_link_libraries(CemuInput PRIVATE glm::glm pugixml::pugixml ) +if (ENABLE_HIDAPI) + target_link_libraries(CemuInput PRIVATE hidapi::hidapi) +endif() if(ENABLE_SDL) target_link_libraries(CemuInput PRIVATE SDL2::SDL2) diff --git a/src/input/InputManager.h b/src/input/InputManager.h index 91a2e80e..97f82ac9 100644 --- a/src/input/InputManager.h +++ b/src/input/InputManager.h @@ -3,6 +3,9 @@ #if BOOST_OS_WINDOWS #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/XInput/XInputControllerProvider.h" +#endif + +#ifdef SUPPORTS_WIIMOTE #include "input/api/Wiimote/WiimoteControllerProvider.h" #endif diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index a742a9d3..55f28c01 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -2,11 +2,10 @@ #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" -#if BOOST_OS_WINDOWS -#include "input/api/Wiimote/windows/WinWiimoteDevice.h" -#endif +#include "input/api/Wiimote/hidapi/HidapiWiimote.h" #include +#include WiimoteControllerProvider::WiimoteControllerProvider() : m_running(true) @@ -28,20 +27,39 @@ WiimoteControllerProvider::~WiimoteControllerProvider() std::vector> WiimoteControllerProvider::get_controllers() { std::scoped_lock lock(m_device_mutex); - for (const auto& device : WiimoteDevice_t::get_devices()) + + std::queue disconnected_wiimote_indices; + for (auto i{0u}; i < m_wiimotes.size(); ++i){ + if (!(m_wiimotes[i].connected = m_wiimotes[i].device->write_data({kStatusRequest, 0x00}))){ + disconnected_wiimote_indices.push(i); + } + } + + const auto valid_new_device = [&](std::shared_ptr & device) { + const auto writeable = device->write_data({kStatusRequest, 0x00}); + const auto not_already_connected = + std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), + [device](const auto& it) { + return (*it.device == *device) && it.connected; + }); + return writeable && not_already_connected; + }; + + for (auto& device : WiimoteDevice_t::get_devices()) { - // test connection of all devices as they might have been changed - const bool is_connected = device->write_data({kStatusRequest, 0x00}); - if (is_connected) - { - // only add unknown, connected devices to our list - const bool is_new_device = std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), - [&device](const auto& it) { return *it.device == *device; }); - if (is_new_device) - { - m_wiimotes.push_back(std::make_unique(device)); - } - } + if (!valid_new_device(device)) + continue; + // Replace disconnected wiimotes + if (!disconnected_wiimote_indices.empty()){ + const auto idx = disconnected_wiimote_indices.front(); + disconnected_wiimote_indices.pop(); + + m_wiimotes.replace(idx, std::make_unique(device)); + } + // Otherwise add them + else { + m_wiimotes.push_back(std::make_unique(device)); + } } std::vector> result; @@ -163,9 +181,7 @@ void WiimoteControllerProvider::reader_thread() { case kStatus: { -#ifdef WIIMOTE_DEBUG - printf("WiimoteControllerProvider::read_thread: kStatus\n"); -#endif + cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kStatus"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; new_state.flags = *data; @@ -183,9 +199,7 @@ void WiimoteControllerProvider::reader_thread() if (HAS_FLAG(new_state.flags, kExtensionConnected)) { -#ifdef WIIMOTE_DEBUG - printf("\tExtension flag is set\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension flag is set"); if(new_state.m_extension.index() == 0) request_extension(index); } @@ -199,9 +213,7 @@ void WiimoteControllerProvider::reader_thread() break; case kRead: { -#ifdef WIIMOTE_DEBUG - printf("WiimoteControllerProvider::read_thread: kRead\n"); -#endif + cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kRead"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; const uint8 error_flag = *data & 0xF, size = (*data >> 4) + 1; @@ -209,10 +221,9 @@ void WiimoteControllerProvider::reader_thread() if (error_flag) { + // 7 means that wiimote is already enabled or not available -#ifdef WIIMOTE_DEBUG - printf("Received error on data read 0x%x\n", error_flag); -#endif + cemuLog_logDebug(LogType::Force,"Received error on data read {:#x}", error_flag); continue; } @@ -220,9 +231,7 @@ void WiimoteControllerProvider::reader_thread() data += 2; if (address == (kRegisterCalibration & 0xFFFF)) { -#ifdef WIIMOTE_DEBUG - printf("Calibration received\n"); -#endif + cemuLog_logDebug(LogType::Force,"Calibration received"); cemu_assert(size == 8); @@ -255,17 +264,10 @@ void WiimoteControllerProvider::reader_thread() { if (size == 0xf) { -#ifdef WIIMOTE_DEBUG - printf("Extension type received but no extension connected\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension type received but no extension connected"); continue; } - -#ifdef WIIMOTE_DEBUG - printf("Extension type received\n"); -#endif - cemu_assert(size == 6); auto be_type = *(betype*)data; data += 6; // 48 @@ -274,42 +276,38 @@ void WiimoteControllerProvider::reader_thread() switch (be_type.value()) { case kExtensionNunchuck: -#ifdef WIIMOTE_DEBUG - printf("\tNunchuck\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: Nunchuck"); new_state.m_extension = NunchuckData{}; break; case kExtensionClassic: -#ifdef WIIMOTE_DEBUG - printf("\tClassic\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic"); new_state.m_extension = ClassicData{}; break; case kExtensionClassicPro: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic Pro"); + break; case kExtensionGuitar: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Guitar"); + break; case kExtensionDrums: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Drums"); + break; case kExtensionBalanceBoard: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Balance Board"); + break; case kExtensionMotionPlus: - //m_motion_plus = true; -#ifdef WIIMOTE_DEBUG - printf("\tMotion plus detected\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: MotionPlus"); set_motion_plus(index, true); new_state.m_motion_plus = MotionPlusData{}; break; case kExtensionPartialyInserted: -#ifdef WIIMOTE_DEBUG - printf("\tExtension only partially inserted!\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension only partially inserted"); new_state.m_extension = {}; request_status(index); break; default: - new_state.m_extension = {}; + cemuLog_logDebug(LogType::Force,"Unknown extension: {:#x}", be_type.value()); + new_state.m_extension = {}; break; } @@ -319,9 +317,7 @@ void WiimoteControllerProvider::reader_thread() else if (address == (kRegisterExtensionCalibration & 0xFFFF)) { cemu_assert(size == 0x10); -#ifdef WIIMOTE_DEBUG - printf("Extension calibration received\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension calibration received"); std::visit( overloaded { @@ -337,9 +333,7 @@ void WiimoteControllerProvider::reader_thread() std::array zero{}; if (memcmp(zero.data(), data, zero.size()) == 0) { -#ifdef WIIMOTE_DEBUG - printf("\tExtension calibration data is zero!\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension calibration data is zero"); return; } @@ -372,15 +366,23 @@ void WiimoteControllerProvider::reader_thread() } else { -#ifdef WIIMOTE_DEBUG - printf("Unhandled read data received\n"); -#endif - continue; + cemuLog_logDebug(LogType::Force,"Unhandled read data received"); + continue; } update_report = true; } break; + case kAcknowledge: + { + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + const auto report_id = *data++; + const auto error = *data++; + if (error) + cemuLog_logDebug(LogType::Force, "Error {:#x} from output report {:#x}", error, report_id); + break; + } case kDataCore: { // 30 BB BB @@ -476,10 +478,7 @@ void WiimoteControllerProvider::reader_thread() orientation /= tmp;*/ mp.orientation = orientation; -#ifdef WIIMOTE_DEBUG - printf("\tmp: %.2lf %.2lf %.2lf\n", mp.orientation.x, mp.orientation.y, - mp.orientation.z); -#endif + cemuLog_logDebug(LogType::Force,"MotionPlus: {:.2f}, {:.2f} {:.2f}", mp.orientation.x, mp.orientation.y, mp.orientation.z); }, [data](NunchuckData& nunchuck) mutable { @@ -553,12 +552,11 @@ void WiimoteControllerProvider::reader_thread() zero3, zero4 ); -#ifdef WIIMOTE_DEBUG - printf("\tn: %d,%d | %lf - %lf | %.2lf %.2lf %.2lf\n", nunchuck.z, nunchuck.c, - nunchuck.axis.x, nunchuck.axis.y, - RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), - RadToDeg(nunchuck.acceleration.z)); -#endif + cemuLog_logDebug(LogType::Force,"Nunchuck: Z={}, C={} | {}, {} | {:.2f}, {:.2f}, {:.2f}", + nunchuck.z, nunchuck.c, + nunchuck.axis.x, nunchuck.axis.y, + RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), + RadToDeg(nunchuck.acceleration.z)); }, [data](ClassicData& classic) mutable { @@ -592,11 +590,11 @@ void WiimoteControllerProvider::reader_thread() classic.trigger = classic.raw_trigger; classic.trigger /= 31.0f; -#ifdef WIIMOTE_DEBUG - printf("\tc: %d | %lf - %lf | %lf - %lf | %lf - %lf\n", classic.buttons, - classic.left_axis.x, classic.left_axis.y, classic.right_axis.x, - classic.right_axis.y, classic.trigger.x, classic.trigger.y); -#endif + cemuLog_logDebug(LogType::Force,"Classic Controller: Buttons={:b} | {}, {} | {}, {} | {}, {}", + classic.buttons, classic.left_axis.x, classic.left_axis.y, + classic.right_axis.x, classic.right_axis.y, classic.trigger.x, + classic.trigger.y); + } }, new_state.m_extension); @@ -609,9 +607,7 @@ void WiimoteControllerProvider::reader_thread() break; } default: -#ifdef WIIMOTE_DEBUG - printf("unhandled input packet id %d for wiimote\n", data[0]); -#endif + cemuLog_logDebug(LogType::Force,"unhandled input packet id {} for wiimote {}", id, index); } // update motion data @@ -694,7 +690,6 @@ void WiimoteControllerProvider::parse_acceleration(WiimoteState& wiimote_state, tmp -= calib.zero; acceleration = (wiimote_state.m_acceleration / tmp); - //printf("%d, %d, %d\n", (int)m_acceleration.x, (int)m_acceleration.y, (int)m_acceleration.z); const float pi_2 = (float)std::numbers::pi / 2.0f; wiimote_state.m_roll = std::atan2(acceleration.z, acceleration.x) - pi_2; } @@ -713,7 +708,6 @@ void WiimoteControllerProvider::rotate_ir(WiimoteState& wiimote_state) i++; if (!dot.visible) continue; - //printf("%d:\t%.02lf | %.02lf\n", i, dot.pos.x, dot.pos.y); // move to center, rotate and move back dot.pos -= 0.5f; dot.pos.x = (dot.pos.x * cos) + (dot.pos.y * (-sin)); @@ -973,9 +967,7 @@ void WiimoteControllerProvider::update_report_type(size_t index) else report_type = kDataCore; -#ifdef WIIMOTE_DEBUG - printf("Setting report type to %d\n", report_type); -#endif + cemuLog_logDebug(LogType::Force,"Setting report type to {}", report_type); send_packet(index, {kType, 0x04, report_type}); state.ir_camera.mode = set_ir_camera(index, true); diff --git a/src/input/api/Wiimote/WiimoteMessages.h b/src/input/api/Wiimote/WiimoteMessages.h index 712c3a5c..32dd4658 100644 --- a/src/input/api/Wiimote/WiimoteMessages.h +++ b/src/input/api/Wiimote/WiimoteMessages.h @@ -8,7 +8,7 @@ enum InputReportId : uint8 kStatus = 0x20, kRead = 0x21, - kWrite = 0x22, + kAcknowledge = 0x22, kDataCore = 0x30, kDataCoreAcc = 0x31, diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp new file mode 100644 index 00000000..db185675 --- /dev/null +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -0,0 +1,56 @@ +#include "HidapiWiimote.h" +#include + +static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e; +static constexpr uint16 WIIMOTE_PRODUCT_ID = 0x0306; +static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330; +static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22; +static constexpr auto PRO_CONTROLLER_NAME = L"Nintendo RVL-CNT-01-UC"; + +HidapiWiimote::HidapiWiimote(hid_device* dev, std::string_view path) + : m_handle(dev), m_path(path) { + +} + +bool HidapiWiimote::write_data(const std::vector &data) { + return hid_write(m_handle, data.data(), data.size()) >= 0; +} + +std::optional> HidapiWiimote::read_data() { + std::array read_data{}; + const auto result = hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH); + if (result < 0) + return {}; + return {{read_data.cbegin(), read_data.cbegin() + result}}; +} + +std::vector HidapiWiimote::get_devices() { + std::vector wiimote_devices; + hid_init(); + const auto device_enumeration = hid_enumerate(WIIMOTE_VENDOR_ID, 0x0); + + for (auto it = device_enumeration; it != nullptr; it = it->next){ + if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID) + continue; + if (std::wcscmp(it->product_string, PRO_CONTROLLER_NAME) == 0) + continue; + auto dev = hid_open_path(it->path); + if (!dev){ + cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr))); + } + else { + hid_set_nonblocking(dev, true); + wiimote_devices.push_back(std::make_shared(dev, it->path)); + } + } + hid_free_enumeration(device_enumeration); + return wiimote_devices; +} + +bool HidapiWiimote::operator==(WiimoteDevice& o) const { + return static_cast(o).m_path == m_path; +} + +HidapiWiimote::~HidapiWiimote() { + hid_close(m_handle); +} diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h new file mode 100644 index 00000000..858cb1f3 --- /dev/null +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class HidapiWiimote : public WiimoteDevice { +public: + HidapiWiimote(hid_device* dev, std::string_view path); + ~HidapiWiimote() override; + + bool write_data(const std::vector &data) override; + std::optional> read_data() override; + bool operator==(WiimoteDevice& o) const override; + + static std::vector get_devices(); + +private: + hid_device* m_handle; + const std::string m_path; + +}; + +using WiimoteDevice_t = HidapiWiimote; \ No newline at end of file diff --git a/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp b/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp deleted file mode 100644 index 546a9615..00000000 --- a/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "input/api/Wiimote/windows/WinWiimoteDevice.h" - -#include -#include - -WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector identifier) - : m_handle(handle), m_identifier(std::move(identifier)) -{ - m_overlapped.hEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr); -} - -WinWiimoteDevice::~WinWiimoteDevice() -{ - CancelIo(m_handle); - ResetEvent(m_overlapped.hEvent); - CloseHandle(m_handle); -} - -bool WinWiimoteDevice::write_data(const std::vector& data) -{ - return HidD_SetOutputReport(m_handle, (void*)data.data(), (ULONG)data.size()); -} - -std::optional> WinWiimoteDevice::read_data() -{ - DWORD read = 0; - std::array buffer{}; - - if (!ReadFile(m_handle, buffer.data(), (DWORD)buffer.size(), &read, &m_overlapped)) - { - const auto error = GetLastError(); - if (error == ERROR_DEVICE_NOT_CONNECTED) - return {}; - else if (error == ERROR_IO_PENDING) - { - const auto wait_result = WaitForSingleObject(m_overlapped.hEvent, 100); - if (wait_result == WAIT_TIMEOUT) - { - CancelIo(m_handle); - ResetEvent(m_overlapped.hEvent); - return {}; - } - else if (wait_result == WAIT_FAILED) - return {}; - - if (GetOverlappedResult(m_handle, &m_overlapped, &read, FALSE) == FALSE) - return {}; - } - else if (error == ERROR_INVALID_HANDLE) - { - ResetEvent(m_overlapped.hEvent); - return {}; - } - else - { - cemu_assert_debug(false); - } - } - - ResetEvent(m_overlapped.hEvent); - if (read == 0) - return {}; - - return {{buffer.cbegin(), buffer.cbegin() + read}}; -} - -std::vector WinWiimoteDevice::get_devices() -{ - std::vector result; - - GUID hid_guid; - HidD_GetHidGuid(&hid_guid); - - const auto device_info = SetupDiGetClassDevs(&hid_guid, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)); - - for (DWORD index = 0; ; ++index) - { - SP_DEVICE_INTERFACE_DATA device_data{}; - device_data.cbSize = sizeof(device_data); - if (SetupDiEnumDeviceInterfaces(device_info, nullptr, &hid_guid, index, &device_data) == FALSE) - break; - - DWORD device_data_len; - if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, nullptr, 0, &device_data_len, nullptr) == FALSE - && GetLastError() != ERROR_INSUFFICIENT_BUFFER) - continue; - - std::vector detail_data_buffer; - detail_data_buffer.resize(device_data_len); - - const auto detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)detail_data_buffer.data(); - detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - - if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, device_data_len, nullptr, nullptr) - == FALSE) - continue; - - HANDLE device_handle = CreateFile(detail_data->DevicePath, (GENERIC_READ | GENERIC_WRITE), - (FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, nullptr); - if (device_handle == INVALID_HANDLE_VALUE) - continue; - - HIDD_ATTRIBUTES attributes{}; - attributes.Size = sizeof(attributes); - if (HidD_GetAttributes(device_handle, &attributes) == FALSE) - { - CloseHandle(device_handle); - continue; - } - - if (attributes.VendorID != 0x057e || (attributes.ProductID != 0x0306 && attributes.ProductID != 0x0330)) - { - CloseHandle(device_handle); - continue; - } - - result.emplace_back(std::make_shared(device_handle, detail_data_buffer)); - } - - return result; -} - -bool WinWiimoteDevice::operator==(WiimoteDevice& o) const -{ - return m_identifier == static_cast(o).m_identifier; -} diff --git a/src/input/api/Wiimote/windows/WinWiimoteDevice.h b/src/input/api/Wiimote/windows/WinWiimoteDevice.h deleted file mode 100644 index 077882db..00000000 --- a/src/input/api/Wiimote/windows/WinWiimoteDevice.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "input/api/Wiimote/WiimoteDevice.h" - -class WinWiimoteDevice : public WiimoteDevice -{ -public: - WinWiimoteDevice(HANDLE handle, std::vector identifier); - ~WinWiimoteDevice(); - - bool write_data(const std::vector& data) override; - std::optional> read_data() override; - - static std::vector get_devices(); - - bool operator==(WiimoteDevice& o) const override; - -private: - HANDLE m_handle; - OVERLAPPED m_overlapped{}; - std::vector m_identifier; -}; - -using WiimoteDevice_t = WinWiimoteDevice; diff --git a/src/input/emulated/EmulatedController.cpp b/src/input/emulated/EmulatedController.cpp index 8712bcf0..e254db34 100644 --- a/src/input/emulated/EmulatedController.cpp +++ b/src/input/emulated/EmulatedController.cpp @@ -2,7 +2,7 @@ #include "input/api/Controller.h" -#if BOOST_OS_WINDOWS +#ifdef SUPPORTS_WIIMOTE #include "input/api/Wiimote/NativeWiimoteController.h" #endif @@ -131,15 +131,15 @@ bool EmulatedController::has_second_motion() const if(controller->use_motion()) { // if wiimote has nunchuck connected, we use its acceleration - #if BOOST_OS_WINDOWS - if(controller->api() == InputAPI::Wiimote) + #if SUPPORTS_WIIMOTE + if(controller->api() == InputAPI::Wiimote) { if(((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck) { return true; } } - #endif + #endif motion++; } } @@ -156,7 +156,7 @@ MotionSample EmulatedController::get_second_motion_data() const if (controller->use_motion()) { // if wiimote has nunchuck connected, we use its acceleration - #if BOOST_OS_WINDOWS + #ifdef SUPPORTS_WIIMOTE if (controller->api() == InputAPI::Wiimote) { if (((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck) @@ -211,12 +211,11 @@ void EmulatedController::add_controller(std::shared_ptr controll { controller->connect(); - #if BOOST_OS_WINDOWS - if (const auto wiimote = std::dynamic_pointer_cast(controller)) { + #ifdef SUPPORTS_WIIMOTE + if (const auto wiimote = std::dynamic_pointer_cast(controller)) { wiimote->set_player_index(m_player_index); } - #endif - + #endif std::scoped_lock lock(m_mutex); m_controllers.emplace_back(std::move(controller)); } diff --git a/src/main.cpp b/src/main.cpp index 8a423daf..5d51c887 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,5 @@ #include "util/crypto/aes128.h" #include "Cafe/OS/RPL/rpl.h" -#include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" @@ -57,66 +56,6 @@ std::atomic_bool g_isGPUInitFinished = false; std::wstring executablePath; -void logCPUAndMemoryInfo() -{ - std::string cpuName = g_CPUFeatures.GetCPUName(); - if (!cpuName.empty()) - cemuLog_log(LogType::Force, "CPU: {}", cpuName); - - #if BOOST_OS_WINDOWS - MEMORYSTATUSEX statex; - statex.dwLength = sizeof(statex); - GlobalMemoryStatusEx(&statex); - uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); - cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB); - #elif BOOST_OS_LINUX - struct sysinfo info {}; - sysinfo(&info); - cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast(info.totalram) * info.mem_unit) / 1024LL / 1024LL)); - #elif BOOST_OS_MACOS - int64_t totalRam; - size_t size = sizeof(totalRam); - int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0); - if (result == 0) - cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); - #endif -} - -bool g_running_in_wine = false; -bool IsRunningInWine() -{ - return g_running_in_wine; -} - -void checkForWine() -{ - #if BOOST_OS_WINDOWS - const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); - if (!hmodule) - return; - - const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); - if (pwine_get_version) - { - g_running_in_wine = true; - cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version()); - } - #else - g_running_in_wine = false; - #endif -} - -void infoLog_cemuStartup() -{ - cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); - cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); - cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); - checkForWine(); - // CPU and RAM info - logCPUAndMemoryInfo(); - cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); -} - // some implementations of _putenv dont copy the string and instead only store a pointer // thus we use a helper to keep a permanent copy std::vector sPutEnvMap; @@ -186,22 +125,18 @@ void CemuCommonInit() g_config.Load(); if (NetworkConfig::XMLExists()) n_config.Load(); - // symbol storage - rplSymbolStorage_init(); // parallelize expensive init code std::future futureInitAudioAPI = std::async(std::launch::async, []{ IAudioAPI::InitializeStatic(); IAudioInputAPI::InitializeStatic(); return 0; }); std::future futureInitGraphicPacks = std::async(std::launch::async, []{ GraphicPack2::LoadAll(); return 0; }); InputManager::instance().load(); futureInitAudioAPI.wait(); futureInitGraphicPacks.wait(); - // log Cemu startup info - infoLog_cemuStartup(); // init Cafe system CafeSystem::Initialize(); // init title list CafeTitleList::Initialize(ActiveSettings::GetUserDataPath("title_list_cache.xml")); for (auto& it : GetConfig().game_paths) - CafeTitleList::AddScanPath(it); + CafeTitleList::AddScanPath(_utf8ToPath(it)); fs::path mlcPath = ActiveSettings::GetMlcPath(); if (!mlcPath.empty()) CafeTitleList::SetMLCPath(mlcPath); diff --git a/src/resource/MacOSXBundleInfo.plist.in b/src/resource/MacOSXBundleInfo.plist.in index c181b388..98064735 100644 --- a/src/resource/MacOSXBundleInfo.plist.in +++ b/src/resource/MacOSXBundleInfo.plist.in @@ -26,7 +26,22 @@ NSHumanReadableCopyright ${MACOSX_BUNDLE_COPYRIGHT} - LSApplicationCategoryType - ${MACOSX_BUNDLE_CATEGORY} + LSApplicationCategoryType + ${MACOSX_BUNDLE_CATEGORY} + LSMinimumSystemVersion + ${MACOSX_MINIMUM_SYSTEM_VERSION} + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + ${MACOSX_BUNDLE_TYPE_EXTENSION} + + CFBundleTypeName + Wii U File + CFBundleTypeRole + Viewer + + - \ No newline at end of file + diff --git a/src/util/ChunkedHeap/ChunkedHeap.h b/src/util/ChunkedHeap/ChunkedHeap.h index 8e458d40..abc45429 100644 --- a/src/util/ChunkedHeap/ChunkedHeap.h +++ b/src/util/ChunkedHeap/ChunkedHeap.h @@ -489,6 +489,11 @@ private: bool _alloc(uint32 size, uint32 alignment, uint32& allocOffsetOut) { + if(size == 0) + { + size = 1; // zero-sized allocations are not supported + cemu_assert_suspicious(); + } // find smallest bucket to scan uint32 alignmentM1 = alignment - 1; uint32 bucketIndex = ulog2(size); @@ -521,7 +526,10 @@ private: { auto it = map_allocatedRange.find(addrOffset); if (it == map_allocatedRange.end()) - assert_dbg(); + { + cemuLog_log(LogType::Force, "VHeap internal error"); + cemu_assert(false); + } allocRange_t* range = it->second; map_allocatedRange.erase(it); m_statsMemAllocated -= range->size; diff --git a/src/util/Fiber/FiberUnix.cpp b/src/util/Fiber/FiberUnix.cpp index 7d3bf05a..0d527069 100644 --- a/src/util/Fiber/FiberUnix.cpp +++ b/src/util/Fiber/FiberUnix.cpp @@ -1,5 +1,6 @@ #include "Fiber.h" #include +#include thread_local Fiber* sCurrentFiber{}; @@ -44,7 +45,9 @@ void Fiber::Switch(Fiber& targetFiber) { Fiber* leavingFiber = sCurrentFiber; sCurrentFiber = &targetFiber; + std::atomic_thread_fence(std::memory_order_seq_cst); swapcontext((ucontext_t*)(leavingFiber->m_implData), (ucontext_t*)(targetFiber.m_implData)); + std::atomic_thread_fence(std::memory_order_seq_cst); } void* Fiber::GetFiberPrivateData() diff --git a/src/util/helpers/StringHelpers.h b/src/util/helpers/StringHelpers.h index 24e70d49..54141808 100644 --- a/src/util/helpers/StringHelpers.h +++ b/src/util/helpers/StringHelpers.h @@ -2,6 +2,7 @@ #include "boost/nowide/convert.hpp" #include +// todo - move the Cafe/PPC specific parts to CafeString.h eventually namespace StringHelpers { // convert Wii U big-endian wchar_t string to utf8 string diff --git a/src/util/highresolutiontimer/HighResolutionTimer.h b/src/util/highresolutiontimer/HighResolutionTimer.h index 12dc0751..7a545c86 100644 --- a/src/util/highresolutiontimer/HighResolutionTimer.h +++ b/src/util/highresolutiontimer/HighResolutionTimer.h @@ -36,6 +36,16 @@ public: static HighResolutionTimer now(); static HRTick getFrequency(); + static HRTick microsecondsToTicks(uint64 microseconds) + { + return microseconds * m_freq / 1000000; + } + + static uint64 ticksToMicroseconds(HRTick ticks) + { + return ticks * 1000000 / m_freq; + } + private: HighResolutionTimer(uint64 timePoint) : m_timePoint(timePoint) {}; diff --git a/vcpkg.json b/vcpkg.json index 8bec5d9e..d794b76e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "cemu", "version-string": "1.0", - "builtin-baseline": "b81bc3a83fdbdffe80325eeabb2ec735a1f3c29d", + "builtin-baseline": "53bef8994c541b6561884a8395ea35715ece75db", "dependencies": [ "pugixml", "zlib", @@ -41,6 +41,10 @@ }, "boost-random", "fmt", + { + "name": "hidapi", + "platform": "!android" + }, "libpng", "glm", { @@ -58,6 +62,10 @@ "name": "curl", "default-features": false, "features": [ "openssl" ] + }, + { + "name": "libusb", + "platform": "!android" } ] }