diff --git a/.clang-format b/.clang-format index b22a1048..0cef9ae4 100644 --- a/.clang-format +++ b/.clang-format @@ -15,6 +15,7 @@ BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: true + AfterClass: true AfterControlStatement: Always AfterEnum: true AfterExternBlock: true diff --git a/.github/getversion.cpp b/.github/getversion.cpp deleted file mode 100644 index 469a796e..00000000 --- a/.github/getversion.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include "./../src/Common/version.h" - -// output current Cemu version for CI workflow. Do not modify -int main() -{ - printf("%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR); - return 0; -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2342c27..72bbcf52 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,10 +3,10 @@ name: Build Cemu on: workflow_call: inputs: - deploymode: + next_version_major: required: false type: string - experimentalversion: + next_version_minor: required: false type: string @@ -24,30 +24,17 @@ jobs: submodules: "recursive" fetch-depth: 0 - - name: "Fetch full history for vcpkg submodule" - run: | - cd dependencies/vcpkg - git fetch --unshallow - - - name: Setup release mode parameters (for deploy) - if: ${{ inputs.deploymode == 'release' }} + - name: Setup release mode parameters run: | echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "Build mode is release" - - name: Setup debug mode parameters (for continous build) - if: ${{ inputs.deploymode != 'release' }} + - name: Setup build flags for version + if: ${{ inputs.next_version_major != '' }} run: | - echo "BUILD_MODE=debug" >> $GITHUB_ENV - echo "BUILD_FLAGS=" >> $GITHUB_ENV - echo "Build mode is debug" - - - name: Setup version for experimental - if: ${{ inputs.experimentalversion != '' }} - run: | - echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" - echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV + echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" + echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV - name: "Install system dependencies" run: | @@ -86,12 +73,10 @@ jobs: cmake --build build - name: Prepare artifact - if: ${{ inputs.deploymode == 'release' }} run: mv bin/Cemu_release bin/Cemu - name: Upload artifact uses: actions/upload-artifact@v4 - if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-linux-x64 path: ./bin/Cemu @@ -133,29 +118,17 @@ jobs: with: submodules: "recursive" - - name: "Fetch full history for vcpkg submodule" - run: | - cd dependencies/vcpkg - git fetch --unshallow - - - name: Setup release mode parameters (for deploy) - if: ${{ inputs.deploymode == 'release' }} + - name: Setup release mode parameters run: | echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "Build mode is release" - - - name: Setup debug mode parameters (for continous build) - if: ${{ inputs.deploymode != 'release' }} + + - name: Setup build flags for version + if: ${{ inputs.next_version_major != '' }} run: | - echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - echo "Build mode is debug" - - name: Setup version for experimental - if: ${{ inputs.experimentalversion != '' }} - run: | - echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" - echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" + echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 @@ -194,52 +167,45 @@ jobs: cmake --build . --config ${{ env.BUILD_MODE }} - name: Prepare artifact - if: ${{ inputs.deploymode == 'release' }} run: Rename-Item bin/Cemu_release.exe Cemu.exe - name: Upload artifact uses: actions/upload-artifact@v4 - if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-windows-x64 path: ./bin/Cemu.exe build-macos: - runs-on: macos-12 + runs-on: macos-14 steps: - name: "Checkout repo" uses: actions/checkout@v4 with: submodules: "recursive" - - name: "Fetch full history for vcpkg submodule" - run: | - cd dependencies/vcpkg - git fetch --unshallow - - - name: Setup release mode parameters (for deploy) - if: ${{ inputs.deploymode == 'release' }} + - name: Setup release mode parameters run: | echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "Build mode is release" - - name: Setup debug mode parameters (for continous build) - if: ${{ inputs.deploymode != 'release' }} + + - name: Setup build flags for version + if: ${{ inputs.next_version_major != '' }} run: | - echo "BUILD_MODE=debug" >> $GITHUB_ENV - echo "BUILD_FLAGS=" >> $GITHUB_ENV - echo "Build mode is debug" - - - name: Setup version for experimental - if: ${{ inputs.experimentalversion != '' }} - run: | - echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" - echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV + echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" + echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV - name: "Install system dependencies" run: | brew update - brew install llvm@15 ninja nasm molten-vk automake libtool + brew install ninja nasm automake libtool + + - name: "Install molten-vk" + run: | + curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar + tar xf MoltenVK-macos.tar + sudo mkdir -p /usr/local/lib + sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 @@ -270,9 +236,8 @@ jobs: cd build cmake .. ${{ env.BUILD_FLAGS }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ + -DCMAKE_OSX_ARCHITECTURES=x86_64 \ -DMACOS_BUNDLE=ON \ - -DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \ - -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \ -G Ninja - name: "Build Cemu" @@ -280,7 +245,6 @@ jobs: cmake --build build - name: Prepare artifact - if: ${{ inputs.deploymode == 'release' }} run: | mkdir bin/Cemu_app mv bin/Cemu_release.app bin/Cemu_app/Cemu.app @@ -294,7 +258,6 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 - if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-macos-x64 path: ./bin/Cemu.dmg diff --git a/.github/workflows/build_check.yml b/.github/workflows/build_check.yml index 49ef79e9..5d24b0c6 100644 --- a/.github/workflows/build_check.yml +++ b/.github/workflows/build_check.yml @@ -16,6 +16,3 @@ on: jobs: build: uses: ./.github/workflows/build.yml - with: - deploymode: release - experimentalversion: 999999 diff --git a/.github/workflows/deploy_experimental_release.yml b/.github/workflows/deploy_experimental_release.yml index a8c5ec53..97e0c69e 100644 --- a/.github/workflows/deploy_experimental_release.yml +++ b/.github/workflows/deploy_experimental_release.yml @@ -1,20 +1,83 @@ name: Deploy experimental release on: workflow_dispatch: + inputs: + changelog0: + description: 'Enter the changelog lines for this release. Each line is a feature / bullet point. Do not use dash.' + required: true + type: string + changelog1: + description: 'Feature 2' + required: false + type: string + changelog2: + description: 'Feature 3' + required: false + type: string + changelog3: + description: 'Feature 4' + required: false + type: string + changelog4: + description: 'Feature 5' + required: false + type: string + changelog5: + description: 'Feature 6' + required: false + type: string + changelog6: + description: 'Feature 7' + required: false + type: string + changelog7: + description: 'Feature 8' + required: false + type: string + changelog8: + description: 'Feature 9' + required: false + type: string + changelog9: + description: 'Feature 10' + required: false + type: string jobs: + calculate-version: + name: Calculate Version + uses: ./.github/workflows/determine_release_version.yml call-release-build: uses: ./.github/workflows/build.yml + needs: calculate-version with: - deploymode: release - experimentalversion: ${{ github.run_number }} + next_version_major: ${{ needs.calculate-version.outputs.next_version_major }} + next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }} deploy: name: Deploy experimental release runs-on: ubuntu-22.04 - needs: call-release-build + needs: [call-release-build, calculate-version] steps: - - uses: actions/checkout@v3 - + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + id: generate_changelog + run: | + CHANGELOG="" + if [ -n "${{ github.event.inputs.changelog0 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog0 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog1 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog1 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog2 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog2 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog3 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog3 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog4 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog4 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog5 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog5 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog6 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog6 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog7 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog7 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog8 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog8 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog9 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog9 }}\n"; fi + echo -e "$CHANGELOG" + echo "RELEASE_BODY=$CHANGELOG" >> $GITHUB_ENV - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 @@ -40,15 +103,13 @@ jobs: mkdir upload sudo apt install zip - - name: Get version + - name: Set version dependent vars run: | - echo "Experimental version: ${{ github.run_number }}" - ls - gcc -o getversion .github/getversion.cpp - ./getversion - echo "Cemu CI version: $(./getversion)" - echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV - echo "CEMU_VERSION=$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV + echo "Version: ${{ needs.calculate-version.outputs.next_version }}" + echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" + echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" + echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV + echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV - name: Create release from windows-bin run: | @@ -83,4 +144,8 @@ jobs: wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz tar xvzf ghr.tar.gz; rm ghr.tar.gz echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}" - ghr_v0.15.0_linux_amd64/ghr -prerelease -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }} (Experimental)" -b "Cemu experimental release" "v${{ env.CEMU_VERSION }}" ./upload + CHANGELOG_UNESCAPED=$(printf "%s\n" "${{ env.RELEASE_BODY }}" | sed 's/\\n/\n/g') + RELEASE_BODY=$(printf "%s\n%s" \ + "**Changelog:**" \ + "$CHANGELOG_UNESCAPED") + ghr_v0.15.0_linux_amd64/ghr -draft -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "$RELEASE_BODY" "v${{ env.CEMU_VERSION }}" ./upload diff --git a/.github/workflows/determine_release_version.yml b/.github/workflows/determine_release_version.yml new file mode 100644 index 00000000..be606941 --- /dev/null +++ b/.github/workflows/determine_release_version.yml @@ -0,0 +1,74 @@ +name: Calculate Next Version from release history + +on: + workflow_dispatch: + workflow_call: + outputs: + next_version: + description: "The next semantic version" + value: ${{ jobs.calculate-version.outputs.next_version }} + next_version_major: + description: "The next semantic version (major)" + value: ${{ jobs.calculate-version.outputs.next_version_major }} + next_version_minor: + description: "The next semantic version (minor)" + value: ${{ jobs.calculate-version.outputs.next_version_minor }} + +jobs: + calculate-version: + runs-on: ubuntu-latest + outputs: + next_version: ${{ steps.calculate_next_version.outputs.next_version }} + next_version_major: ${{ steps.calculate_next_version.outputs.next_version_major }} + next_version_minor: ${{ steps.calculate_next_version.outputs.next_version_minor }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Get all releases + id: get_all_releases + run: | + # Fetch all releases and check for API errors + RESPONSE=$(curl -s -o response.json -w "%{http_code}" "https://api.github.com/repos/${{ github.repository }}/releases?per_page=100") + if [ "$RESPONSE" -ne 200 ]; then + echo "Failed to fetch releases. HTTP status: $RESPONSE" + cat response.json + exit 1 + fi + + # Extract and sort tags + ALL_TAGS=$(jq -r '.[].tag_name' response.json | grep -E '^v[0-9]+\.[0-9]+(-[0-9]+)?$' | sed 's/-.*//' | sort -V | tail -n 1) + + # Exit if no tags were found + if [ -z "$ALL_TAGS" ]; then + echo "No valid tags found." + exit 1 + fi + + echo "::set-output name=tag::$ALL_TAGS" + # echo "tag=$ALL_TAGS" >> $GITHUB_STATE + + - name: Calculate next semver minor + id: calculate_next_version + run: | + LATEST_VERSION=${{ steps.get_all_releases.outputs.tag }} + + # strip 'v' prefix and split into major.minor + LATEST_VERSION=${LATEST_VERSION//v/} + IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_VERSION" + + MAJOR=${VERSION_PARTS[0]} + MINOR=${VERSION_PARTS[1]} + + # increment the minor version + MINOR=$((MINOR + 1)) + + NEXT_VERSION="${MAJOR}.${MINOR}" + + echo "Major: $MAJOR" + echo "Minor: $MINOR" + + echo "Next version: $NEXT_VERSION" + echo "::set-output name=next_version::$NEXT_VERSION" + echo "::set-output name=next_version_major::$MAJOR" + echo "::set-output name=next_version_minor::$MINOR" \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index f352d478..dc69c441 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,12 @@ [submodule "dependencies/vcpkg"] path = dependencies/vcpkg url = https://github.com/microsoft/vcpkg - shallow = true + shallow = false [submodule "dependencies/Vulkan-Headers"] path = dependencies/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers + shallow = true [submodule "dependencies/imgui"] path = dependencies/imgui url = https://github.com/ocornut/imgui + shallow = true diff --git a/BUILD.md b/BUILD.md index 3ff2254f..44d69c6c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -16,11 +16,11 @@ - [Compiling Errors](#compiling-errors) - [Building Errors](#building-errors) - [macOS](#macos) - - [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used) - [Installing brew](#installing-brew) - - [Installing Dependencies](#installing-dependencies) - - [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang) - - [Updating Cemu and source code](#updating-cemu-and-source-code) + - [Installing Tool Dependencies](#installing-tool-dependencies) + - [Installing Library Dependencies](#installing-library-dependencies) + - [Build Cemu using CMake](#build-cemu-using-cmake) +- [Updating Cemu and source code](#updating-cemu-and-source-code) ## Windows @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel` +`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` ### Build Cemu @@ -141,31 +141,41 @@ If you are getting a different error than any of the errors listed above, you ma ## macOS -To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and -below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics -API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the -Molten-VK compatibility layer - -### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used - -You can skip this section if you have an Intel Mac. Every time you compile, you need to perform steps 2. - -1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once -2. `arch -x86_64 zsh` # run an x64 shell +To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below +don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that +you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't +supported on macOS, so Vulkan must be used through the Molten-VK compatibility layer. ### Installing brew 1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` -2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env +2. Set up the Homebrew shell environment: + 1. **On an Intel Mac:** `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` + 2. **On an Apple Silicon Mac:** eval `"$(/opt/homebrew/bin/brew shellenv)"` -### Installing Dependencies +### Installing Tool Dependencies -`brew install boost git cmake llvm ninja nasm molten-vk automake libtool` +The native versions of these can be used regardless of what type of Mac you have. + +`brew install git cmake ninja nasm automake libtool` + +### Installing Library Dependencies + +**On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used to install these dependencies:** +1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once +2. `arch -x86_64 zsh` # run an x64 shell +3. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` +4. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` + +Then install the dependencies: + +`brew install boost molten-vk` + +### Build Cemu using CMake -### Build Cemu using CMake and Clang 1. `git clone --recursive https://github.com/cemu-project/Cemu` 2. `cd Cemu` -3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja` +3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Ninja` 4. `cmake --build build` 5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`. diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b5f3881..54e2012a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,20 +2,39 @@ cmake_minimum_required(VERSION 3.21.1) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) -set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version -if (EXPERIMENTAL_VERSION) - add_definitions(-DEMULATOR_VERSION_MINOR=${EXPERIMENTAL_VERSION}) - execute_process( - COMMAND git log --format=%h -1 - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} - OUTPUT_VARIABLE GIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - add_definitions(-DEMULATOR_HASH=${GIT_HASH}) -endif() +# used by CI script to set version: +set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "") +set(EMULATOR_VERSION_MINOR "0" CACHE STRING "") +set(EMULATOR_VERSION_PATCH "0" CACHE STRING "") + +execute_process( + COMMAND git log --format=%h -1 + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + OUTPUT_VARIABLE GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +add_definitions(-DEMULATOR_HASH=${GIT_HASH}) if (ENABLE_VCPKG) + # check if vcpkg is shallow and unshallow it if necessary + execute_process( + COMMAND git rev-parse --is-shallow-repository + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg + OUTPUT_VARIABLE is_vcpkg_shallow + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(is_vcpkg_shallow STREQUAL "true") + message(STATUS "vcpkg is shallow. Unshallowing it now...") + execute_process( + COMMAND git fetch --unshallow + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg" + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ) + endif() + if(UNIX AND NOT APPLE) set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux") elseif(APPLE) @@ -44,6 +63,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_definitions($<$:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT +add_definitions(-DEMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR}) +add_definitions(-DEMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR}) +add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}) + set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds @@ -69,6 +92,7 @@ endif() if (APPLE) enable_language(OBJC OBJCXX) + set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") endif() if (UNIX AND NOT APPLE) diff --git a/bin/resources/de/cemu.mo b/bin/resources/de/cemu.mo index 918fcd35..8dc4e8cc 100644 Binary files a/bin/resources/de/cemu.mo and b/bin/resources/de/cemu.mo differ diff --git a/bin/resources/es/cemu.mo b/bin/resources/es/cemu.mo index 4f78fdcd..a43d4a1d 100644 Binary files a/bin/resources/es/cemu.mo and b/bin/resources/es/cemu.mo differ diff --git a/bin/resources/fr/cemu.mo b/bin/resources/fr/cemu.mo index d31685fc..f3f3b498 100644 Binary files a/bin/resources/fr/cemu.mo and b/bin/resources/fr/cemu.mo differ diff --git a/bin/resources/hu/cemu.mo b/bin/resources/hu/cemu.mo index d7f761fe..51b00d08 100644 Binary files a/bin/resources/hu/cemu.mo and b/bin/resources/hu/cemu.mo differ diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo index 9f20110f..5ea5e1da 100644 Binary files a/bin/resources/ko/cemu.mo and b/bin/resources/ko/cemu.mo differ diff --git a/bin/resources/ru/cemu.mo b/bin/resources/ru/cemu.mo index f89098fd..4ff04e2b 100644 Binary files a/bin/resources/ru/cemu.mo and b/bin/resources/ru/cemu.mo differ diff --git a/bin/resources/zh/cemu.mo b/bin/resources/zh/cemu.mo index f9825b24..3e636971 100644 Binary files a/bin/resources/zh/cemu.mo and b/bin/resources/zh/cemu.mo differ diff --git a/bin/shaderCache/info.txt b/bin/shaderCache/info.txt deleted file mode 100644 index 962cf88b..00000000 --- a/bin/shaderCache/info.txt +++ /dev/null @@ -1 +0,0 @@ -If you plan to transfer the shader cache to a different PC or Cemu installation you only need to copy the 'transferable' directory. \ No newline at end of file diff --git a/dependencies/ih264d/CMakeLists.txt b/dependencies/ih264d/CMakeLists.txt index d97d6dda..686a9d08 100644 --- a/dependencies/ih264d/CMakeLists.txt +++ b/dependencies/ih264d/CMakeLists.txt @@ -117,7 +117,13 @@ add_library (ih264d "decoder/ivd.h" ) -if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") +if (CMAKE_OSX_ARCHITECTURES) +set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) +else() +set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64") set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86") include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES}) target_sources(ih264d PRIVATE @@ -140,7 +146,7 @@ target_sources(ih264d PRIVATE "decoder/x86/ih264d_function_selector_sse42.c" "decoder/x86/ih264d_function_selector_ssse3.c" ) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") +elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64") enable_language( C CXX ASM ) set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm") include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES}) @@ -178,7 +184,7 @@ target_sources(ih264d PRIVATE ) target_compile_options(ih264d PRIVATE -DARMV8) else() -message(FATAL_ERROR "ih264d unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}") +message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}") endif() if(MSVC) diff --git a/dependencies/vcpkg b/dependencies/vcpkg index cbf4a664..a4275b7e 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit cbf4a6641528cee6f172328984576f51698de726 +Subproject commit a4275b7eee79fb24ec2e135481ef5fce8b41c339 diff --git a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b9..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch deleted file mode 100644 index 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 22685e6a..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$: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 1f460375..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch deleted file mode 100644 index 70654cf8..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake deleted file mode 100644 index 426d8af7..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports/tiff/usage b/dependencies/vcpkg_overlay_ports/tiff/usage deleted file mode 100644 index d47265b1..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7a..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a8..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch deleted file mode 100644 index ac827f0c..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt -index 8cde1ffe0..d4d09f223 100644 ---- a/tools/CMakeLists.txt -+++ b/tools/CMakeLists.txt -@@ -91,7 +91,9 @@ endif() - add_executable(dbus-launch ${dbus_launch_SOURCES}) - target_link_libraries(dbus-launch ${DBUS_LIBRARIES}) - if(DBUS_BUILD_X11) -- target_link_libraries(dbus-launch ${X11_LIBRARIES} ) -+ find_package(Threads REQUIRED) -+ target_link_libraries(dbus-launch ${X11_LIBRARIES} ${X11_xcb_LIB} ${X11_Xau_LIB} ${X11_Xdmcp_LIB} Threads::Threads) -+ target_include_directories(dbus-launch PRIVATE ${X11_INCLUDE_DIR}) - endif() - install(TARGETS dbus-launch ${INSTALL_TARGETS_DEFAULT_ARGS}) - diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch deleted file mode 100644 index 5cd2309e..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake -index b7f3702..e2336ba 100644 ---- a/cmake/ConfigureChecks.cmake -+++ b/cmake/ConfigureChecks.cmake -@@ -51,6 +51,7 @@ check_symbol_exists(closefrom "unistd.h" HAVE_CLOSEFROM) # - check_symbol_exists(environ "unistd.h" HAVE_DECL_ENVIRON) - check_symbol_exists(fstatfs "sys/vfs.h" HAVE_FSTATFS) - check_symbol_exists(getgrouplist "grp.h" HAVE_GETGROUPLIST) # dbus-sysdeps.c -+check_symbol_exists(getpeereid "sys/types.h;unistd.h" HAVE_GETPEEREID) # dbus-sysdeps.c, - check_symbol_exists(getpeerucred "ucred.h" HAVE_GETPEERUCRED) # dbus-sysdeps.c, dbus-sysdeps-win.c - check_symbol_exists(getpwnam_r "errno.h;pwd.h" HAVE_GETPWNAM_R) # dbus-sysdeps-util-unix.c - check_symbol_exists(getrandom "sys/random.h" HAVE_GETRANDOM) -diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake -index 77fc19c..2f25643 100644 ---- a/cmake/config.h.cmake -+++ b/cmake/config.h.cmake -@@ -140,6 +140,9 @@ - /* Define to 1 if you have getgrouplist */ - #cmakedefine HAVE_GETGROUPLIST 1 - -+/* Define to 1 if you have getpeereid */ -+#cmakedefine HAVE_GETPEEREID 1 -+ - /* Define to 1 if you have getpeerucred */ - #cmakedefine HAVE_GETPEERUCRED 1 - diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch deleted file mode 100644 index 74193dc4..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index d3ec71b..932066a 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -141,6 +141,10 @@ if(DBUS_LINUX) - if(ENABLE_SYSTEMD AND SYSTEMD_FOUND) - set(DBUS_BUS_ENABLE_SYSTEMD ON) - set(HAVE_SYSTEMD ${SYSTEMD_FOUND}) -+ pkg_check_modules(SYSTEMD libsystemd IMPORTED_TARGET) -+ set(SYSTEMD_LIBRARIES PkgConfig::SYSTEMD CACHE INTERNAL "") -+ else() -+ set(SYSTEMD_LIBRARIES "" CACHE INTERNAL "") - endif() - option(ENABLE_USER_SESSION "enable user-session semantics for session bus under systemd" OFF) - set(DBUS_ENABLE_USER_SESSION ${ENABLE_USER_SESSION}) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch deleted file mode 100644 index 63581487..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index caef738..b878f42 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -724,11 +724,11 @@ add_custom_target(help-options - # - if(DBUS_ENABLE_PKGCONFIG) - set(PLATFORM_LIBS pthread ${LIBRT}) -- if(PKG_CONFIG_FOUND) -- # convert lists of link libraries into -lstdc++ -lm etc.. -- foreach(LIB ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) -- set(LIBDBUS_LIBS "${LIBDBUS_LIBS} -l${LIB}") -- endforeach() -+ if(1) -+ set(LIBDBUS_LIBS "${CMAKE_THREAD_LIBS_INIT}") -+ if(LIBRT) -+ string(APPEND LIBDBUS_LIBS " -lrt") -+ endif() - set(original_prefix "${CMAKE_INSTALL_PREFIX}") - if(DBUS_RELOCATABLE) - set(pkgconfig_prefix "\${pcfiledir}/../..") diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake deleted file mode 100644 index 56c7e182..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake +++ /dev/null @@ -1,88 +0,0 @@ -vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) - -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.freedesktop.org/ - OUT_SOURCE_PATH SOURCE_PATH - REPO dbus/dbus - REF "dbus-${VERSION}" - SHA512 8e476b408514e6540c36beb84e8025827c22cda8958b6eb74d22b99c64765eb3cd5a6502aea546e3e5f0534039857b37edee89c659acef40e7cab0939947d4af - HEAD_REF master - PATCHES - cmake.dep.patch - pkgconfig.patch - getpeereid.patch # missing check from configure.ac - libsystemd.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS options - FEATURES - systemd ENABLE_SYSTEMD - x11 DBUS_BUILD_X11 - x11 CMAKE_REQUIRE_FIND_PACKAGE_X11 -) - -unset(ENV{DBUSDIR}) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - -DDBUS_BUILD_TESTS=OFF - -DDBUS_ENABLE_DOXYGEN_DOCS=OFF - -DDBUS_ENABLE_XML_DOCS=OFF - -DDBUS_INSTALL_SYSTEM_LIBS=OFF - #-DDBUS_SERVICE=ON - -DDBUS_WITH_GLIB=OFF - -DTHREADS_PREFER_PTHREAD_FLAG=ON - -DXSLTPROC_EXECUTABLE=FALSE - "-DCMAKE_INSTALL_SYSCONFDIR=${CURRENT_PACKAGES_DIR}/etc/${PORT}" - "-DWITH_SYSTEMD_SYSTEMUNITDIR=lib/systemd/system" - "-DWITH_SYSTEMD_USERUNITDIR=lib/systemd/user" - ${options} - OPTIONS_RELEASE - -DDBUS_DISABLE_ASSERT=OFF - -DDBUS_ENABLE_STATS=OFF - -DDBUS_ENABLE_VERBOSE_MODE=OFF - MAYBE_UNUSED_VARIABLES - DBUS_BUILD_X11 - DBUS_WITH_GLIB - ENABLE_SYSTEMD - THREADS_PREFER_PTHREAD_FLAG - WITH_SYSTEMD_SYSTEMUNITDIR - WITH_SYSTEMD_USERUNITDIR -) -vcpkg_cmake_install() -vcpkg_copy_pdbs() -vcpkg_cmake_config_fixup(PACKAGE_NAME "DBus1" CONFIG_PATH "lib/cmake/DBus1") -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/debug/var/" - "${CURRENT_PACKAGES_DIR}/etc" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/services" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/session.d" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system-services" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.d" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" - "${CURRENT_PACKAGES_DIR}/share/doc" - "${CURRENT_PACKAGES_DIR}/var" -) - -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.conf" "") -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.d" "") -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session-local.conf" "") - -set(TOOLS daemon launch monitor run-session send test-tool update-activation-environment) -if(VCPKG_TARGET_IS_WINDOWS) - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") - file(RENAME "${CURRENT_PACKAGES_DIR}/bin/dbus-env.bat" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat" "${CURRENT_PACKAGES_DIR}" "%~dp0/../..") -else() - list(APPEND TOOLS cleanup-sockets uuidgen) -endif() -list(TRANSFORM TOOLS PREPEND "dbus-" ) -vcpkg_copy_tools(TOOL_NAMES ${TOOLS} AUTO_CLEAN) - -file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json deleted file mode 100644 index 853dff05..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "dbus", - "version": "1.15.8", - "port-version": 2, - "description": "D-Bus specification and reference implementation, including libdbus and dbus-daemon", - "homepage": "https://gitlab.freedesktop.org/dbus/dbus", - "license": "AFL-2.1 OR GPL-2.0-or-later", - "supports": "!uwp & !staticcrt", - "dependencies": [ - "expat", - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - ], - "features": { - "x11": { - "description": "Build with X11 autolaunch support", - "dependencies": [ - "libx11" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b9..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch deleted file mode 100644 index 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 22685e6a..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$: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 1f460375..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch deleted file mode 100644 index 70654cf8..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake deleted file mode 100644 index 426d8af7..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/usage b/dependencies/vcpkg_overlay_ports_linux/tiff/usage deleted file mode 100644 index d47265b1..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7a..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a8..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b9..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch deleted file mode 100644 index a8637d8c..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake deleted file mode 100644 index 22685e6a..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage deleted file mode 100644 index 1cddcd46..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $ - $,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json deleted file mode 100644 index 1f460375..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch deleted file mode 100644 index 70654cf8..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake deleted file mode 100644 index 426d8af7..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/usage b/dependencies/vcpkg_overlay_ports_mac/tiff/usage deleted file mode 100644 index d47265b1..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7a..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a8..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index 7bfc4701..b66326d7 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -10,6 +10,8 @@ curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/re chmod a+x mkappimage.AppImage curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh" chmod a+x linuxdeploy-plugin-gtk.sh +curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh" +chmod a+x linuxdeploy-plugin-checkrt.sh if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh @@ -39,7 +41,8 @@ export NO_STRIP=1 -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \ -i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \ -e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \ - --plugin gtk + --plugin gtk \ + --plugin checkrt if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then GITVERSION=experimental @@ -47,7 +50,6 @@ 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/src/CMakeLists.txt b/src/CMakeLists.txt index 1b78b1fb..7d64d91b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,12 @@ add_executable(CemuBin mainLLE.cpp ) +if(MSVC AND MSVC_VERSION EQUAL 1940) + # workaround for an msvc issue on VS 17.10 where generated ILK files are too large + # see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511 + set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO") +endif() + if(WIN32) target_sources(CemuBin PRIVATE resource/cemu.rc @@ -100,7 +106,7 @@ if (MACOS_BUNDLE) COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" - COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + COMMAND bash -c "install_name_tool -change /Users/runner/work/Cemu/Cemu/build/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 851854fc..91d257b2 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -218,6 +218,8 @@ add_library(CemuCafe HW/SI/SI.cpp HW/SI/si.h HW/VI/VI.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.h IOSU/fsa/fsa_types.h IOSU/fsa/iosu_fsa.cpp IOSU/fsa/iosu_fsa.h @@ -372,12 +374,24 @@ add_library(CemuCafe OS/libs/gx2/GX2_Texture.h OS/libs/gx2/GX2_TilingAperture.cpp OS/libs/h264_avc/H264Dec.cpp + OS/libs/h264_avc/H264DecBackendAVC.cpp OS/libs/h264_avc/h264dec.h + OS/libs/h264_avc/H264DecInternal.h OS/libs/h264_avc/parser OS/libs/h264_avc/parser/H264Parser.cpp OS/libs/h264_avc/parser/H264Parser.h OS/libs/mic/mic.cpp OS/libs/mic/mic.h + OS/libs/nfc/ndef.cpp + OS/libs/nfc/ndef.h + OS/libs/nfc/nfc.cpp + OS/libs/nfc/nfc.h + OS/libs/nfc/stream.cpp + OS/libs/nfc/stream.h + OS/libs/nfc/TagV0.cpp + OS/libs/nfc/TagV0.h + OS/libs/nfc/TLV.cpp + OS/libs/nfc/TLV.h OS/libs/nlibcurl/nlibcurl.cpp OS/libs/nlibcurl/nlibcurlDebug.hpp OS/libs/nlibcurl/nlibcurl.h @@ -445,14 +459,22 @@ add_library(CemuCafe OS/libs/nsyshid/AttachDefaultBackends.cpp OS/libs/nsyshid/Whitelist.cpp OS/libs/nsyshid/Whitelist.h + OS/libs/nsyshid/BackendEmulated.cpp + OS/libs/nsyshid/BackendEmulated.h OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.h + OS/libs/nsyshid/Infinity.cpp + OS/libs/nsyshid/Infinity.h + OS/libs/nsyshid/Skylander.cpp + OS/libs/nsyshid/Skylander.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.h + OS/libs/ntag/ntag.cpp + OS/libs/ntag/ntag.h OS/libs/padscore/padscore.cpp OS/libs/padscore/padscore.h OS/libs/proc_ui/proc_ui.cpp diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 3c62a686..958a5a57 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -35,6 +35,7 @@ #include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" @@ -51,6 +52,8 @@ #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/mic/mic.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/ntag/ntag.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" @@ -533,6 +536,7 @@ namespace CafeSystem iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), + iosu::ccr_nfc::GetModule(), }; // initialize all subsystems which are persistent and don't depend on a game running @@ -587,6 +591,8 @@ namespace CafeSystem H264::Initialize(); snd_core::Initialize(); mic::Initialize(); + nfc::Initialize(); + ntag::Initialize(); // init hardware register interfaces HW_SI::Initialize(); } diff --git a/src/Cafe/Filesystem/FST/fstUtil.h b/src/Cafe/Filesystem/FST/fstUtil.h index 01283684..a432cc95 100644 --- a/src/Cafe/Filesystem/FST/fstUtil.h +++ b/src/Cafe/Filesystem/FST/fstUtil.h @@ -3,6 +3,8 @@ #include +#include "../fsc.h" + // path parser and utility class for Wii U paths // optimized to be allocation-free for common path lengths class FSCPath @@ -119,9 +121,7 @@ public: template class FSAFileTree { -public: - -private: + private: enum NODETYPE : uint8 { @@ -133,6 +133,7 @@ private: { std::string name; std::vector subnodes; + size_t fileSize; F* custom; NODETYPE type; }; @@ -179,13 +180,54 @@ private: return newNode; } + class DirectoryIterator : public FSCVirtualFile + { + public: + DirectoryIterator(node_t* node) + : m_node(node), m_subnodeIndex(0) + { + } + + sint32 fscGetType() override + { + return FSC_TYPE_DIRECTORY; + } + + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (m_subnodeIndex >= m_node->subnodes.size()) + return false; + + const node_t* subnode = m_node->subnodes[m_subnodeIndex]; + + strncpy(dirEntry->path, subnode->name.c_str(), sizeof(dirEntry->path) - 1); + dirEntry->path[sizeof(dirEntry->path) - 1] = '\0'; + dirEntry->isDirectory = subnode->type == FSAFileTree::NODETYPE_DIRECTORY; + dirEntry->isFile = subnode->type == FSAFileTree::NODETYPE_FILE; + dirEntry->fileSize = subnode->type == FSAFileTree::NODETYPE_FILE ? subnode->fileSize : 0; + + ++m_subnodeIndex; + return true; + } + + bool fscRewindDir() override + { + m_subnodeIndex = 0; + return true; + } + + private: + node_t* m_node; + size_t m_subnodeIndex; + }; + public: FSAFileTree() { rootNode.type = NODETYPE_DIRECTORY; } - bool addFile(std::string_view path, F* custom) + bool addFile(std::string_view path, size_t fileSize, F* custom) { FSCPath p(path); if (p.GetNodeCount() == 0) @@ -196,6 +238,7 @@ public: return false; // node already exists // add file node node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1)); + fileNode->fileSize = fileSize; fileNode->custom = custom; return true; } @@ -214,6 +257,20 @@ public: return true; } + bool getDirectory(std::string_view path, FSCVirtualFile*& dirIterator) + { + FSCPath p(path); + if (p.GetNodeCount() == 0) + return false; + node_t* node = getByNodePath(p, p.GetNodeCount(), false); + if (node == nullptr) + return false; + if (node->type != NODETYPE_DIRECTORY) + return false; + dirIterator = new DirectoryIterator(node); + return true; + } + bool removeFile(std::string_view path) { FSCPath p(path); diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h index a3df2af2..8b8ed5ef 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -212,4 +212,4 @@ bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTarg // redirect device void fscDeviceRedirect_map(); -void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority); +void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority); diff --git a/src/Cafe/Filesystem/fscDeviceRedirect.cpp b/src/Cafe/Filesystem/fscDeviceRedirect.cpp index d25bff86..9c62d37a 100644 --- a/src/Cafe/Filesystem/fscDeviceRedirect.cpp +++ b/src/Cafe/Filesystem/fscDeviceRedirect.cpp @@ -11,7 +11,7 @@ struct RedirectEntry FSAFileTree redirectTree; -void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority) +void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority) { // check if source already has a redirection RedirectEntry* existingEntry; @@ -24,7 +24,7 @@ void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& t delete existingEntry; } RedirectEntry* entry = new RedirectEntry(targetFilePath, priority); - redirectTree.addFile(virtualSourcePath, entry); + redirectTree.addFile(virtualSourcePath, fileSize, entry); } class fscDeviceTypeRedirect : public fscDeviceC @@ -32,8 +32,15 @@ class fscDeviceTypeRedirect : public fscDeviceC FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { RedirectEntry* redirectionEntry; - if (redirectTree.getFile(path, redirectionEntry)) + + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && redirectTree.getFile(path, redirectionEntry)) return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus); + + FSCVirtualFile* dirIterator; + + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR) && redirectTree.getDirectory(path, dirIterator)) + return dirIterator; + return nullptr; } diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 27d423b9..c54c31cb 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -830,7 +830,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC { virtualMountPath = fs::path("vol/content/") / virtualMountPath; } - fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority); + fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority); } } } diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index 62a5d592..e7369af6 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -501,8 +501,6 @@ 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 diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp index ed97288d..fe9316f0 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp @@ -212,11 +212,12 @@ static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode) { - // untested (Don't Starve Giant Edition uses this) + // Seen in Don't Starve Giant Edition and Teslagrad // also used by DS Virtual Console (Super Mario 64 DS) PPC_OPC_TEMPL3_XO(); - hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; - PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], hCPU->gpr[rD])); + uint32 result = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], result)); + hCPU->gpr[rD] = result; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp index a9ab49a5..ace1601f 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -90,7 +90,7 @@ uint8* PPCInterpreterGetStackPointer() return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]); } -uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset) +uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset); diff --git a/src/Cafe/HW/Espresso/PPCCallback.h b/src/Cafe/HW/Espresso/PPCCallback.h index 19fcd4d1..3d5393b1 100644 --- a/src/Cafe/HW/Espresso/PPCCallback.h +++ b/src/Cafe/HW/Espresso/PPCCallback.h @@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t { sint32 gprCount = 0; sint32 floatCount = 0; + sint32 stackCount = 0; }; +inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value) +{ + if (data.gprCount < 8) + { + hCPU->gpr[3 + data.gprCount] = value; + data.gprCount++; + } + else + { + uint32 stackOffset = 8 + data.stackCount * 4; + + // PPCCore_executeCallbackInternal does -16*4 to save the current stack area + stackOffset -= 16 * 4; + + memory_writeU32(hCPU->gpr[1] + stackOffset, value); + data.stackCount++; + } +} + // callback functions inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) { @@ -16,23 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) template uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) { - cemu_assert_debug(data.gprCount <= 8); - cemu_assert_debug(data.floatCount <= 8); + // TODO float arguments on stack + cemu_assert_debug(data.floatCount < 8); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if constexpr (std::is_pointer_v) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR()); } else if constexpr (std::is_base_of_v>) { - hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR()); } else if constexpr (std::is_reference_v) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR()); } else if constexpr(std::is_enum_v) { @@ -53,8 +71,7 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, } else { - hCPU->gpr[3 + data.gprCount] = (uint32)currentArg; - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg); } return PPCCoreCallback(function, data, args...); diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index 134f73a8..c315ed0e 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -213,7 +213,7 @@ void PPCTimer_start(); // core info and control extern uint32 ppcThreadQuantum; -uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); uint8* PPCInterpreterGetStackPointer(); void PPCInterpreterModifyStackPointer(sint32 offset); diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index b59702cd..77f16468 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -524,7 +524,7 @@ void LatteSHRC_UpdateGSBaseHash(uint8* geometryShaderPtr, uint32 geometryShaderS // update hash from geometry shader data uint64 gsHash1 = 0; uint64 gsHash2 = 0; - _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheVS, &gsHash1, &gsHash2); + _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheGS, &gsHash1, &gsHash2); // get geometry shader uint64 gsHash = gsHash1 + gsHash2; gsHash += (uint64)_activeVertexShader->ringParameterCount; diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 3754fb19..d8852891 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -235,6 +235,9 @@ void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture) // if this function returns false, textures will not be synchronized even if their data overlaps bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) { + if(formatA == formatB) + return true; // if the format is identical then compatibility must be guaranteed (otherwise we can't create the necessary default view of a texture) + // todo - find a better way to handle this for (sint32 swap = 0; swap < 2; swap++) { diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp index ea2b9491..76d76322 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp @@ -241,6 +241,16 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } + else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned == 1 ) + { + // seen in Rabbids Land + _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.xyzw = floatBitsToUint(vec4(ivec4(attrDecoder)));" _CRLF); + } else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams @@ -496,3 +506,5 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex cemu_assert_debug(false); } } + + diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp index 3d46f206..cae53140 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp @@ -23,16 +23,13 @@ bool RendererShaderGL::loadBinary() cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); - sint32 fileSize = 0; std::vector cacheFileData; if (!s_programBinaryCache->GetFile({h1, h2 }, cacheFileData)) return false; - if (fileSize < sizeof(uint32)) - { + if (cacheFileData.size() <= sizeof(uint32)) return false; - } - uint32 shaderBinFormat = *(uint32*)(cacheFileData.data() + 0); + uint32 shaderBinFormat = *(uint32*)(cacheFileData.data()); m_program = glCreateProgram(); glProgramBinary(m_program, shaderBinFormat, cacheFileData.data()+4, cacheFileData.size()-4); diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp index f66dc9f4..23c8d0ea 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp @@ -12,9 +12,9 @@ uint32 RendererShader::GeneratePrecompiledCacheId() v += (uint32)(*s); s++; } - v += (EMULATOR_VERSION_LEAD * 1000000u); - v += (EMULATOR_VERSION_MAJOR * 10000u); - v += (EMULATOR_VERSION_MINOR * 100u); + v += (EMULATOR_VERSION_MAJOR * 1000000u); + v += (EMULATOR_VERSION_MINOR * 10000u); + v += (EMULATOR_VERSION_PATCH * 100u); // settings that can influence shaders v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 75ff02ba..56a3ab12 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -146,8 +146,17 @@ void SwapchainInfoVk::Create() UnrecoverableError("Failed to create semaphore for swapchain acquire"); } + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); + if (result != VK_SUCCESS) + UnrecoverableError("Failed to create fence for swapchain"); + m_acquireIndex = 0; hasDefinedSwapchainImage = false; + + m_queueDepth = 0; } void SwapchainInfoVk::Cleanup() @@ -177,6 +186,12 @@ void SwapchainInfoVk::Cleanup() m_swapchainFramebuffers.clear(); + if (m_imageAvailableFence) + { + WaitAvailableFence(); + vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); + m_imageAvailableFence = nullptr; + } if (m_swapchain) { vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); @@ -189,6 +204,18 @@ bool SwapchainInfoVk::IsValid() const return m_swapchain && !m_acquireSemaphores.empty(); } +void SwapchainInfoVk::WaitAvailableFence() +{ + if(m_awaitableFence != VK_NULL_HANDLE) + vkWaitForFences(m_logicalDevice, 1, &m_awaitableFence, VK_TRUE, UINT64_MAX); + m_awaitableFence = VK_NULL_HANDLE; +} + +void SwapchainInfoVk::ResetAvailableFence() const +{ + vkResetFences(m_logicalDevice, 1, &m_imageAvailableFence); +} + VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() { VkSemaphore ret = m_currentSemaphore; @@ -198,8 +225,10 @@ VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() bool SwapchainInfoVk::AcquireImage() { + ResetAvailableFence(); + VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, nullptr, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; if (result == VK_TIMEOUT) @@ -216,6 +245,7 @@ bool SwapchainInfoVk::AcquireImage() return false; } m_currentSemaphore = acquireSemaphore; + m_awaitableFence = m_imageAvailableFence; m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); return true; @@ -319,6 +349,7 @@ VkExtent2D SwapchainInfoVk::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& cap VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector& modes) { + m_maxQueued = 0; const auto vsyncState = (VSync)GetConfig().vsync.GetValue(); if (vsyncState == VSync::MAILBOX) { @@ -345,6 +376,7 @@ VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector m_acquireSemaphores; // indexed by m_acquireIndex + VkFence m_imageAvailableFence{}; + VkFence m_awaitableFence = VK_NULL_HANDLE; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; std::array m_swapchainQueueFamilyIndices; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index 0489bb4e..6bde2a0b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -188,6 +188,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR); VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR); +// khr_present_wait +VKFUNC_DEVICE(vkWaitForPresentKHR); + // transform feedback extension VKFUNC_DEVICE(vkCmdBindTransformFeedbackBuffersEXT); VKFUNC_DEVICE(vkCmdBeginTransformFeedbackEXT); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index ce582b9a..ba094a84 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -826,7 +826,7 @@ void PipelineCompiler::InitDepthStencilState() depthStencilState.front.reference = stencilRefFront; depthStencilState.front.compareMask = stencilCompareMaskFront; - depthStencilState.front.writeMask = stencilWriteMaskBack; + depthStencilState.front.writeMask = stencilWriteMaskFront; depthStencilState.front.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; depthStencilState.front.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; depthStencilState.front.failOp = stencilOpTable[(size_t)frontStencilFail]; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 9209e3cd..12d1d975 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -7,6 +7,7 @@ #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" @@ -29,6 +30,7 @@ #include #include +#include // for localization #ifndef VK_API_VERSION_MAJOR #define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU) @@ -45,7 +47,9 @@ const std::vector kOptionalDeviceExtensions = VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, - VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME + VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, + VK_KHR_PRESENT_WAIT_EXTENSION_NAME, + VK_KHR_PRESENT_ID_EXTENSION_NAME }; const std::vector kRequiredDeviceExtensions = @@ -123,7 +127,7 @@ std::vector VulkanRenderer::GetDevices() VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = EMULATOR_NAME; - app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); app_info.pEngineName = EMULATOR_NAME; app_info.engineVersion = app_info.applicationVersion; app_info.apiVersion = apiVersion; @@ -250,12 +254,24 @@ void VulkanRenderer::GetDeviceFeatures() pcc.pNext = prevStruct; prevStruct = &pcc; + VkPhysicalDevicePresentIdFeaturesKHR pidf{}; + pidf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR; + pidf.pNext = prevStruct; + prevStruct = &pidf; + + VkPhysicalDevicePresentWaitFeaturesKHR pwf{}; + pwf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR; + pwf.pNext = prevStruct; + prevStruct = &pwf; + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.pNext = prevStruct; vkGetPhysicalDeviceFeatures2(m_physicalDevice, &physicalDeviceFeatures2); + cemuLog_log(LogType::Force, "Vulkan: present_wait extension: {}", (pwf.presentWait && pidf.presentId) ? "supported" : "unsupported"); + /* Get Vulkan device properties and limits */ VkPhysicalDeviceFloatControlsPropertiesKHR pfcp{}; prevStruct = nullptr; @@ -285,7 +301,7 @@ void VulkanRenderer::GetDeviceFeatures() cemuLog_log(LogType::Force, "VK_EXT_pipeline_creation_cache_control not supported. Cannot use asynchronous shader and pipeline compilation"); // if async shader compilation is enabled show warning message if (GetConfig().async_compile) - wxMessageBox(_("The currently installed graphics driver does not support the Vulkan extension necessary for asynchronous shader compilation. Asynchronous compilation cannot be used.\n \nRequired extension: VK_EXT_pipeline_creation_cache_control\n\nInstalling the latest graphics driver may solve this error."), _("Information"), wxOK | wxCENTRE); + LatteOverlay_pushNotification(_("Async shader compile is enabled but not supported by the graphics driver\nCemu will use synchronous compilation which can cause additional stutter").utf8_string(), 10000); } if (!m_featureControl.deviceExtensions.custom_border_color_without_format) { @@ -337,7 +353,7 @@ VulkanRenderer::VulkanRenderer() VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = EMULATOR_NAME; - app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); app_info.pEngineName = EMULATOR_NAME; app_info.engineVersion = app_info.applicationVersion; app_info.apiVersion = apiVersion; @@ -488,6 +504,24 @@ VulkanRenderer::VulkanRenderer() customBorderColorFeature.customBorderColors = VK_TRUE; customBorderColorFeature.customBorderColorWithoutFormat = VK_TRUE; } + // enable VK_KHR_present_id + VkPhysicalDevicePresentIdFeaturesKHR presentIdFeature{}; + if(m_featureControl.deviceExtensions.present_wait) + { + presentIdFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR; + presentIdFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &presentIdFeature; + presentIdFeature.presentId = VK_TRUE; + } + // enable VK_KHR_present_wait + VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeature{}; + if(m_featureControl.deviceExtensions.present_wait) + { + presentWaitFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR; + presentWaitFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &presentWaitFeature; + presentWaitFeature.presentWait = VK_TRUE; + } std::vector used_extensions; VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); @@ -1045,6 +1079,10 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vectorvkImageAspect = VK_IMAGE_ASPECT_COLOR_BIT; + if(format == (Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT | Latte::E_GX2SURFFMT::FMT_BIT_SRGB)) // Seen in Sonic Transformed level Starry Speedway. SRGB should just be ignored for native float formats? + format = Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT; switch (format) { // RGBA formats @@ -2439,6 +2480,11 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD // used by Color Splash and Resident Evil formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format? formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify + case Latte::E_GX2SURFFMT::R32_X8_FLOAT: + // seen in Disney Infinity 3.0 + formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; + formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); + break; default: cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format); cemu_assert_debug(false); @@ -2686,11 +2732,21 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) ClearColorImageRaw(chainInfo.m_swapchainImages[chainInfo.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } + const size_t currentFrameCmdBufferID = GetCurrentCommandBufferId(); + VkSemaphore presentSemaphore = chainInfo.m_presentSemaphores[chainInfo.swapchainImageIndex]; SubmitCommandBuffer(presentSemaphore); // submit all command and signal semaphore cemu_assert_debug(m_numSubmittedCmdBuffers > 0); + // wait for the previous frame to finish rendering + WaitCommandBufferFinished(m_commandBufferIDOfPrevFrame); + m_commandBufferIDOfPrevFrame = currentFrameCmdBufferID; + + chainInfo.WaitAvailableFence(); + + VkPresentIdKHR presentId = {}; + VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.swapchainCount = 1; @@ -2700,6 +2756,24 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = &presentSemaphore; + // if present_wait is available and enabled, add frame markers to present requests + // and limit the number of queued present operations + if (m_featureControl.deviceExtensions.present_wait && chainInfo.m_maxQueued > 0) + { + presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR; + presentId.swapchainCount = 1; + presentId.pPresentIds = &chainInfo.m_presentId; + + presentInfo.pNext = &presentId; + + if(chainInfo.m_queueDepth >= chainInfo.m_maxQueued) + { + uint64 waitFrameId = chainInfo.m_presentId - chainInfo.m_queueDepth; + vkWaitForPresentKHR(m_logicalDevice, chainInfo.m_swapchain, waitFrameId, 40'000'000); + chainInfo.m_queueDepth--; + } + } + VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo); if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR) { @@ -2708,6 +2782,12 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) chainInfo.m_shouldRecreate = true; + if(result >= 0) + { + chainInfo.m_queueDepth++; + chainInfo.m_presentId++; + } + chainInfo.hasDefinedSwapchainImage = false; chainInfo.swapchainImageIndex = -1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 6df53da4..ce97b5e9 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -450,6 +450,7 @@ private: bool synchronization2 = false; // VK_KHR_synchronization2 bool dynamic_rendering = false; // VK_KHR_dynamic_rendering bool shader_float_controls = false; // VK_KHR_shader_float_controls + bool present_wait = false; // VK_KHR_present_wait }deviceExtensions; struct @@ -457,7 +458,7 @@ private: bool shaderRoundingModeRTEFloat32{ false }; }shaderFloatControls; // from VK_KHR_shader_float_controls - struct + struct { bool debug_utils = false; // VK_EXT_DEBUG_UTILS }instanceExtensions; @@ -635,6 +636,7 @@ private: size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) + size_t m_commandBufferIDOfPrevFrame = 0; std::array m_cmd_buffer_fences; std::array m_commandBuffers; std::array m_commandBufferSemaphores; diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp new file mode 100644 index 00000000..1ceb16dc --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp @@ -0,0 +1,406 @@ +#include "iosu_ccr_nfc.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "util/crypto/aes128.h" +#include +#include + +namespace iosu +{ + namespace ccr_nfc + { + IOSMsgQueueId sCCRNFCMsgQueue; + SysAllocator sCCRNFCMsgQueueMsgBuffer; + std::thread sCCRNFCThread; + + constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 }; + constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB }; + + constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; + constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; + constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 }; + constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 }; + + constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; + constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; + + uint8 sLockedSecretInternalKey[0x10]; + uint8 sLockedSecretInternalNonce[0x10]; + uint8 sLockedSecretInternalHmacKey[0x10]; + + uint8 sUnfixedInfosInternalKey[0x10]; + uint8 sUnfixedInfosInternalNonce[0x10]; + uint8 sUnfixedInfosInternalHmacKey[0x10]; + + sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets) + { + if (!data) + { + return CCR_NFC_ERROR; + } + + if (size != sizeof(CCRNFCCryptData)) + { + return CCR_NFC_ERROR; + } + + if (!validateOffsets) + { + return 0; + } + + // Make sure all offsets are within bounds + if (data->version == 0) + { + if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 && + data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 && + data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9) + { + return 0; + } + } + else if (data->version == 2) + { + if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D && + data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D && + data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D) + { + return 0; + } + } + + return CCR_NFC_ERROR; + } + + sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32 inSize, void* outData, uint32 outSize) + { + uint8 tmpIv[0x10]; + memcpy(tmpIv, ivNonce, sizeof(tmpIv)); + + memcpy(outData, inData, inSize); + AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv); + return 0; + } + + sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32 nameSize, const uint8* inData, uint32 inSize, uint8* outData, uint32 outSize) + { + if (nameSize != 0xe || outSize != 0x40) + { + return CCR_NFC_ERROR; + } + + // Create a buffer containing 2 counter bytes, the key name, and the key data + uint8 buffer[0x50]; + buffer[0] = 0; + buffer[1] = 0; + memcpy(buffer + 2, name, nameSize); + memcpy(buffer + nameSize + 2, inData, inSize); + + uint16 counter = 0; + while (outSize > 0) + { + // Set counter bytes and increment counter + buffer[0] = (counter >> 8) & 0xFF; + buffer[1] = counter & 0xFF; + counter++; + + uint32 dataSize = outSize; + if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize)) + { + return CCR_NFC_ERROR; + } + + outSize -= 0x20; + outData += 0x20; + } + + return 0; + } + + sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) + { + uint8 lockedSecretBuffer[0x40] = { 0 }; + uint8 unfixedInfosBuffer[0x40] = { 0 }; + uint8 outBuffer[0x40] = { 0 }; + + // Fill the locked secret buffer + memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sLockedSecretInternalKey, outBuffer, 0x10); + memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10); + + // Fill the unfixed infos buffer + memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2); + memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10); + memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10); + + return 0; + } + + sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) + { + // Decrypt key generation salt + uint8 keyGenSalt[0x20]; + sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); + if (res != 0) + { + return res; + } + + // Prepare internal keys + res = __CCRNFCGenerateInternalKeys(in, keyGenSalt); + if (res != 0) + { + return res; + } + + if (decrypt) + { + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + + // Decrypt unfxied infos + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + + // Verify HMACs + uint8 hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + if (in->version == 0) + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + else + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_UNFIXED_INFOS; + } + } + else + { + uint8 hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + else + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + } + + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + } + + return res; + } + + void CCRNFCThread() + { + iosu::kernel::IOSMessage msg; + while (true) + { + IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0); + cemu_assert(!IOS_ResultIsError(error)); + + // Check for system exit + if (msg == 0xf00dd00d) + { + return; + } + + IPCCommandBody* cmd = MEMPTR(msg).GetPtr(); + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) + { + sint32 result; + uint32 requestId = cmd->args[0]; + void* ptrIn = MEMPTR(cmd->args[1]); + uint32 sizeIn = cmd->args[2]; + void* ptrOut = MEMPTR(cmd->args[3]); + uint32 sizeOut = cmd->args[4]; + + if ((result = __CCRNFCValidateCryptData(static_cast(ptrIn), sizeIn, true)) == 0 && + (result = __CCRNFCValidateCryptData(static_cast(ptrOut), sizeOut, false)) == 0) + { + // Initialize outData with inData + memcpy(ptrOut, ptrIn, sizeIn); + + switch (requestId) + { + case 1: // encrypt + result = __CCRNFCCryptData(static_cast(ptrIn), static_cast(ptrOut), false); + break; + case 2: // decrypt + result = __CCRNFCCryptData(static_cast(ptrIn), static_cast(ptrOut), true); + break; + default: + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId"); + cemu_assert_suspicious(); + result = IOS_ERROR_INVALID; + break; + } + } + + iosu::kernel::IOS_ResourceReply(cmd, static_cast(result)); + } + else + { + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId"); + cemu_assert_suspicious(); + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + } + + class : public ::IOSUModule + { + void SystemLaunch() override + { + sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount()); + cemu_assert(!IOS_ResultIsError(static_cast(sCCRNFCMsgQueue))); + + IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue); + cemu_assert(!IOS_ResultIsError(error)); + + sCCRNFCThread = std::thread(CCRNFCThread); + } + + void SystemExit() override + { + if (sCCRNFCMsgQueue < 0) + { + return; + } + + iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0); + sCCRNFCThread.join(); + + iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue); + sCCRNFCMsgQueue = -1; + } + } sIOSUModuleCCRNFC; + + IOSUModule* GetModule() + { + return &sIOSUModuleCCRNFC; + } + } +} diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h new file mode 100644 index 00000000..ae99d645 --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h @@ -0,0 +1,31 @@ +#pragma once +#include "Cafe/IOSU/iosu_types_common.h" + +#define CCR_NFC_ERROR (-0x2F001E) +#define CCR_NFC_INVALID_LOCKED_SECRET (-0x2F0029) +#define CCR_NFC_INVALID_UNFIXED_INFOS (-0x2F002A) + +namespace iosu +{ + namespace ccr_nfc + { + struct CCRNFCCryptData + { + uint32 version; + uint32 dataSize; + uint32 seedOffset; + uint32 keyGenSaltOffset; + uint32 uuidOffset; + uint32 unfixedInfosOffset; + uint32 unfixedInfosSize; + uint32 lockedSecretOffset; + uint32 lockedSecretSize; + uint32 unfixedInfosHmacOffset; + uint32 lockedSecretHmacOffset; + uint8 data[540]; + }; + static_assert(sizeof(CCRNFCCryptData) == 0x248); + + IOSUModule* GetModule(); + } +} diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp index aca1a332..28d248ae 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.cpp +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -511,6 +511,8 @@ namespace iosu 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::UpdateCommentAsync: + return CallHandler_UpdateCommentAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::UpdatePreferenceAsync: return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync: @@ -719,18 +721,23 @@ namespace iosu 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; + if(g_fpd.nexFriendSession) + { + if(vecOut->size != MY_COMMENT_LENGTH * sizeof(uint16be)) + { + cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + nexComment myNexComment; + g_fpd.nexFriendSession->getMyComment(myNexComment); + myComment = StringHelpers::FromUtf8(myNexComment.commentString); + } + myComment.insert(0, 1, '\0'); + memcpy(vecOut->basePhys.GetPtr(), myComment.c_str(), MY_COMMENT_LENGTH * sizeof(uint16be)); + return FPResult_Ok; } nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) @@ -1143,6 +1150,36 @@ namespace iosu return FPResult_Ok; } + nnResult CallHandler_UpdateCommentAsync(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; + uint32 messageLength = vecIn[0].size / sizeof(uint16be); + DeclareInputPtr(newComment, uint16be, messageLength, 0); + if (messageLength == 0 || newComment[messageLength-1] != 0) + { + cemuLog_log(LogType::Force, "UpdateCommentAsync: Message must contain at least a null-termination character"); + return FPResult_InvalidIPCParam; + } + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + + auto utf8_comment = StringHelpers::ToUtf8(newComment, messageLength); + nexComment temporaryComment; + temporaryComment.ukn0 = 0; + temporaryComment.commentString = utf8_comment; + temporaryComment.ukn1 = 0; + + g_fpd.nexFriendSession->updateCommentAsync(temporaryComment, [cmd](NexFriends::RpcErrorCode result) { + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } + nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.h b/src/Cafe/IOSU/legacy/iosu_fpd.h index 0a6f0885..b1c30765 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.h +++ b/src/Cafe/IOSU/legacy/iosu_fpd.h @@ -212,6 +212,7 @@ namespace iosu static const int RELATIONSHIP_FRIEND = 3; static const int GAMEMODE_MAX_MESSAGE_LENGTH = 0x80; // limit includes null-terminator character, so only 0x7F actual characters can be used + static const int MY_COMMENT_LENGTH = 0x12; enum class FPD_REQUEST_ID { @@ -245,6 +246,7 @@ namespace iosu CheckSettingStatusAsync = 0x7596, GetFriendListEx = 0x75F9, GetFriendRequestListEx = 0x76C1, + UpdateCommentAsync = 0x7726, UpdatePreferenceAsync = 0x7727, RemoveFriendAsync = 0x7789, DeleteFriendFlagsAsync = 0x778A, diff --git a/src/Cafe/OS/common/OSCommon.h b/src/Cafe/OS/common/OSCommon.h index 4fb65a47..34f207bb 100644 --- a/src/Cafe/OS/common/OSCommon.h +++ b/src/Cafe/OS/common/OSCommon.h @@ -23,3 +23,86 @@ void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64); // utility functions #include "Cafe/OS/common/OSUtil.h" + +// va_list +struct ppc_va_list +{ + uint8be gprIndex; + uint8be fprIndex; + uint8be _padding2[2]; + MEMPTR overflow_arg_area; + MEMPTR reg_save_area; +}; +static_assert(sizeof(ppc_va_list) == 0xC); + +struct ppc_va_list_reg_storage +{ + uint32be gpr_save_area[8]; // 32 bytes, r3 to r10 + float64be fpr_save_area[8]; // 64 bytes, f1 to f8 + ppc_va_list vargs; + uint32be padding; +}; +static_assert(sizeof(ppc_va_list_reg_storage) == 0x70); + +// Equivalent of va_start for PPC HLE functions. Must be called before any StackAllocator<> definitions +#define ppc_define_va_list(__gprIndex, __fprIndex) \ + MPTR vaOriginalR1 = PPCInterpreter_getCurrentInstance()->gpr[1]; \ + StackAllocator va_list_storage; \ + for(int i=3; i<=10; i++) va_list_storage->gpr_save_area[i-3] = PPCInterpreter_getCurrentInstance()->gpr[i]; \ + for(int i=1; i<=8; i++) va_list_storage->fpr_save_area[i-1] = PPCInterpreter_getCurrentInstance()->fpr[i].fp0; \ + va_list_storage->vargs.gprIndex = __gprIndex; \ + va_list_storage->vargs.fprIndex = __fprIndex; \ + va_list_storage->vargs.reg_save_area = (uint8be*)&va_list_storage; \ + va_list_storage->vargs.overflow_arg_area = {vaOriginalR1 + 8}; \ + ppc_va_list& vargs = va_list_storage->vargs; + +enum class ppc_va_type +{ + INT32 = 1, + INT64 = 2, + FLOAT_OR_DOUBLE = 3, +}; + +static void* _ppc_va_arg(ppc_va_list* vargs, ppc_va_type argType) +{ + void* r; + switch ( argType ) + { + default: + cemu_assert_suspicious(); + case ppc_va_type::INT32: + if ( vargs[0].gprIndex < 8u ) + { + r = &vargs->reg_save_area[4 * vargs->gprIndex]; + vargs->gprIndex++; + return r; + } + r = vargs->overflow_arg_area; + vargs->overflow_arg_area += 4; + return r; + case ppc_va_type::INT64: + if ( (vargs->gprIndex & 1) != 0 ) + vargs->gprIndex++; + if ( vargs->gprIndex < 8 ) + { + r = &vargs->reg_save_area[4 * vargs->gprIndex]; + vargs->gprIndex += 2; + return r; + } + vargs->overflow_arg_area = {(vargs->overflow_arg_area.GetMPTR()+7) & 0xFFFFFFF8}; + r = vargs->overflow_arg_area; + vargs->overflow_arg_area += 8; + return r; + case ppc_va_type::FLOAT_OR_DOUBLE: + if ( vargs->fprIndex < 8 ) + { + r = &vargs->reg_save_area[0x20 + 8 * vargs->fprIndex]; + vargs->fprIndex++; + return r; + } + vargs->overflow_arg_area = {(vargs->overflow_arg_area.GetMPTR()+7) & 0xFFFFFFF8}; + r = vargs->overflow_arg_area; + vargs->overflow_arg_area += 8; + return r; + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index 49d232f8..00327a97 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -1,6 +1,6 @@ #include "Cafe/OS/common/OSCommon.h" #include "Common/SysAllocator.h" -#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" @@ -69,7 +69,7 @@ sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) return score; } -void DebugLogStackTrace(OSThread_t* thread, MPTR sp) +void DebugLogStackTrace(OSThread_t* thread, MPTR sp, bool printSymbols) { // sp might not point to a valid stackframe // scan stack and evaluate which sp is most likely the beginning of the stackframe @@ -107,7 +107,15 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp) uint32 returnAddress = 0; returnAddress = memory_readU32(nextStackPtr + 4); - cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddr {1:08x}", nextStackPtr, returnAddress)); + + RPLStoredSymbol* symbol = nullptr; + if(printSymbols) + symbol = rplSymbolStorage_getByClosestAddress(returnAddress); + + if(symbol) + cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x} ({}.{}+0x{:x})", nextStackPtr, returnAddress, (const char*)symbol->libName, (const char*)symbol->symbolName, returnAddress - symbol->address)); + else + cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x}", nextStackPtr, returnAddress)); currentStackPtr = nextStackPtr; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp index 7ddadcf1..552a610f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp @@ -2,8 +2,6 @@ #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - #define EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(__blockchain__) (MEMExpHeapHead2*)((uintptr_t)__blockchain__ - offsetof(MEMExpHeapHead2, expHeapHead) - offsetof(MEMExpHeapHead40_t, chainFreeBlocks)) namespace coreinit diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index e2b50661..71a7d6e2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -7,14 +7,9 @@ namespace coreinit { - - /* coreinit logging and string format */ - - sint32 ppcSprintf(const char* formatStr, char* strOut, sint32 maxLength, PPCInterpreter_t* hCPU, sint32 initialParamIndex) + sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs) { char tempStr[4096]; - sint32 integerParamIndex = initialParamIndex; - sint32 floatParamIndex = 0; sint32 writeIndex = 0; while (*formatStr) { @@ -101,8 +96,7 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); - integerParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -120,13 +114,12 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - MPTR strOffset = PPCInterpreter_getCallParamU32(hCPU, integerParamIndex); + MPTR strOffset = *(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32); sint32 tempLen = 0; if (strOffset == MPTR_NULL) tempLen = sprintf(tempStr, "NULL"); else tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset)); - integerParamIndex++; for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -136,25 +129,6 @@ namespace coreinit } strOut[std::min(maxLength - 1, writeIndex)] = '\0'; } - else if (*formatStr == 'f') - { - // float - formatStr++; - 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'; - sint32 tempLen = sprintf(tempStr, tempFormat, (float)hCPU->fpr[1 + floatParamIndex].fp0); - floatParamIndex++; - for (sint32 i = 0; i < tempLen; i++) - { - if (writeIndex >= maxLength) - break; - strOut[writeIndex] = tempStr[i]; - writeIndex++; - } - } else if (*formatStr == 'c') { // character @@ -164,8 +138,24 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); - integerParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32)); + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if (*formatStr == 'f' || *formatStr == 'g' || *formatStr == 'G') + { + formatStr++; + 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'; + sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -183,8 +173,7 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, (double)hCPU->fpr[1 + floatParamIndex].fp0); - floatParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -196,16 +185,13 @@ namespace coreinit else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X'))) { formatStr += 3; - // double (64bit) + // 64bit int 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; + sint32 tempLen = sprintf(tempStr, tempFormat, (uint64)*(uint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -223,10 +209,7 @@ namespace coreinit 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; + sint32 tempLen = sprintf(tempStr, tempFormat, (sint64)*(sint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -255,9 +238,12 @@ namespace coreinit return std::min(writeIndex, maxLength - 1); } + /* coreinit logging and string format */ + sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr) { - sint32 r = ppcSprintf(formatStr, outputStr, maxLength, PPCInterpreter_getCurrentInstance(), 3); + ppc_define_va_list(3, 0); + sint32 r = ppc_vprintf(formatStr, outputStr, maxLength, &vargs); return r; } @@ -322,32 +308,40 @@ namespace coreinit } } - void OSReport(const char* format) + void COSVReport(COSReportModule module, COSReportLevel level, const char* format, ppc_va_list* vargs) { - char buffer[1024 * 2]; - sint32 len = ppcSprintf(format, buffer, sizeof(buffer), PPCInterpreter_getCurrentInstance(), 1); - WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len); + char tmpBuffer[1024]; + sint32 len = ppc_vprintf(format, tmpBuffer, sizeof(tmpBuffer), vargs); + WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len); } - void OSVReport(const char* format, MPTR vaArgs) + void OSReport(const char* format) { - cemu_assert_unimplemented(); + ppc_define_va_list(1, 0); + COSVReport(COSReportModule::coreinit, COSReportLevel::Info, format, &vargs); + } + + void OSVReport(const char* format, ppc_va_list* vargs) + { + COSVReport(COSReportModule::coreinit, COSReportLevel::Info, format, vargs); } void COSWarn(int moduleId, const char* format) { - char buffer[1024 * 2]; - int prefixLen = sprintf(buffer, "[COSWarn-%d] ", moduleId); - sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 2); - WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); + ppc_define_va_list(2, 0); + char tmpBuffer[1024]; + int prefixLen = sprintf(tmpBuffer, "[COSWarn-%d] ", moduleId); + sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs); + WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen); } void OSLogPrintf(int ukn1, int ukn2, int ukn3, const char* format) { - char buffer[1024 * 2]; - int prefixLen = sprintf(buffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); - sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 4); - WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); + ppc_define_va_list(4, 0); + char tmpBuffer[1024]; + int prefixLen = sprintf(tmpBuffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); + sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs); + WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen); } void OSConsoleWrite(const char* strPtr, sint32 length) @@ -562,9 +556,11 @@ namespace coreinit s_transitionToForeground = false; cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); + + cafeExportRegister("coreinit", COSVReport, LogType::Placeholder); + cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSReport, LogType::Placeholder); cafeExportRegister("coreinit", OSVReport, LogType::Placeholder); - cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h index 7abba92f..36f6b06a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h @@ -26,5 +26,19 @@ namespace coreinit uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3); uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId); + enum class COSReportModule + { + coreinit = 0, + }; + + enum class COSReportLevel + { + Error = 0, + Warn = 1, + Info = 2 + }; + + sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs); + void miscInit(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp index 4c55c2b0..5201d441 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp @@ -140,7 +140,7 @@ namespace coreinit // we are in single-core mode and the lock will never be released unless we let other threads resume work // to avoid an infinite loop we have no choice but to yield the thread even it is in an uninterruptible state if( !OSIsInterruptEnabled() ) - cemuLog_log(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)"); + cemuLog_logOnce(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)"); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp index 9e5de19e..c144c384 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp @@ -310,7 +310,7 @@ namespace coreinit currentThread->mutexQueue.removeMutex(mutex); mutex->owner = nullptr; if (!mutex->threadQueue.isEmpty()) - mutex->threadQueue.wakeupSingleThreadWaitQueue(true); + mutex->threadQueue.wakeupSingleThreadWaitQueue(true, true); } // currentThread->cancelState = currentThread->cancelState & ~0x10000; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp index e37949d7..2f819c50 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp @@ -14,13 +14,10 @@ namespace coreinit return coreinit::MEMAllocFromExpHeapEx(_sysHeapHandle, size, alignment); } - void export_OSAllocFromSystem(PPCInterpreter_t* hCPU) + void OSFreeToSystem(void* ptr) { - ppcDefineParamU32(size, 0); - ppcDefineParamS32(alignment, 1); - MEMPTR mem = OSAllocFromSystem(size, alignment); - cemuLog_logDebug(LogType::Force, "OSAllocFromSystem(0x{:x}, {}) -> 0x{:08x}", size, alignment, mem.GetMPTR()); - osLib_returnFromFunction(hCPU, mem.GetMPTR()); + _sysHeapFreeCounter++; + coreinit::MEMFreeToExpHeap(_sysHeapHandle, ptr); } void InitSysHeap() @@ -34,7 +31,8 @@ namespace coreinit void InitializeSysHeap() { - osLib_addFunction("coreinit", "OSAllocFromSystem", export_OSAllocFromSystem); + cafeExportRegister("h264", OSAllocFromSystem, LogType::CoreinitMem); + cafeExportRegister("h264", OSFreeToSystem, LogType::CoreinitMem); } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h index 428224af..ad115754 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h @@ -4,5 +4,8 @@ namespace coreinit { void InitSysHeap(); + void* OSAllocFromSystem(uint32 size, uint32 alignment); + void OSFreeToSystem(void* ptr); + void InitializeSysHeap(); } \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index fbf498db..2f3808b7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -758,14 +758,14 @@ namespace coreinit } // returns true if thread runs on same core and has higher priority - bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread) + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround) { uint32 coreIndex = OSGetCoreId(); if (!newThread->context.hasCoreAffinitySet(coreIndex)) return false; // special case: if current and new thread are running only on the same core then reschedule even if priority is equal // this resolves a deadlock in Just Dance 2019 where one thread would always reacquire the same mutex within it's timeslice, blocking another thread on the same core from acquiring it - if ((1<context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) + if (sharedPriorityAndAffinityWorkaround && (1<context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) return true; // otherwise reschedule if new thread has higher priority return newThread->effectivePriority < currentThread->effectivePriority; @@ -791,7 +791,7 @@ namespace coreinit // todo - only set this once? thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); // reschedule if thread has higher priority - if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, false)) PPCCore_switchToSchedulerWithLock(); } return previousSuspendCount; @@ -948,7 +948,7 @@ namespace coreinit OSThread_t* currentThread = OSGetCurrentThread(); if (currentThread && currentThread != thread) { - if (__OSCoreShouldSwitchToThread(currentThread, thread)) + if (__OSCoreShouldSwitchToThread(currentThread, thread, false)) PPCCore_switchToSchedulerWithLock(); } __OSUnlockScheduler(); @@ -1608,21 +1608,3 @@ namespace coreinit } } - -void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count) -{ - // for legacy source - OSThreadBE->suspendCounter += count; -} - -void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count) -{ - __OSLockScheduler(); - coreinit::__OSResumeThreadInternal(OSThreadBE, count); - __OSUnlockScheduler(); -} - -OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU) -{ - return coreinit::__currentCoreThread[PPCInterpreter_getCoreIndex(hCPU)]; -} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index fdbcfea7..df787bf0 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -126,8 +126,8 @@ namespace coreinit // counterparts for queueAndWait void cancelWait(OSThread_t* thread); - void wakeupEntireWaitQueue(bool reschedule); - void wakeupSingleThreadWaitQueue(bool reschedule); + void wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); + void wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); private: OSThread_t* takeFirstFromQueue(size_t linkOffset) @@ -611,7 +611,7 @@ namespace coreinit // internal void __OSAddReadyThreadToRunQueue(OSThread_t* thread); - bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread); + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround); void __OSQueueThreadDeallocation(OSThread_t* thread); bool __OSIsThreadActive(OSThread_t* thread); @@ -621,11 +621,6 @@ namespace coreinit #pragma pack() // deprecated / clean up required -void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count = 1); -void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count = 1); - -OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU); - extern MPTR activeThread[256]; extern sint32 activeThreadCount; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp index 68cb22b3..b33aa888 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp @@ -128,7 +128,8 @@ namespace coreinit // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) - void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule) + // sharedPriorityAndAffinityWorkaround is currently a hack/placeholder for some special cases. A proper fix likely involves handling all the nuances of thread effective priority + void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); bool shouldReschedule = false; @@ -139,7 +140,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) @@ -148,7 +149,7 @@ namespace coreinit // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) - void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule) + void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink)); @@ -159,7 +160,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp index 804e3da0..ec96a4ff 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -154,14 +154,6 @@ namespace GX2 return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL; } - bool GX2WriteGather_isDisplayListActive() - { - uint32 coreIndex = coreinit::OSGetCoreId(); - if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) - return true; - return false; - } - uint32 GX2WriteGather_getReadWriteDistance() { uint32 coreIndex = sGX2MainCoreIndex; diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.h b/src/Cafe/OS/libs/gx2/GX2_Command.h index 635680e0..51c04928 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.h +++ b/src/Cafe/OS/libs/gx2/GX2_Command.h @@ -84,8 +84,6 @@ inline void gx2WriteGather_submit(Targs... args) namespace GX2 { - - bool GX2WriteGather_isDisplayListActive(); uint32 GX2WriteGather_getReadWriteDistance(); void GX2WriteGather_checkAndInsertWrapAroundMark(); @@ -96,6 +94,8 @@ namespace GX2 void GX2CallDisplayList(MPTR addr, uint32 size); void GX2DirectCallDisplayList(void* addr, uint32 size); + bool GX2GetDisplayListWriteStatus(); + void GX2Init_writeGather(); void GX2CommandInit(); void GX2CommandResetToDefaultState(); diff --git a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp index a4cfc93d..cf150b47 100644 --- a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp @@ -291,8 +291,7 @@ void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU) void _GX2ContextCreateRestoreStateDL(GX2ContextState_t* gx2ContextState) { // begin display list - if (GX2::GX2WriteGather_isDisplayListActive()) - assert_dbg(); + cemu_assert_debug(!GX2::GX2GetDisplayListWriteStatus()); // must not already be writing to a display list GX2::GX2BeginDisplayList((void*)gx2ContextState->loadDL_buffer, sizeof(gx2ContextState->loadDL_buffer)); _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); uint32 displayListSize = GX2::GX2EndDisplayList((void*)gx2ContextState->loadDL_buffer); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index d004288b..dfbbfcff 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -426,7 +426,7 @@ namespace GX2 } if((aluRegisterOffset+sizeInU32s) > 0x400) { - cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 0x400)", aluRegisterOffset, sizeInU32s); + cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 1024)", aluRegisterOffset, sizeInU32s); } if( (sizeInU32s&3) != 0) { diff --git a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp index 024965fd..82db039b 100644 --- a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp +++ b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp @@ -1,17 +1,12 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" +#include "Cafe/OS/libs/h264_avc/H264DecInternal.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "Cafe/CafeSystem.h" #include "h264dec.h" -extern "C" -{ -#include "../dependencies/ih264d/common/ih264_typedefs.h" -#include "../dependencies/ih264d/decoder/ih264d.h" -}; - enum class H264DEC_STATUS : uint32 { SUCCESS = 0x0, @@ -33,10 +28,35 @@ namespace H264 return false; } + struct H264Context + { + struct + { + MEMPTR ptr{ nullptr }; + uint32be length{ 0 }; + float64be timestamp; + }BitStream; + struct + { + MEMPTR outputFunc{ nullptr }; + uint8be outputPerFrame{ 0 }; // whats the default? + MEMPTR userMemoryParam{ nullptr }; + }Param; + // misc + uint32be sessionHandle; + + // decoder state + struct + { + uint32 numFramesInFlight{0}; + }decoderState; + }; + uint32 H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut) { if (H264_IsBotW()) { + static_assert(sizeof(H264Context) < 256); *sizeRequirementOut = 256; return 0; } @@ -169,590 +189,47 @@ namespace H264 return H264DEC_STATUS::BAD_STREAM; } - struct H264Context - { - struct - { - MEMPTR ptr{ nullptr }; - uint32be length{ 0 }; - float64be timestamp; - }BitStream; - struct - { - MEMPTR outputFunc{ nullptr }; - uint8be outputPerFrame{ 0 }; // whats the default? - MEMPTR userMemoryParam{ nullptr }; - }Param; - // misc - uint32be sessionHandle; - }; - - class H264AVCDecoder - { - static void* ivd_aligned_malloc(void* ctxt, WORD32 alignment, WORD32 size) - { -#ifdef _WIN32 - return _aligned_malloc(size, alignment); -#else - // alignment is atleast sizeof(void*) - alignment = std::max(alignment, sizeof(void*)); - - //smallest multiple of 2 at least as large as alignment - alignment--; - alignment |= alignment << 1; - alignment |= alignment >> 1; - alignment |= alignment >> 2; - alignment |= alignment >> 4; - alignment |= alignment >> 8; - alignment |= alignment >> 16; - alignment ^= (alignment >> 1); - - void* temp; - posix_memalign(&temp, (size_t)alignment, (size_t)size); - return temp; -#endif - } - - static void ivd_aligned_free(void* ctxt, void* buf) - { -#ifdef _WIN32 - _aligned_free(buf); -#else - free(buf); -#endif - return; - } - - public: - struct DecodeResult - { - bool frameReady{ false }; - double timestamp; - void* imageOutput; - ivd_video_decode_op_t decodeOutput; - }; - - void Init(bool isBufferedMode) - { - ih264d_create_ip_t s_create_ip{ 0 }; - ih264d_create_op_t s_create_op{ 0 }; - - s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); - s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; - s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 1; // shared display buffer mode -> We give the decoder a list of buffers that it will use (?) - - s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); - s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; - s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; - s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; - s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; - - WORD32 status = ih264d_api_function(m_codecCtx, &s_create_ip, &s_create_op); - cemu_assert(!status); - - m_codecCtx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; - m_codecCtx->pv_fxns = (void*)&ih264d_api_function; - m_codecCtx->u4_size = sizeof(iv_obj_t); - - SetDecoderCoreCount(1); - - m_isBufferedMode = isBufferedMode; - - UpdateParameters(false); - - m_bufferedResults.clear(); - m_numDecodedFrames = 0; - m_hasBufferSizeInfo = false; - m_timestampIndex = 0; - } - - void Destroy() - { - if (!m_codecCtx) - return; - ih264d_delete_ip_t s_delete_ip{ 0 }; - ih264d_delete_op_t s_delete_op{ 0 }; - s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); - s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; - s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); - WORD32 status = ih264d_api_function(m_codecCtx, &s_delete_ip, &s_delete_op); - cemu_assert_debug(!status); - 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 - ih264d_create_ip_t s_create_ip{ 0 }; - ih264d_create_op_t s_create_op{ 0 }; - s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); - s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; - s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0; - s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); - s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; - s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; - s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; - s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; - iv_obj_t* ctx = nullptr; - WORD32 status = ih264d_api_function(ctx, &s_create_ip, &s_create_op); - cemu_assert_debug(!status); - if (status != IV_SUCCESS) - return false; - ctx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; - ctx->pv_fxns = (void*)&ih264d_api_function; - ctx->u4_size = sizeof(iv_obj_t); - // set header-only mode - ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; - ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; - ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; - ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; - ps_ctl_ip->u4_disp_wd = 0; - ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; - ps_ctl_ip->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; - ps_ctl_ip->e_vid_dec_mode = IVD_DECODE_HEADER; - ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; - ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; - ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); - ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); - status = ih264d_api_function(ctx, &s_h264d_ctl_ip, &s_h264d_ctl_op); - cemu_assert(!status); - // decode stream - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - s_dec_ip.pv_stream_buffer = stream; - s_dec_ip.u4_num_Bytes = length; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - - s_dec_op.u4_raw_wd = 0; - s_dec_op.u4_raw_ht = 0; - - status = ih264d_api_function(ctx, &s_dec_ip, &s_dec_op); - //cemu_assert(status == 0); -> This errors when not both the headers are present, but it will still set the parameters we need - bool isValid = false; - if (true)//status == 0) - { - imageWidth = s_dec_op.u4_raw_wd; - imageHeight = s_dec_op.u4_raw_ht; - cemu_assert_debug(imageWidth != 0 && imageHeight != 0); - isValid = true; - } - // destroy decoder - ih264d_delete_ip_t s_delete_ip{ 0 }; - ih264d_delete_op_t s_delete_op{ 0 }; - s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); - s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; - s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); - status = ih264d_api_function(ctx, &s_delete_ip, &s_delete_op); - cemu_assert_debug(!status); - return isValid; - } - - void Decode(void* data, uint32 length, double timestamp, void* imageOutput, DecodeResult& decodeResult) - { - if (!m_hasBufferSizeInfo) - { - uint32 numByteConsumed = 0; - if (!DetermineBufferSizes(data, length, numByteConsumed)) - { - cemuLog_log(LogType::Force, "H264: Unable to determine picture size. Ignoring decode input"); - decodeResult.frameReady = false; - return; - } - length -= numByteConsumed; - data = (uint8*)data + numByteConsumed; - m_hasBufferSizeInfo = true; - } - - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - - // remember timestamp and associated output buffer - m_timestamps[m_timestampIndex] = timestamp; - m_imageBuffers[m_timestampIndex] = imageOutput; - s_dec_ip.u4_ts = m_timestampIndex; - m_timestampIndex = (m_timestampIndex + 1) % 64; - - s_dec_ip.pv_stream_buffer = (uint8*)data; - s_dec_ip.u4_num_Bytes = length; - - s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; - s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - - BenchmarkTimer bt; - bt.Start(); - WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); - if (status != 0 && (s_dec_op.u4_error_code&0xFF) == IVD_RES_CHANGED) - { - // resolution change - ResetDecoder(); - m_hasBufferSizeInfo = false; - Decode(data, length, timestamp, imageOutput, decodeResult); - return; - } - else if (status != 0) - { - cemuLog_log(LogType::Force, "H264: Failed to decode frame (error 0x{:08x})", status); - decodeResult.frameReady = false; - return; - } - - bt.Stop(); - double decodeTime = bt.GetElapsedMilliseconds(); - - cemu_assert(s_dec_op.u4_frame_decoded_flag); - cemu_assert_debug(s_dec_op.u4_num_bytes_consumed == length); - - cemu_assert_debug(m_isBufferedMode || s_dec_op.u4_output_present); // if buffered mode is disabled, then every input should output a frame (except for partial slices?) - - if (s_dec_op.u4_output_present) - { - cemu_assert(s_dec_op.e_output_format == IV_YUV_420SP_UV); - if (H264_IsBotW()) - { - if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) - s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; - } - DecodeResult tmpResult; - tmpResult.frameReady = s_dec_op.u4_output_present != 0; - tmpResult.timestamp = m_timestamps[s_dec_op.u4_ts]; - tmpResult.imageOutput = m_imageBuffers[s_dec_op.u4_ts]; - tmpResult.decodeOutput = s_dec_op; - AddBufferedResult(tmpResult); - // transfer image to PPC output buffer and also correct stride - bt.Start(); - CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_imageBuffers[s_dec_op.u4_ts], s_dec_op); - bt.Stop(); - double copyTime = bt.GetElapsedMilliseconds(); - // release buffer - sint32 bufferId = -1; - for (size_t i = 0; i < m_displayBuf.size(); i++) - { - if (s_dec_op.s_disp_frm_buf.pv_y_buf >= m_displayBuf[i].data() && s_dec_op.s_disp_frm_buf.pv_y_buf < (m_displayBuf[i].data() + m_displayBuf[i].size())) - { - bufferId = (sint32)i; - break; - } - } - cemu_assert_debug(bufferId == s_dec_op.u4_disp_buf_id); - cemu_assert(bufferId >= 0); - ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; - ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; - s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; - s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); - s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); - s_video_rel_disp_ip.u4_disp_buf_id = bufferId; - status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); - cemu_assert(!status); - - cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms CopyTime {}ms", decodeTime, copyTime); - } - else - { - cemuLog_log(LogType::H264, "H264Bench | DecodeTime{}ms", decodeTime); - } - - if (s_dec_op.u4_frame_decoded_flag) - m_numDecodedFrames++; - - if (m_isBufferedMode) - { - // in buffered mode, always buffer 5 frames regardless of actual reordering and decoder latency - if (m_numDecodedFrames > 5) - GetCurrentBufferedResult(decodeResult); - } - else if(m_numDecodedFrames > 0) - GetCurrentBufferedResult(decodeResult); - - // get VUI - //ih264d_ctl_get_vui_params_ip_t s_ctl_get_vui_params_ip; - //ih264d_ctl_get_vui_params_op_t s_ctl_get_vui_params_op; - - //s_ctl_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; - //s_ctl_get_vui_params_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_GET_VUI_PARAMS; - //s_ctl_get_vui_params_ip.u4_size = sizeof(ih264d_ctl_get_vui_params_ip_t); - //s_ctl_get_vui_params_op.u4_size = sizeof(ih264d_ctl_get_vui_params_op_t); - - //status = ih264d_api_function(mCodecCtx, &s_ctl_get_vui_params_ip, &s_ctl_get_vui_params_op); - //cemu_assert(status == 0); - } - - std::vector Flush() - { - std::vector results; - // set flush mode - ivd_ctl_flush_ip_t s_video_flush_ip{ 0 }; - ivd_ctl_flush_op_t s_video_flush_op{ 0 }; - s_video_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_video_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; - s_video_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); - s_video_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); - WORD32 status = ih264d_api_function(m_codecCtx, &s_video_flush_ip, &s_video_flush_op); - if (status != 0) - cemuLog_log(LogType::Force, "H264Dec: Unexpected error during flush ({})", status); - // get all frames from the codec - while (true) - { - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - s_dec_ip.pv_stream_buffer = NULL; - s_dec_ip.u4_num_Bytes = 0; - s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; - s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); - if (status != 0) - break; - cemu_assert_debug(s_dec_op.u4_output_present != 0); // should never be zero? - if(s_dec_op.u4_output_present == 0) - continue; - if (H264_IsBotW()) - { - if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) - s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; - } - DecodeResult tmpResult; - tmpResult.frameReady = s_dec_op.u4_output_present != 0; - tmpResult.timestamp = m_timestamps[s_dec_op.u4_ts]; - tmpResult.imageOutput = m_imageBuffers[s_dec_op.u4_ts]; - tmpResult.decodeOutput = s_dec_op; - AddBufferedResult(tmpResult); - CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_imageBuffers[s_dec_op.u4_ts], s_dec_op); - } - results = std::move(m_bufferedResults); - return results; - } - - void CopyImageToResultBuffer(uint8* yIn, uint8* uvIn, uint8* bufOut, ivd_video_decode_op_t& decodeInfo) - { - uint32 imageWidth = decodeInfo.s_disp_frm_buf.u4_y_wd; - uint32 imageHeight = decodeInfo.s_disp_frm_buf.u4_y_ht; - - size_t inputStride = decodeInfo.s_disp_frm_buf.u4_y_strd; - size_t outputStride = (imageWidth + 0xFF) & ~0xFF; - - // copy Y - uint8* yOut = bufOut; - for (uint32 row = 0; row < imageHeight; row++) - { - memcpy(yOut, yIn, imageWidth); - yIn += inputStride; - yOut += outputStride; - } - - // copy UV - uint8* uvOut = bufOut + outputStride * imageHeight; - for (uint32 row = 0; row < imageHeight/2; row++) - { - memcpy(uvOut, uvIn, imageWidth); - uvIn += inputStride; - uvOut += outputStride; - } - } - - private: - - bool DetermineBufferSizes(void* data, uint32 length, uint32& numByteConsumed) - { - numByteConsumed = 0; - UpdateParameters(true); - - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - s_dec_ip.pv_stream_buffer = (uint8*)data; - s_dec_ip.u4_num_Bytes = length; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); - if (status != 0) - { - cemuLog_log(LogType::Force, "H264: Unable to determine buffer sizes for stream"); - return false; - } - numByteConsumed = s_dec_op.u4_num_bytes_consumed; - cemu_assert(status == 0); - if (s_dec_op.u4_pic_wd == 0 || s_dec_op.u4_pic_ht == 0) - return false; - UpdateParameters(false); - ReinitBuffers(); - return true; - } - - void ReinitBuffers() - { - ivd_ctl_getbufinfo_ip_t s_ctl_ip{ 0 }; - ivd_ctl_getbufinfo_op_t s_ctl_op{ 0 }; - WORD32 outlen = 0; - - s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_GETBUFINFO; - s_ctl_ip.u4_size = sizeof(ivd_ctl_getbufinfo_ip_t); - s_ctl_op.u4_size = sizeof(ivd_ctl_getbufinfo_op_t); - - WORD32 status = ih264d_api_function(m_codecCtx, &s_ctl_ip, &s_ctl_op); - cemu_assert(!status); - - // allocate - for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) - { - m_displayBuf.emplace_back().resize(s_ctl_op.u4_min_out_buf_size[0] + s_ctl_op.u4_min_out_buf_size[1]); - } - // set - ivd_set_display_frame_ip_t s_set_display_frame_ip{ 0 }; // make sure to zero-initialize this. The codec seems to check the first 3 pointers/sizes per frame, regardless of the value of u4_num_bufs - ivd_set_display_frame_op_t s_set_display_frame_op{ 0 }; - - s_set_display_frame_ip.e_cmd = IVD_CMD_SET_DISPLAY_FRAME; - s_set_display_frame_ip.u4_size = sizeof(ivd_set_display_frame_ip_t); - s_set_display_frame_op.u4_size = sizeof(ivd_set_display_frame_op_t); - - cemu_assert_debug(s_ctl_op.u4_min_num_out_bufs == 2); - cemu_assert_debug(s_ctl_op.u4_min_out_buf_size[0] != 0 && s_ctl_op.u4_min_out_buf_size[1] != 0); - - s_set_display_frame_ip.num_disp_bufs = s_ctl_op.u4_num_disp_bufs; - - for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) - { - s_set_display_frame_ip.s_disp_buffer[i].u4_num_bufs = 2; - s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[0] = s_ctl_op.u4_min_out_buf_size[0]; - s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[1] = s_ctl_op.u4_min_out_buf_size[1]; - s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[0] = m_displayBuf[i].data() + 0; - s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[1] = m_displayBuf[i].data() + s_ctl_op.u4_min_out_buf_size[0]; - } - - status = ih264d_api_function(m_codecCtx, &s_set_display_frame_ip, &s_set_display_frame_op); - cemu_assert(!status); - - - // mark all as released (available) - for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) - { - ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; - ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; - - s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; - s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); - s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); - s_video_rel_disp_ip.u4_disp_buf_id = i; - - status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); - cemu_assert(!status); - } - } - - void ResetDecoder() - { - ivd_ctl_reset_ip_t s_ctl_ip; - ivd_ctl_reset_op_t s_ctl_op; - - s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_RESET; - s_ctl_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); - s_ctl_op.u4_size = sizeof(ivd_ctl_reset_op_t); - - WORD32 status = ih264d_api_function(m_codecCtx, (void*)&s_ctl_ip, (void*)&s_ctl_op); - cemu_assert_debug(status == 0); - } - - void UpdateParameters(bool headerDecodeOnly) - { - ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; - ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; - ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; - ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; - - ps_ctl_ip->u4_disp_wd = 0; - ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; - ps_ctl_ip->e_frm_out_mode = m_isBufferedMode ? IVD_DISPLAY_FRAME_OUT : IVD_DECODE_FRAME_OUT; - ps_ctl_ip->e_vid_dec_mode = headerDecodeOnly ? IVD_DECODE_HEADER : IVD_DECODE_FRAME; - ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; - ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; - ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); - ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); - - WORD32 status = ih264d_api_function(m_codecCtx, &s_h264d_ctl_ip, &s_h264d_ctl_op); - cemu_assert(status == 0); - } - - /* In non-flush mode we have a delay of (at least?) 5 frames */ - void AddBufferedResult(DecodeResult& decodeResult) - { - if (decodeResult.frameReady) - m_bufferedResults.emplace_back(decodeResult); - } - - void GetCurrentBufferedResult(DecodeResult& decodeResult) - { - cemu_assert(!m_bufferedResults.empty()); - if (m_bufferedResults.empty()) - { - decodeResult.frameReady = false; - return; - } - decodeResult = m_bufferedResults.front(); - m_bufferedResults.erase(m_bufferedResults.begin()); - } - private: - iv_obj_t* m_codecCtx{nullptr}; - bool m_hasBufferSizeInfo{ false }; - bool m_isBufferedMode{ false }; - double m_timestamps[64]; - void* m_imageBuffers[64]; - uint32 m_timestampIndex{0}; - std::vector m_bufferedResults; - uint32 m_numDecodedFrames{0}; - std::vector> m_displayBuf; - }; - H264DEC_STATUS H264DECGetImageSize(uint8* stream, uint32 length, uint32 offset, uint32be* outputWidth, uint32be* outputHeight) { - cemu_assert(offset <= length); - - uint32 imageWidth, imageHeight; - - if (H264AVCDecoder::GetImageInfo(stream, length, imageWidth, imageHeight)) + if(!stream || length < 4 || !outputWidth || !outputHeight) + return H264DEC_STATUS::INVALID_PARAM; + if( (offset+4) > length ) + return H264DEC_STATUS::INVALID_PARAM; + uint8* cur = stream + offset; + uint8* end = stream + length; + cur += 2; // we access cur[-2] and cur[-1] so we need to start at offset 2 + while(cur < end-2) { - if (H264_IsBotW()) + // check for start code + if(*cur != 1) { - if (imageWidth == 1920 && imageHeight == 1088) - imageHeight = 1080; + cur++; + continue; } - *outputWidth = imageWidth; - *outputHeight = imageHeight; + // check if this is a valid NAL header + if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1) + { + cur++; + continue; + } + uint8 nalHeader = cur[1]; + if((nalHeader & 0x1F) != 7) + { + cur++; + continue; + } + h264State_seq_parameter_set_t psp; + bool r = h264Parser_ParseSPS(cur+2, end-cur-2, psp); + if(!r) + { + cemu_assert_suspicious(); // should not happen + return H264DEC_STATUS::BAD_STREAM; + } + *outputWidth = (psp.pic_width_in_mbs_minus1 + 1) * 16; + *outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16; // affected by frame_mbs_only_flag? + return H264DEC_STATUS::SUCCESS; } - else - { - *outputWidth = 0; - *outputHeight = 0; - return H264DEC_STATUS::BAD_STREAM; - } - - return H264DEC_STATUS::SUCCESS; + return H264DEC_STATUS::BAD_STREAM; } uint32 H264DECInitParam(uint32 workMemorySize, void* workMemory) @@ -762,26 +239,28 @@ namespace H264 return 0; } - std::unordered_map sDecoderSessions; + std::unordered_map sDecoderSessions; std::mutex sDecoderSessionsMutex; std::atomic_uint32_t sCurrentSessionHandle{ 1 }; - static H264AVCDecoder* _CreateDecoderSession(uint32& handleOut) + H264DecoderBackend* CreateAVCDecoder(); + + static H264DecoderBackend* _CreateDecoderSession(uint32& handleOut) { std::unique_lock _lock(sDecoderSessionsMutex); handleOut = sCurrentSessionHandle.fetch_add(1); - H264AVCDecoder* session = new H264AVCDecoder(); + H264DecoderBackend* session = CreateAVCDecoder(); sDecoderSessions.try_emplace(handleOut, session); return session; } - static H264AVCDecoder* _AcquireDecoderSession(uint32 handle) + static H264DecoderBackend* _AcquireDecoderSession(uint32 handle) { std::unique_lock _lock(sDecoderSessionsMutex); auto it = sDecoderSessions.find(handle); if (it == sDecoderSessions.end()) return nullptr; - H264AVCDecoder* session = it->second; + H264DecoderBackend* session = it->second; if (sDecoderSessions.size() >= 5) { cemuLog_log(LogType::Force, "H264: Warning - more than 5 active sessions"); @@ -790,7 +269,7 @@ namespace H264 return session; } - static void _ReleaseDecoderSession(H264AVCDecoder* session) + static void _ReleaseDecoderSession(H264DecoderBackend* session) { std::unique_lock _lock(sDecoderSessionsMutex); @@ -802,7 +281,7 @@ namespace H264 auto it = sDecoderSessions.find(handle); if (it == sDecoderSessions.end()) return; - H264AVCDecoder* session = it->second; + H264DecoderBackend* session = it->second; session->Destroy(); delete session; sDecoderSessions.erase(it); @@ -830,45 +309,44 @@ namespace H264 uint32 H264DECBegin(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; - H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECBegin(): Invalid session"); return 0; } session->Init(ctx->Param.outputPerFrame == 0); + ctx->decoderState.numFramesInFlight = 0; _ReleaseDecoderSession(session); return 0; } - void H264DoFrameOutputCallback(H264Context* ctx, H264AVCDecoder::DecodeResult& decodeResult); - - void _async_H264DECEnd(coreinit::OSEvent* executeDoneEvent, H264AVCDecoder* session, H264Context* ctx, std::vector* decodeResultsOut) - { - *decodeResultsOut = session->Flush(); - coreinit::OSSignalEvent(executeDoneEvent); - } + void H264DoFrameOutputCallback(H264Context* ctx, H264DecoderBackend::DecodeResult& decodeResult); H264DEC_STATUS H264DECEnd(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; - H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECEnd(): Invalid session"); return H264DEC_STATUS::SUCCESS; } - StackAllocator executeDoneEvent; - 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); - _ReleaseDecoderSession(session); - if (!results.empty()) + coreinit::OSEvent* flushEvt = &session->GetFlushEvent(); + coreinit::OSResetEvent(flushEvt); + session->QueueFlush(); + coreinit::OSWaitEvent(flushEvt); + while(true) { - for (auto& itr : results) - H264DoFrameOutputCallback(ctx, itr); + H264DecoderBackend::DecodeResult decodeResult; + if( !session->GetFrameOutputIfReady(decodeResult) ) + break; + // todo - output all frames in a single callback? + H264DoFrameOutputCallback(ctx, decodeResult); + ctx->decoderState.numFramesInFlight--; } + cemu_assert_debug(ctx->decoderState.numFramesInFlight == 0); // no frames should be in flight anymore. Exact behavior is not well understood but we may have to output dummy frames if necessary + _ReleaseDecoderSession(session); return H264DEC_STATUS::SUCCESS; } @@ -930,7 +408,6 @@ namespace H264 return 0; } - struct H264DECFrameOutput { /* +0x00 */ uint32be result; @@ -967,7 +444,7 @@ namespace H264 static_assert(sizeof(H264OutputCBStruct) == 12); - void H264DoFrameOutputCallback(H264Context* ctx, H264AVCDecoder::DecodeResult& decodeResult) + void H264DoFrameOutputCallback(H264Context* ctx, H264DecoderBackend::DecodeResult& decodeResult) { sint32 outputFrameCount = 1; @@ -984,14 +461,14 @@ namespace H264 frameOutput->imagePtr = (uint8*)decodeResult.imageOutput; frameOutput->result = 100; frameOutput->timestamp = decodeResult.timestamp; - frameOutput->frameWidth = decodeResult.decodeOutput.u4_pic_wd; - frameOutput->frameHeight = decodeResult.decodeOutput.u4_pic_ht; - frameOutput->bytesPerRow = (decodeResult.decodeOutput.u4_pic_wd + 0xFF) & ~0xFF; - frameOutput->cropEnable = decodeResult.decodeOutput.u1_frame_cropping_flag; - frameOutput->cropTop = decodeResult.decodeOutput.u1_frame_cropping_rect_top_ofst; - frameOutput->cropBottom = decodeResult.decodeOutput.u1_frame_cropping_rect_bottom_ofst; - frameOutput->cropLeft = decodeResult.decodeOutput.u1_frame_cropping_rect_left_ofst; - frameOutput->cropRight = decodeResult.decodeOutput.u1_frame_cropping_rect_right_ofst; + frameOutput->frameWidth = decodeResult.frameWidth; + frameOutput->frameHeight = decodeResult.frameHeight; + frameOutput->bytesPerRow = decodeResult.bytesPerRow; + frameOutput->cropEnable = decodeResult.cropEnable; + frameOutput->cropTop = decodeResult.cropTop; + frameOutput->cropBottom = decodeResult.cropBottom; + frameOutput->cropLeft = decodeResult.cropLeft; + frameOutput->cropRight = decodeResult.cropRight; StackAllocator stack_fptrOutputData; stack_fptrOutputData->frameCount = outputFrameCount; @@ -1006,29 +483,41 @@ namespace H264 } } - void _async_H264DECExecute(coreinit::OSEvent* executeDoneEvent, H264AVCDecoder* session, H264Context* ctx, void* imageOutput, H264AVCDecoder::DecodeResult* decodeResult) - { - session->Decode(ctx->BitStream.ptr.GetPtr(), ctx->BitStream.length, ctx->BitStream.timestamp, imageOutput, *decodeResult); - coreinit::OSSignalEvent(executeDoneEvent); - } - uint32 H264DECExecute(void* workMemory, void* imageOutput) { + BenchmarkTimer bt; + bt.Start(); H264Context* ctx = (H264Context*)workMemory; - H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECExecute(): Invalid session"); return 0; } - StackAllocator executeDoneEvent; - 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, session, ctx, imageOutput , &decodeResult); - coreinit::OSWaitEvent(&executeDoneEvent); + // feed data to backend + session->QueueForDecode((uint8*)ctx->BitStream.ptr.GetPtr(), ctx->BitStream.length, ctx->BitStream.timestamp, imageOutput); + ctx->decoderState.numFramesInFlight++; + // H264DECExecute is synchronous and will return a frame after either every call (non-buffered) or after 6 calls (buffered) + // normally frame decoding happens only during H264DECExecute, but in order to hide the latency of our CPU decoder we will decode asynchronously in buffered mode + uint32 numFramesToBuffer = (ctx->Param.outputPerFrame == 0) ? 5 : 0; + if(ctx->decoderState.numFramesInFlight > numFramesToBuffer) + { + ctx->decoderState.numFramesInFlight--; + while(true) + { + coreinit::OSEvent& evt = session->GetFrameOutputEvent(); + coreinit::OSWaitEvent(&evt); + H264DecoderBackend::DecodeResult decodeResult; + if( !session->GetFrameOutputIfReady(decodeResult) ) + continue; + H264DoFrameOutputCallback(ctx, decodeResult); + break; + } + } _ReleaseDecoderSession(session); - if(decodeResult.frameReady) - H264DoFrameOutputCallback(ctx, decodeResult); + bt.Stop(); + double callTime = bt.GetElapsedMilliseconds(); + cemuLog_log(LogType::H264, "H264Bench | H264DECExecute took {}ms", callTime); return 0x80 | 100; } diff --git a/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp b/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp new file mode 100644 index 00000000..228f65a5 --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp @@ -0,0 +1,502 @@ +#include "H264DecInternal.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +extern "C" +{ +#include "../dependencies/ih264d/common/ih264_typedefs.h" +#include "../dependencies/ih264d/decoder/ih264d.h" +}; + +namespace H264 +{ + bool H264_IsBotW(); + + class H264AVCDecoder : public H264DecoderBackend + { + static void* ivd_aligned_malloc(void* ctxt, WORD32 alignment, WORD32 size) + { +#ifdef _WIN32 + return _aligned_malloc(size, alignment); +#else + // alignment is atleast sizeof(void*) + alignment = std::max(alignment, sizeof(void*)); + + //smallest multiple of 2 at least as large as alignment + alignment--; + alignment |= alignment << 1; + alignment |= alignment >> 1; + alignment |= alignment >> 2; + alignment |= alignment >> 4; + alignment |= alignment >> 8; + alignment |= alignment >> 16; + alignment ^= (alignment >> 1); + + void* temp; + posix_memalign(&temp, (size_t)alignment, (size_t)size); + return temp; +#endif + } + + static void ivd_aligned_free(void* ctxt, void* buf) + { +#ifdef _WIN32 + _aligned_free(buf); +#else + free(buf); +#endif + } + + public: + H264AVCDecoder() + { + m_decoderThread = std::thread(&H264AVCDecoder::DecoderThread, this); + } + + ~H264AVCDecoder() + { + m_threadShouldExit = true; + m_decodeSem.increment(); + if (m_decoderThread.joinable()) + m_decoderThread.join(); + } + + void Init(bool isBufferedMode) + { + ih264d_create_ip_t s_create_ip{ 0 }; + ih264d_create_op_t s_create_op{ 0 }; + + s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); + s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; + s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 1; // shared display buffer mode -> We give the decoder a list of buffers that it will use (?) + + s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); + s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; + s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; + s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; + s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; + + WORD32 status = ih264d_api_function(m_codecCtx, &s_create_ip, &s_create_op); + cemu_assert(!status); + + m_codecCtx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; + m_codecCtx->pv_fxns = (void*)&ih264d_api_function; + m_codecCtx->u4_size = sizeof(iv_obj_t); + + SetDecoderCoreCount(1); + + m_isBufferedMode = isBufferedMode; + + UpdateParameters(false); + + m_numDecodedFrames = 0; + m_hasBufferSizeInfo = false; + } + + void Destroy() + { + if (!m_codecCtx) + return; + ih264d_delete_ip_t s_delete_ip{ 0 }; + ih264d_delete_op_t s_delete_op{ 0 }; + s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); + s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; + s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); + WORD32 status = ih264d_api_function(m_codecCtx, &s_delete_ip, &s_delete_op); + cemu_assert_debug(!status); + m_codecCtx = nullptr; + } + + void PushDecodedFrame(ivd_video_decode_op_t& s_dec_op) + { + // copy image data outside of lock since its an expensive operation + CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_decodedSliceArray[s_dec_op.u4_ts].result.imageOutput, s_dec_op); + + std::unique_lock _l(m_decodeQueueMtx); + cemu_assert(s_dec_op.u4_ts < m_decodedSliceArray.size()); + auto& result = m_decodedSliceArray[s_dec_op.u4_ts]; + cemu_assert_debug(result.isUsed); + cemu_assert_debug(s_dec_op.u4_output_present != 0); + + result.result.isDecoded = true; + result.result.hasFrame = s_dec_op.u4_output_present != 0; + result.result.frameWidth = s_dec_op.u4_pic_wd; + result.result.frameHeight = s_dec_op.u4_pic_ht; + result.result.bytesPerRow = (s_dec_op.u4_pic_wd + 0xFF) & ~0xFF; + result.result.cropEnable = s_dec_op.u1_frame_cropping_flag; + result.result.cropTop = s_dec_op.u1_frame_cropping_rect_top_ofst; + result.result.cropBottom = s_dec_op.u1_frame_cropping_rect_bottom_ofst; + result.result.cropLeft = s_dec_op.u1_frame_cropping_rect_left_ofst; + result.result.cropRight = s_dec_op.u1_frame_cropping_rect_right_ofst; + + m_displayQueue.push_back(s_dec_op.u4_ts); + + _l.unlock(); + coreinit::OSSignalEvent(m_displayQueueEvt); + } + + // called from async worker thread + void Decode(DecodedSlice& decodedSlice) + { + if (!m_hasBufferSizeInfo) + { + uint32 numByteConsumed = 0; + if (!DetermineBufferSizes(decodedSlice.dataToDecode.m_data, decodedSlice.dataToDecode.m_length, numByteConsumed)) + { + cemuLog_log(LogType::Force, "H264AVC: Unable to determine picture size. Ignoring decode input"); + std::unique_lock _l(m_decodeQueueMtx); + decodedSlice.result.isDecoded = true; + decodedSlice.result.hasFrame = false; + coreinit::OSSignalEvent(m_displayQueueEvt); + return; + } + decodedSlice.dataToDecode.m_length -= numByteConsumed; + decodedSlice.dataToDecode.m_data = (uint8*)decodedSlice.dataToDecode.m_data + numByteConsumed; + m_hasBufferSizeInfo = true; + } + + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + + s_dec_ip.u4_ts = std::distance(m_decodedSliceArray.data(), &decodedSlice); + cemu_assert_debug(s_dec_ip.u4_ts < m_decodedSliceArray.size()); + + s_dec_ip.pv_stream_buffer = (uint8*)decodedSlice.dataToDecode.m_data; + s_dec_ip.u4_num_Bytes = decodedSlice.dataToDecode.m_length; + + s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + + BenchmarkTimer bt; + bt.Start(); + WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0 && (s_dec_op.u4_error_code&0xFF) == IVD_RES_CHANGED) + { + // resolution change + ResetDecoder(); + m_hasBufferSizeInfo = false; + Decode(decodedSlice); + return; + } + else if (status != 0) + { + cemuLog_log(LogType::Force, "H264: Failed to decode frame (error 0x{:08x})", status); + decodedSlice.result.hasFrame = false; + cemu_assert_unimplemented(); + return; + } + + bt.Stop(); + double decodeTime = bt.GetElapsedMilliseconds(); + + cemu_assert(s_dec_op.u4_frame_decoded_flag); + cemu_assert_debug(s_dec_op.u4_num_bytes_consumed == decodedSlice.dataToDecode.m_length); + + cemu_assert_debug(m_isBufferedMode || s_dec_op.u4_output_present); // if buffered mode is disabled, then every input should output a frame (except for partial slices?) + + if (s_dec_op.u4_output_present) + { + cemu_assert(s_dec_op.e_output_format == IV_YUV_420SP_UV); + if (H264_IsBotW()) + { + if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) + s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; + } + bt.Start(); + PushDecodedFrame(s_dec_op); + bt.Stop(); + double copyTime = bt.GetElapsedMilliseconds(); + // release buffer + sint32 bufferId = -1; + for (size_t i = 0; i < m_displayBuf.size(); i++) + { + if (s_dec_op.s_disp_frm_buf.pv_y_buf >= m_displayBuf[i].data() && s_dec_op.s_disp_frm_buf.pv_y_buf < (m_displayBuf[i].data() + m_displayBuf[i].size())) + { + bufferId = (sint32)i; + break; + } + } + cemu_assert_debug(bufferId == s_dec_op.u4_disp_buf_id); + cemu_assert(bufferId >= 0); + ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; + ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; + s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; + s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); + s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); + s_video_rel_disp_ip.u4_disp_buf_id = bufferId; + status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); + cemu_assert(!status); + + cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms CopyTime {}ms", decodeTime, copyTime); + } + else + { + cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms (no frame output)", decodeTime); + } + + if (s_dec_op.u4_frame_decoded_flag) + m_numDecodedFrames++; + // get VUI + //ih264d_ctl_get_vui_params_ip_t s_ctl_get_vui_params_ip; + //ih264d_ctl_get_vui_params_op_t s_ctl_get_vui_params_op; + + //s_ctl_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + //s_ctl_get_vui_params_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_GET_VUI_PARAMS; + //s_ctl_get_vui_params_ip.u4_size = sizeof(ih264d_ctl_get_vui_params_ip_t); + //s_ctl_get_vui_params_op.u4_size = sizeof(ih264d_ctl_get_vui_params_op_t); + + //status = ih264d_api_function(mCodecCtx, &s_ctl_get_vui_params_ip, &s_ctl_get_vui_params_op); + //cemu_assert(status == 0); + } + + void Flush() + { + // set flush mode + ivd_ctl_flush_ip_t s_video_flush_ip{ 0 }; + ivd_ctl_flush_op_t s_video_flush_op{ 0 }; + s_video_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_video_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; + s_video_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); + s_video_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); + WORD32 status = ih264d_api_function(m_codecCtx, &s_video_flush_ip, &s_video_flush_op); + if (status != 0) + cemuLog_log(LogType::Force, "H264Dec: Unexpected error during flush ({})", status); + // get all frames from the decoder + while (true) + { + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = NULL; + s_dec_ip.u4_num_Bytes = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0) + break; + cemu_assert_debug(s_dec_op.u4_output_present != 0); // should never be false? + if(s_dec_op.u4_output_present == 0) + continue; + if (H264_IsBotW()) + { + if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) + s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; + } + PushDecodedFrame(s_dec_op); + } + } + + void CopyImageToResultBuffer(uint8* yIn, uint8* uvIn, uint8* bufOut, ivd_video_decode_op_t& decodeInfo) + { + uint32 imageWidth = decodeInfo.s_disp_frm_buf.u4_y_wd; + uint32 imageHeight = decodeInfo.s_disp_frm_buf.u4_y_ht; + + size_t inputStride = decodeInfo.s_disp_frm_buf.u4_y_strd; + size_t outputStride = (imageWidth + 0xFF) & ~0xFF; + + // copy Y + uint8* yOut = bufOut; + for (uint32 row = 0; row < imageHeight; row++) + { + memcpy(yOut, yIn, imageWidth); + yIn += inputStride; + yOut += outputStride; + } + + // copy UV + uint8* uvOut = bufOut + outputStride * imageHeight; + for (uint32 row = 0; row < imageHeight/2; row++) + { + memcpy(uvOut, uvIn, imageWidth); + uvIn += inputStride; + uvOut += outputStride; + } + } + private: + 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); + } + + bool DetermineBufferSizes(void* data, uint32 length, uint32& numByteConsumed) + { + numByteConsumed = 0; + UpdateParameters(true); + + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = (uint8*)data; + s_dec_ip.u4_num_Bytes = length; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0) + { + cemuLog_log(LogType::Force, "H264: Unable to determine buffer sizes for stream"); + return false; + } + numByteConsumed = s_dec_op.u4_num_bytes_consumed; + cemu_assert(status == 0); + if (s_dec_op.u4_pic_wd == 0 || s_dec_op.u4_pic_ht == 0) + return false; + UpdateParameters(false); + ReinitBuffers(); + return true; + } + + void ReinitBuffers() + { + ivd_ctl_getbufinfo_ip_t s_ctl_ip{ 0 }; + ivd_ctl_getbufinfo_op_t s_ctl_op{ 0 }; + WORD32 outlen = 0; + + s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_GETBUFINFO; + s_ctl_ip.u4_size = sizeof(ivd_ctl_getbufinfo_ip_t); + s_ctl_op.u4_size = sizeof(ivd_ctl_getbufinfo_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, &s_ctl_ip, &s_ctl_op); + cemu_assert(!status); + + // allocate + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + m_displayBuf.emplace_back().resize(s_ctl_op.u4_min_out_buf_size[0] + s_ctl_op.u4_min_out_buf_size[1]); + } + // set + ivd_set_display_frame_ip_t s_set_display_frame_ip{ 0 }; // make sure to zero-initialize this. The codec seems to check the first 3 pointers/sizes per frame, regardless of the value of u4_num_bufs + ivd_set_display_frame_op_t s_set_display_frame_op{ 0 }; + + s_set_display_frame_ip.e_cmd = IVD_CMD_SET_DISPLAY_FRAME; + s_set_display_frame_ip.u4_size = sizeof(ivd_set_display_frame_ip_t); + s_set_display_frame_op.u4_size = sizeof(ivd_set_display_frame_op_t); + + cemu_assert_debug(s_ctl_op.u4_min_num_out_bufs == 2); + cemu_assert_debug(s_ctl_op.u4_min_out_buf_size[0] != 0 && s_ctl_op.u4_min_out_buf_size[1] != 0); + + s_set_display_frame_ip.num_disp_bufs = s_ctl_op.u4_num_disp_bufs; + + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + s_set_display_frame_ip.s_disp_buffer[i].u4_num_bufs = 2; + s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[0] = s_ctl_op.u4_min_out_buf_size[0]; + s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[1] = s_ctl_op.u4_min_out_buf_size[1]; + s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[0] = m_displayBuf[i].data() + 0; + s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[1] = m_displayBuf[i].data() + s_ctl_op.u4_min_out_buf_size[0]; + } + + status = ih264d_api_function(m_codecCtx, &s_set_display_frame_ip, &s_set_display_frame_op); + cemu_assert(!status); + + + // mark all as released (available) + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; + ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; + + s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; + s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); + s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); + s_video_rel_disp_ip.u4_disp_buf_id = i; + + status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); + cemu_assert(!status); + } + } + + void ResetDecoder() + { + ivd_ctl_reset_ip_t s_ctl_ip; + ivd_ctl_reset_op_t s_ctl_op; + + s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_RESET; + s_ctl_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); + s_ctl_op.u4_size = sizeof(ivd_ctl_reset_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, (void*)&s_ctl_ip, (void*)&s_ctl_op); + cemu_assert_debug(status == 0); + } + + void UpdateParameters(bool headerDecodeOnly) + { + ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; + ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; + ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; + ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; + + ps_ctl_ip->u4_disp_wd = 0; + ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; + ps_ctl_ip->e_frm_out_mode = m_isBufferedMode ? IVD_DISPLAY_FRAME_OUT : IVD_DECODE_FRAME_OUT; + ps_ctl_ip->e_vid_dec_mode = headerDecodeOnly ? IVD_DECODE_HEADER : IVD_DECODE_FRAME; + ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; + ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); + ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, &s_h264d_ctl_ip, &s_h264d_ctl_op); + cemu_assert(status == 0); + } + + private: + void DecoderThread() + { + while(!m_threadShouldExit) + { + m_decodeSem.decrementWithWait(); + std::unique_lock _l(m_decodeQueueMtx); + if (m_decodeQueue.empty()) + continue; + uint32 decodeIndex = m_decodeQueue.front(); + m_decodeQueue.erase(m_decodeQueue.begin()); + _l.unlock(); + if(decodeIndex == CMD_FLUSH) + { + Flush(); + _l.lock(); + cemu_assert_debug(m_decodeQueue.empty()); // after flushing the queue should be empty since the sender is waiting for the flush to complete + _l.unlock(); + coreinit::OSSignalEvent(m_flushEvt); + } + else + { + auto& decodedSlice = m_decodedSliceArray[decodeIndex]; + Decode(decodedSlice); + } + } + } + + iv_obj_t* m_codecCtx{nullptr}; + bool m_hasBufferSizeInfo{ false }; + bool m_isBufferedMode{ false }; + uint32 m_numDecodedFrames{0}; + std::vector> m_displayBuf; + + std::thread m_decoderThread; + std::atomic_bool m_threadShouldExit{false}; + }; + + H264DecoderBackend* CreateAVCDecoder() + { + return new H264AVCDecoder(); + } +}; diff --git a/src/Cafe/OS/libs/h264_avc/H264DecInternal.h b/src/Cafe/OS/libs/h264_avc/H264DecInternal.h new file mode 100644 index 00000000..498cccfa --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/H264DecInternal.h @@ -0,0 +1,139 @@ +#pragma once + +#include "util/helpers/Semaphore.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" + +#include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" + +namespace H264 +{ + class H264DecoderBackend + { + protected: + struct DataToDecode + { + uint8* m_data; + uint32 m_length; + std::vector m_buffer; + }; + + static constexpr uint32 CMD_FLUSH = 0xFFFFFFFF; + + public: + struct DecodeResult + { + bool isDecoded{false}; + bool hasFrame{false}; // set to true if a full frame was successfully decoded + double timestamp{}; + void* imageOutput{nullptr}; + sint32 frameWidth{0}; + sint32 frameHeight{0}; + uint32 bytesPerRow{0}; + bool cropEnable{false}; + sint32 cropTop{0}; + sint32 cropBottom{0}; + sint32 cropLeft{0}; + sint32 cropRight{0}; + }; + + struct DecodedSlice + { + bool isUsed{false}; + DecodeResult result; + DataToDecode dataToDecode; + }; + + H264DecoderBackend() + { + m_displayQueueEvt = (coreinit::OSEvent*)coreinit::OSAllocFromSystem(sizeof(coreinit::OSEvent), 4); + coreinit::OSInitEvent(m_displayQueueEvt, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + m_flushEvt = (coreinit::OSEvent*)coreinit::OSAllocFromSystem(sizeof(coreinit::OSEvent), 4); + coreinit::OSInitEvent(m_flushEvt, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + }; + + virtual ~H264DecoderBackend() + { + coreinit::OSFreeToSystem(m_displayQueueEvt); + coreinit::OSFreeToSystem(m_flushEvt); + }; + + virtual void Init(bool isBufferedMode) = 0; + virtual void Destroy() = 0; + + void QueueForDecode(uint8* data, uint32 length, double timestamp, void* imagePtr) + { + std::unique_lock _l(m_decodeQueueMtx); + + DecodedSlice& ds = GetFreeDecodedSliceEntry(); + + ds.dataToDecode.m_buffer.assign(data, data + length); + ds.dataToDecode.m_data = ds.dataToDecode.m_buffer.data(); + ds.dataToDecode.m_length = length; + + ds.result.isDecoded = false; + ds.result.imageOutput = imagePtr; + ds.result.timestamp = timestamp; + + m_decodeQueue.push_back(std::distance(m_decodedSliceArray.data(), &ds)); + m_decodeSem.increment(); + } + + void QueueFlush() + { + std::unique_lock _l(m_decodeQueueMtx); + m_decodeQueue.push_back(CMD_FLUSH); + m_decodeSem.increment(); + } + + bool GetFrameOutputIfReady(DecodeResult& result) + { + std::unique_lock _l(m_decodeQueueMtx); + if(m_displayQueue.empty()) + return false; + uint32 sliceIndex = m_displayQueue.front(); + DecodedSlice& ds = m_decodedSliceArray[sliceIndex]; + cemu_assert_debug(ds.result.isDecoded); + std::swap(result, ds.result); + ds.isUsed = false; + m_displayQueue.erase(m_displayQueue.begin()); + return true; + } + + coreinit::OSEvent& GetFrameOutputEvent() + { + return *m_displayQueueEvt; + } + + coreinit::OSEvent& GetFlushEvent() + { + return *m_flushEvt; + } + + protected: + DecodedSlice& GetFreeDecodedSliceEntry() + { + for (auto& slice : m_decodedSliceArray) + { + if (!slice.isUsed) + { + slice.isUsed = true; + return slice; + } + } + cemu_assert_suspicious(); + return m_decodedSliceArray[0]; + } + + std::mutex m_decodeQueueMtx; + std::vector m_decodeQueue; // indices into m_decodedSliceArray, in order of decode input + CounterSemaphore m_decodeSem; + std::vector m_displayQueue; // indices into m_decodedSliceArray, in order of frame display output + coreinit::OSEvent* m_displayQueueEvt; // signalled when a new frame is ready for display + coreinit::OSEvent* m_flushEvt; // signalled after flush operation finished and all queued slices are decoded + + // frame output queue + std::mutex m_frameOutputMtx; + std::array m_decodedSliceArray; + }; +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp index d77e551f..36f70f81 100644 --- a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp +++ b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp @@ -319,6 +319,17 @@ bool parseNAL_pic_parameter_set_rbsp(h264ParserState_t* h264ParserState, h264Par return true; } +bool h264Parser_ParseSPS(uint8* data, uint32 length, h264State_seq_parameter_set_t& sps) +{ + h264ParserState_t parserState; + RBSPInputBitstream nalStream(data, length); + bool r = parseNAL_seq_parameter_set_rbsp(&parserState, nullptr, nalStream); + if(!r || !parserState.hasSPS) + return false; + sps = parserState.sps; + return true; +} + void parseNAL_ref_pic_list_modification(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) { if (!sliceHeader->slice_type.isSliceTypeI() && !sliceHeader->slice_type.isSliceTypeSI()) @@ -688,9 +699,8 @@ void _calculateFrameOrder(h264ParserState_t* h264ParserState, const h264State_se else if (sps.pic_order_cnt_type == 2) { // display order matches decode order - uint32 prevFrameNum = h264ParserState->picture_order.prevFrameNum; - ; + uint32 FrameNumOffset; if (sliceHeader->IdrPicFlag) { @@ -706,9 +716,6 @@ void _calculateFrameOrder(h264ParserState_t* h264ParserState, const h264State_se FrameNumOffset = prevFrameNumOffset + sps.getMaxFrameNum(); else FrameNumOffset = prevFrameNumOffset; - - - } uint32 tempPicOrderCnt; diff --git a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h index ee32ca8b..6f2b3cf6 100644 --- a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h +++ b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h @@ -513,6 +513,8 @@ typedef struct void h264Parse(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, uint8* data, uint32 length, bool parseSlices = true); sint32 h264GetUnitLength(h264ParserState_t* h264ParserState, uint8* data, uint32 length); +bool h264Parser_ParseSPS(uint8* data, uint32 length, h264State_seq_parameter_set_t& sps); + void h264Parser_getScalingMatrix4x4(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix4x4); void h264Parser_getScalingMatrix8x8(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix8x8); diff --git a/src/Cafe/OS/libs/nfc/TLV.cpp b/src/Cafe/OS/libs/nfc/TLV.cpp new file mode 100644 index 00000000..2650858d --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.cpp @@ -0,0 +1,139 @@ +#include "TLV.h" +#include "stream.h" + +#include + +TLV::TLV() +{ +} + +TLV::TLV(Tag tag, std::vector value) + : mTag(tag), mValue(std::move(value)) +{ +} + +TLV::~TLV() +{ +} + +std::vector TLV::FromBytes(const std::span& data) +{ + bool hasTerminator = false; + std::vector tlvs; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0 && !hasTerminator) + { + // Read the tag + uint8 byte; + stream >> byte; + Tag tag = static_cast(byte); + + switch (tag) + { + case TLV::TAG_NULL: + // Don't need to do anything for NULL tags + break; + + case TLV::TAG_TERMINATOR: + tlvs.emplace_back(tag, std::vector{}); + hasTerminator = true; + break; + + default: + { + // Read the length + uint16 length; + stream >> byte; + length = byte; + + // If the length is 0xff, 2 bytes with length follow + if (length == 0xff) { + stream >> length; + } + + std::vector value; + value.resize(length); + stream.Read(value); + + tlvs.emplace_back(tag, value); + break; + } + } + + if (stream.GetError() != Stream::ERROR_OK) + { + cemuLog_log(LogType::Force, "Error: TLV parsing read past end of stream"); + // Clear tlvs to prevent further havoc while parsing ndef data + tlvs.clear(); + break; + } + } + + // This seems to be okay, at least NTAGs don't add a terminator tag + // if (!hasTerminator) + // { + // cemuLog_log(LogType::Force, "Warning: TLV parsing reached end of stream without terminator tag"); + // } + + return tlvs; +} + +std::vector TLV::ToBytes() const +{ + std::vector bytes; + VectorStream stream(bytes, std::endian::big); + + // Write tag + stream << uint8(mTag); + + switch (mTag) + { + case TLV::TAG_NULL: + case TLV::TAG_TERMINATOR: + // Nothing to do here + break; + + default: + { + // Write length (decide if as a 8-bit or 16-bit value) + if (mValue.size() >= 0xff) + { + stream << uint8(0xff); + stream << uint16(mValue.size()); + } + else + { + stream << uint8(mValue.size()); + } + + // Write value + stream.Write(mValue); + } + } + + return bytes; +} + +TLV::Tag TLV::GetTag() const +{ + return mTag; +} + +const std::vector& TLV::GetValue() const +{ + return mValue; +} + +void TLV::SetTag(Tag tag) +{ + mTag = tag; +} + +void TLV::SetValue(const std::span& value) +{ + // Can only write max 16-bit lengths into TLV + cemu_assert(value.size() < 0x10000); + + mValue.assign(value.begin(), value.end()); +} diff --git a/src/Cafe/OS/libs/nfc/TLV.h b/src/Cafe/OS/libs/nfc/TLV.h new file mode 100644 index 00000000..f582128f --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class TLV +{ +public: + enum Tag + { + TAG_NULL = 0x00, + TAG_LOCK_CTRL = 0x01, + TAG_MEM_CTRL = 0x02, + TAG_NDEF = 0x03, + TAG_PROPRIETARY = 0xFD, + TAG_TERMINATOR = 0xFE, + }; + +public: + TLV(); + TLV(Tag tag, std::vector value); + virtual ~TLV(); + + static std::vector FromBytes(const std::span& data); + std::vector ToBytes() const; + + Tag GetTag() const; + const std::vector& GetValue() const; + + void SetTag(Tag tag); + void SetValue(const std::span& value); + +private: + Tag mTag; + std::vector mValue; +}; diff --git a/src/Cafe/OS/libs/nfc/TagV0.cpp b/src/Cafe/OS/libs/nfc/TagV0.cpp new file mode 100644 index 00000000..41b5c7a0 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.cpp @@ -0,0 +1,301 @@ +#include "TagV0.h" +#include "TLV.h" + +#include + +namespace +{ + +constexpr std::size_t kTagSize = 512u; +constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); + +constexpr uint8 kLockbyteBlock0 = 0xe; +constexpr uint8 kLockbytesStart0 = 0x0; +constexpr uint8 kLockbytesEnd0 = 0x2; +constexpr uint8 kLockbyteBlock1 = 0xf; +constexpr uint8 kLockbytesStart1 = 0x2; +constexpr uint8 kLockbytesEnd1 = 0x8; + +constexpr uint8 kNDEFMagicNumber = 0xe1; + +// These blocks are not part of the locked area +constexpr bool IsBlockLockedOrReserved(uint8 blockIdx) +{ + // Block 0 is the UID + if (blockIdx == 0x0) + { + return true; + } + + // Block 0xd is reserved + if (blockIdx == 0xd) + { + return true; + } + + // Block 0xe and 0xf contains lock / reserved bytes + if (blockIdx == 0xe || blockIdx == 0xf) + { + return true; + } + + return false; +} + +} // namespace + +TagV0::TagV0() +{ +} + +TagV0::~TagV0() +{ +} + +std::shared_ptr TagV0::FromBytes(const std::span& data) +{ + // Version 0 tags need at least 512 bytes + if (data.size() != kTagSize) + { + cemuLog_log(LogType::Force, "Error: Version 0 tags should be {} bytes in size", kTagSize); + return {}; + } + + std::shared_ptr tag = std::make_shared(); + + // Parse the locked area before continuing + if (!tag->ParseLockedArea(data)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse locked area"); + return {}; + } + + // Now that the locked area is known, parse the data area + std::vector dataArea; + if (!tag->ParseDataArea(data, dataArea)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse data area"); + return {}; + } + + // The first few bytes in the dataArea make up the capability container + std::copy_n(dataArea.begin(), tag->mCapabilityContainer.size(), std::as_writable_bytes(std::span(tag->mCapabilityContainer)).begin()); + if (!tag->ValidateCapabilityContainer()) + { + cemuLog_log(LogType::Force, "Error: Failed to validate capability container"); + return {}; + } + + // The rest of the dataArea contains the TLVs + tag->mTLVs = TLV::FromBytes(std::span(dataArea).subspan(tag->mCapabilityContainer.size())); + if (tag->mTLVs.empty()) + { + cemuLog_log(LogType::Force, "Error: Tag contains no TLVs"); + return {}; + } + + // Look for the NDEF tlv + tag->mNdefTlvIdx = static_cast(-1); + for (std::size_t i = 0; i < tag->mTLVs.size(); i++) + { + if (tag->mTLVs[i].GetTag() == TLV::TAG_NDEF) + { + tag->mNdefTlvIdx = i; + break; + } + } + + if (tag->mNdefTlvIdx == static_cast(-1)) + { + cemuLog_log(LogType::Force, "Error: Tag contains no NDEF TLV"); + return {}; + } + + // Append locked data + for (const auto& [key, value] : tag->mLockedBlocks) + { + tag->mLockedArea.insert(tag->mLockedArea.end(), value.begin(), value.end()); + } + + return tag; +} + +std::vector TagV0::ToBytes() const +{ + std::vector bytes(kTagSize); + + // Insert locked or reserved blocks + for (const auto& [key, value] : mLockedOrReservedBlocks) + { + std::copy(value.begin(), value.end(), bytes.begin() + key * sizeof(Block)); + } + + // Insert locked area + auto lockedDataIterator = mLockedArea.begin(); + for (const auto& [key, value] : mLockedBlocks) + { + std::copy_n(lockedDataIterator, sizeof(Block), bytes.begin() + key * sizeof(Block)); + lockedDataIterator += sizeof(Block); + } + + // Pack the dataArea into a linear buffer + std::vector dataArea; + const auto ccBytes = std::as_bytes(std::span(mCapabilityContainer)); + dataArea.insert(dataArea.end(), ccBytes.begin(), ccBytes.end()); + for (const TLV& tlv : mTLVs) + { + const auto tlvBytes = tlv.ToBytes(); + dataArea.insert(dataArea.end(), tlvBytes.begin(), tlvBytes.end()); + } + + // Make sure the dataArea is block size aligned + dataArea.resize((dataArea.size() + (sizeof(Block)-1)) & ~(sizeof(Block)-1)); + + // The rest will be the data area + auto dataIterator = dataArea.begin(); + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + std::copy_n(dataIterator, sizeof(Block), bytes.begin() + currentBlock * sizeof(Block)); + dataIterator += sizeof(Block); + } + } + + return bytes; +} + +const TagV0::Block& TagV0::GetUIDBlock() const +{ + return mLockedOrReservedBlocks.at(0); +} + +const std::vector& TagV0::GetNDEFData() const +{ + return mTLVs[mNdefTlvIdx].GetValue(); +} + +const std::vector& TagV0::GetLockedArea() const +{ + return mLockedArea; +} + +void TagV0::SetNDEFData(const std::span& data) +{ + // Update the ndef value + mTLVs[mNdefTlvIdx].SetValue(data); +} + +bool TagV0::ParseLockedArea(const std::span& data) +{ + uint8 currentBlock = 0; + + // Start by parsing the first set of lock bytes + for (uint8 i = kLockbytesStart0; i < kLockbytesEnd0; i++) + { + uint8 lockByte = uint8(data[kLockbyteBlock0 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (uint8 j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + // Parse the second set of lock bytes + for (uint8 i = kLockbytesStart1; i < kLockbytesEnd1; i++) { + uint8 lockByte = uint8(data[kLockbyteBlock1 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (uint8 j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + return true; +} + +bool TagV0::IsBlockLocked(uint8 blockIdx) const +{ + return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); +} + +bool TagV0::ParseDataArea(const std::span& data, std::vector& dataArea) +{ + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + auto blockOffset = data.begin() + sizeof(Block) * currentBlock; + dataArea.insert(dataArea.end(), blockOffset, blockOffset + sizeof(Block)); + } + } + + return true; +} + +bool TagV0::ValidateCapabilityContainer() +{ + // NDEF Magic Number + uint8 nmn = mCapabilityContainer[0]; + if (nmn != kNDEFMagicNumber) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); + return false; + } + + // Version Number + uint8 vno = mCapabilityContainer[1]; + if (vno >> 4 != 1) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); + return false; + } + + // Tag memory size + uint8 tms = mCapabilityContainer[2]; + if (8u * (tms + 1) < kTagSize) + { + cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); + return false; + } + + return true; +} diff --git a/src/Cafe/OS/libs/nfc/TagV0.h b/src/Cafe/OS/libs/nfc/TagV0.h new file mode 100644 index 00000000..72c321b6 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#include "TLV.h" + +class TagV0 +{ +public: + using Block = std::array; + +public: + TagV0(); + virtual ~TagV0(); + + static std::shared_ptr FromBytes(const std::span& data); + std::vector ToBytes() const; + + const Block& GetUIDBlock() const; + const std::vector& GetNDEFData() const; + const std::vector& GetLockedArea() const; + + void SetNDEFData(const std::span& data); + +private: + bool ParseLockedArea(const std::span& data); + bool IsBlockLocked(uint8 blockIdx) const; + bool ParseDataArea(const std::span& data, std::vector& dataArea); + bool ValidateCapabilityContainer(); + + std::map mLockedOrReservedBlocks; + std::map mLockedBlocks; + std::array mCapabilityContainer; + std::vector mTLVs; + std::size_t mNdefTlvIdx; + std::vector mLockedArea; +}; diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp new file mode 100644 index 00000000..60be5811 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -0,0 +1,278 @@ +#include "ndef.h" + +#include + +namespace ndef +{ + + Record::Record() + : mFlags(0), mTNF(NDEF_TNF_EMPTY) + { + } + + Record::~Record() + { + } + + std::optional Record::FromStream(Stream& stream) + { + Record rec; + + // Read record header + uint8 recHdr; + stream >> recHdr; + rec.mFlags = recHdr & ~NDEF_TNF_MASK; + rec.mTNF = static_cast(recHdr & NDEF_TNF_MASK); + + // Type length + uint8 typeLen; + stream >> typeLen; + + // Payload length; + uint32 payloadLen; + if (recHdr & NDEF_SR) + { + uint8 len; + stream >> len; + payloadLen = len; + } + else + { + stream >> payloadLen; + } + + // Some sane limits for the payload size + if (payloadLen > 2 * 1024 * 1024) + { + return {}; + } + + // ID length + uint8 idLen = 0; + if (recHdr & NDEF_IL) + { + stream >> idLen; + } + + // Make sure we didn't read past the end of the stream yet + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + // Type + rec.mType.resize(typeLen); + stream.Read(rec.mType); + + // ID + rec.mID.resize(idLen); + stream.Read(rec.mID); + + // Payload + rec.mPayload.resize(payloadLen); + stream.Read(rec.mPayload); + + // Make sure we didn't read past the end of the stream again + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + return rec; + } + + std::vector Record::ToBytes(uint8 flags) const + { + std::vector bytes; + VectorStream stream(bytes, std::endian::big); + + // Combine flags (clear message begin and end flags) + uint8 finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); + finalFlags |= flags; + + // Write flags + tnf + stream << uint8(finalFlags | uint8(mTNF)); + + // Type length + stream << uint8(mType.size()); + + // Payload length + if (IsShort()) + { + stream << uint8(mPayload.size()); + } + else + { + stream << uint32(mPayload.size()); + } + + // ID length + if (mFlags & NDEF_IL) + { + stream << uint8(mID.size()); + } + + // Type + stream.Write(mType); + + // ID + stream.Write(mID); + + // Payload + stream.Write(mPayload); + + return bytes; + } + + Record::TypeNameFormat Record::GetTNF() const + { + return mTNF; + } + + const std::vector& Record::GetID() const + { + return mID; + } + + const std::vector& Record::GetType() const + { + return mType; + } + + const std::vector& Record::GetPayload() const + { + return mPayload; + } + + void Record::SetTNF(TypeNameFormat tnf) + { + mTNF = tnf; + } + + void Record::SetID(const std::span& id) + { + cemu_assert(id.size() < 0x100); + + if (id.size() > 0) + { + mFlags |= NDEF_IL; + } + else + { + mFlags &= ~NDEF_IL; + } + + mID.assign(id.begin(), id.end()); + } + + void Record::SetType(const std::span& type) + { + cemu_assert(type.size() < 0x100); + + mType.assign(type.begin(), type.end()); + } + + void Record::SetPayload(const std::span& payload) + { + // Update short record flag + if (payload.size() < 0xff) + { + mFlags |= NDEF_SR; + } + else + { + mFlags &= ~NDEF_SR; + } + + mPayload.assign(payload.begin(), payload.end()); + } + + bool Record::IsLast() const + { + return mFlags & NDEF_ME; + } + + bool Record::IsShort() const + { + return mFlags & NDEF_SR; + } + + Message::Message() + { + } + + Message::~Message() + { + } + + std::optional Message::FromBytes(const std::span& data) + { + Message msg; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0) + { + std::optional rec = Record::FromStream(stream); + if (!rec) + { + cemuLog_log(LogType::Force, "Warning: Failed to parse NDEF Record #{}." + "Ignoring the remaining {} bytes in NDEF message", msg.mRecords.size(), stream.GetRemaining()); + break; + } + + msg.mRecords.emplace_back(*rec); + + if ((*rec).IsLast() && stream.GetRemaining() > 0) + { + cemuLog_log(LogType::Force, "Warning: Ignoring {} bytes in NDEF message", stream.GetRemaining()); + break; + } + } + + if (msg.mRecords.empty()) + { + return {}; + } + + if (!msg.mRecords.back().IsLast()) + { + cemuLog_log(LogType::Force, "Error: NDEF message missing end record"); + return {}; + } + + return msg; + } + + std::vector Message::ToBytes() const + { + std::vector bytes; + + for (std::size_t i = 0; i < mRecords.size(); i++) + { + uint8 flags = 0; + + // Add message begin flag to first record + if (i == 0) + { + flags |= Record::NDEF_MB; + } + + // Add message end flag to last record + if (i == mRecords.size() - 1) + { + flags |= Record::NDEF_ME; + } + + std::vector recordBytes = mRecords[i].ToBytes(flags); + bytes.insert(bytes.end(), recordBytes.begin(), recordBytes.end()); + } + + return bytes; + } + + void Message::append(const Record& r) + { + mRecords.push_back(r); + } + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/ndef.h b/src/Cafe/OS/libs/nfc/ndef.h new file mode 100644 index 00000000..398feb54 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include "stream.h" + +namespace ndef +{ + + class Record + { + public: + enum HeaderFlag + { + NDEF_IL = 0x08, + NDEF_SR = 0x10, + NDEF_CF = 0x20, + NDEF_ME = 0x40, + NDEF_MB = 0x80, + NDEF_TNF_MASK = 0x07, + }; + + enum TypeNameFormat + { + NDEF_TNF_EMPTY = 0, + NDEF_TNF_WKT = 1, + NDEF_TNF_MEDIA = 2, + NDEF_TNF_URI = 3, + NDEF_TNF_EXT = 4, + NDEF_TNF_UNKNOWN = 5, + NDEF_TNF_UNCHANGED = 6, + NDEF_TNF_RESERVED = 7, + }; + + public: + Record(); + virtual ~Record(); + + static std::optional FromStream(Stream& stream); + std::vector ToBytes(uint8 flags = 0) const; + + TypeNameFormat GetTNF() const; + const std::vector& GetID() const; + const std::vector& GetType() const; + const std::vector& GetPayload() const; + + void SetTNF(TypeNameFormat tnf); + void SetID(const std::span& id); + void SetType(const std::span& type); + void SetPayload(const std::span& payload); + + bool IsLast() const; + bool IsShort() const; + + private: + uint8 mFlags; + TypeNameFormat mTNF; + std::vector mID; + std::vector mType; + std::vector mPayload; + }; + + class Message + { + public: + Message(); + virtual ~Message(); + + static std::optional FromBytes(const std::span& data); + std::vector ToBytes() const; + + Record& operator[](int i) { return mRecords[i]; } + const Record& operator[](int i) const { return mRecords[i]; } + + void append(const Record& r); + + auto begin() { return mRecords.begin(); } + auto end() { return mRecords.end(); } + auto begin() const { return mRecords.begin(); } + auto end() const { return mRecords.end(); } + + private: + std::vector mRecords; + }; + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp new file mode 100644 index 00000000..fcb1d8d0 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -0,0 +1,690 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Common/FileStream.h" + +#include "TagV0.h" +#include "ndef.h" + +#define NFC_MODE_INVALID -1 +#define NFC_MODE_IDLE 0 +#define NFC_MODE_ACTIVE 1 + +#define NFC_STATE_UNINITIALIZED 0x0 +#define NFC_STATE_INITIALIZED 0x1 +#define NFC_STATE_IDLE 0x2 +#define NFC_STATE_READ 0x3 +#define NFC_STATE_WRITE 0x4 +#define NFC_STATE_ABORT 0x5 +#define NFC_STATE_FORMAT 0x6 +#define NFC_STATE_SET_READ_ONLY 0x7 +#define NFC_STATE_TAG_PRESENT 0x8 +#define NFC_STATE_DETECT 0x9 +#define NFC_STATE_SEND_RAW_DATA 0xA + +#define NFC_STATUS_COMMAND_COMPLETE 0x1 +#define NFC_STATUS_READY 0x2 +#define NFC_STATUS_HAS_TAG 0x4 + +namespace nfc +{ + struct NFCContext + { + bool isInitialized; + uint32 state; + sint32 mode; + bool hasTag; + + uint32 nfcStatus; + std::chrono::time_point touchTime; + std::chrono::time_point discoveryTimeout; + struct { + NFCUid uid; + NFCUid mask; + } filter; + + MPTR tagDetectCallback; + void* tagDetectContext; + MPTR abortCallback; + void* abortContext; + MPTR rawCallback; + void* rawContext; + MPTR readCallback; + void* readContext; + MPTR writeCallback; + void* writeContext; + MPTR getTagInfoCallback; + + SysAllocator tagInfo; + + fs::path tagPath; + std::shared_ptr tag; + + ndef::Message writeMessage; + }; + NFCContext gNFCContexts[2]; + + sint32 NFCInit(uint32 chan) + { + return NFCInitEx(chan, 0); + } + + void __NFCClearContext(NFCContext* context) + { + context->isInitialized = false; + context->state = NFC_STATE_UNINITIALIZED; + context->mode = NFC_MODE_IDLE; + context->hasTag = false; + + context->nfcStatus = NFC_STATUS_READY; + context->discoveryTimeout = {}; + + context->tagDetectCallback = MPTR_NULL; + context->tagDetectContext = nullptr; + context->abortCallback = MPTR_NULL; + context->abortContext = nullptr; + context->rawCallback = MPTR_NULL; + context->rawContext = nullptr; + context->readCallback = MPTR_NULL; + context->readContext = nullptr; + context->writeCallback = MPTR_NULL; + context->writeContext = nullptr; + + context->tagPath = ""; + context->tag = {}; + } + + sint32 NFCInitEx(uint32 chan, uint32 powerMode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + ctx->isInitialized = true; + ctx->state = NFC_STATE_INITIALIZED; + + return NFC_RESULT_SUCCESS; + } + + sint32 NFCShutdown(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + + return NFC_RESULT_SUCCESS; + } + + bool NFCIsInit(uint32 chan) + { + cemu_assert(chan < 2); + + return gNFCContexts[chan].isInitialized; + } + + bool __NFCCompareUid(NFCUid* uid, NFCUid* filterUid, NFCUid* filterMask) + { + for (int i = 0; i < sizeof(uid->uid); i++) + { + if ((uid->uid[i] & filterMask->uid[i]) != filterUid->uid[i]) + { + return false; + } + } + + return true; + } + + void __NFCHandleRead(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + StackAllocator uid; + bool readOnly = false; + uint32 dataSize = 0; + StackAllocator data; + uint32 lockedDataSize = 0; + StackAllocator lockedData; + + if (ctx->tag) + { + // Compare UID + memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(uid.GetPointer(), &ctx->filter.uid, &ctx->filter.mask)) + { + // Try to parse ndef message + auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); + if (ndefMsg) + { + // Look for the unknown TNF which contains the data we care about + for (const auto& rec : *ndefMsg) + { + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + { + dataSize = rec.GetPayload().size(); + cemu_assert(dataSize < 0x200); + memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); + break; + } + } + + if (dataSize) + { + // Get locked data + lockedDataSize = ctx->tag->GetLockedArea().size(); + memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); + + result = NFC_RESULT_SUCCESS; + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UID_MISMATCH); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_NO_TAG); + } + + PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); + } + + void __NFCHandleWrite(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + + if (ctx->tag) + { + NFCUid uid; + memcpy(&uid, ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(&uid, &ctx->filter.uid, &ctx->filter.mask)) + { + // Update tag NDEF data + ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); + + // open file for writing + FileStream* fs = FileStream::openFile2(ctx->tagPath, true); + if (!fs) + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); + } + else + { + auto tagBytes = ctx->tag->ToBytes(); + fs->writeData(tagBytes.data(), tagBytes.size()); + delete fs; + + result = NFC_RESULT_SUCCESS; + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UID_MISMATCH); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_NO_TAG); + } + + PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); + } + + void __NFCHandleAbort(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + PPCCoreCallback(ctx->abortCallback, chan, 0, ctx->abortContext); + } + + void __NFCHandleRaw(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + result = NFC_RESULT_SUCCESS; + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG); + } + + // We don't actually send any commands/responses + uint32 responseSize = 0; + void* responseData = nullptr; + + PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); + } + + bool __NFCShouldHandleState(NFCContext* ctx) + { + // Always handle abort + if (ctx->state == NFC_STATE_ABORT) + { + return true; + } + + // Do we have a tag? + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + return true; + } + + // Did the timeout expire? + if (ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + return true; + } + + return false; + } + + void NFCProc(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!ctx->isInitialized) + { + return; + } + + if (ctx->state == NFC_STATE_INITIALIZED) + { + ctx->state = NFC_STATE_IDLE; + } + + // Check if the detect callback should be called + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + if (!ctx->hasTag && ctx->state > NFC_STATE_IDLE && ctx->state != NFC_STATE_ABORT) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, true, ctx->tagDetectContext); + } + + ctx->hasTag = true; + } + + // Check if the tag should be removed again + if (ctx->touchTime + std::chrono::seconds(2) < std::chrono::system_clock::now()) + { + ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; + ctx->tag = {}; + ctx->tagPath = ""; + } + } + else + { + if (ctx->hasTag && ctx->state == NFC_STATE_IDLE) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, false, ctx->tagDetectContext); + } + + ctx->hasTag = false; + } + } + + if (__NFCShouldHandleState(ctx)) + { + switch (ctx->state) + { + case NFC_STATE_READ: + __NFCHandleRead(chan); + break; + case NFC_STATE_WRITE: + __NFCHandleWrite(chan); + break; + case NFC_STATE_ABORT: + __NFCHandleAbort(chan); + break; + case NFC_STATE_SEND_RAW_DATA: + __NFCHandleRaw(chan); + break; + default: + break; + } + + // Return back to idle mode + ctx->mode = NFC_MODE_IDLE; + } + } + + sint32 NFCGetMode(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan) || ctx->state == NFC_STATE_UNINITIALIZED) + { + return NFC_MODE_INVALID; + } + + return ctx->mode; + } + + sint32 NFCSetMode(uint32 chan, sint32 mode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_UNINITIALIZED); + } + + if (ctx->state == NFC_STATE_UNINITIALIZED) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_INVALID_STATE); + } + + ctx->mode = mode; + + return NFC_RESULT_SUCCESS; + } + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + ctx->tagDetectCallback = callback; + ctx->tagDetectContext = context; + } + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_UNINITIALIZED); + } + + if (ctx->state <= NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_INVALID_STATE); + } + + ctx->state = NFC_STATE_ABORT; + ctx->abortCallback = callback; + ctx->abortContext = context; + + return NFC_RESULT_SUCCESS; + } + + sint32 __NFCConvertGetTagInfoResult(sint32 result) + { + if (result == NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_GET_TAG_INFO, NFC_RESULT_TAG_INFO_TIMEOUT); + } + + // TODO convert the rest of the results + return result; + } + + void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamU32(responseSize, 2); + ppcDefineParamPtr(responseData, void, 3); + ppcDefineParamPtr(context, void, 4); + + NFCContext* ctx = &gNFCContexts[chan]; + + error = __NFCConvertGetTagInfoResult(error); + if (error == 0 && ctx->tag) + { + // this is usually parsed from response data + ctx->tagInfo->uidSize = sizeof(NFCUid); + memcpy(ctx->tagInfo->uid, ctx->tag->GetUIDBlock().data(), ctx->tagInfo->uidSize); + ctx->tagInfo->technology = NFC_TECHNOLOGY_A; + ctx->tagInfo->protocol = NFC_PROTOCOL_T1T; + } + + PPCCoreCallback(ctx->getTagInfoCallback, chan, error, ctx->tagInfo.GetPtr(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nn::nfp::NFCGetTagInfo(chan, discoveryTimeout, callback, context); + } + + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->getTagInfoCallback = callback; + + sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); + return __NFCConvertGetTagInfoResult(result); + } + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_UNINITIALIZED); + } + + // Only allow discovery + if (!startDiscovery) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); + } + + if (ctx->state != NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_STATE); + } + + ctx->state = NFC_STATE_SEND_RAW_DATA; + ctx->rawCallback = callback; + ctx->rawContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + return NFC_RESULT_SUCCESS; + } + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UNINITIALIZED); + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_MODE); + } + + if (ctx->state != NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_STATE); + } + + ctx->state = NFC_STATE_READ; + ctx->readCallback = callback; + ctx->readContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); + + return NFC_RESULT_SUCCESS; + } + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UNINITIALIZED); + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_MODE); + } + + if (ctx->state != NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_STATE); + } + + // Create unknown record which contains the rw area + ndef::Record rec; + rec.SetTNF(ndef::Record::NDEF_TNF_UNKNOWN); + rec.SetPayload(std::span(reinterpret_cast(data), size)); + + // Create ndef message which contains the record + ndef::Message msg; + msg.append(rec); + ctx->writeMessage = msg; + + ctx->state = NFC_STATE_WRITE; + ctx->writeCallback = callback; + ctx->writeContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); + + return NFC_RESULT_SUCCESS; + } + + void Initialize() + { + cafeExportRegister("nfc", NFCInit, LogType::NFC); + cafeExportRegister("nfc", NFCInitEx, LogType::NFC); + cafeExportRegister("nfc", NFCShutdown, LogType::NFC); + cafeExportRegister("nfc", NFCIsInit, LogType::NFC); + cafeExportRegister("nfc", NFCProc, LogType::NFC); + cafeExportRegister("nfc", NFCGetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetTagDetectCallback, LogType::NFC); + cafeExportRegister("nfc", NFCGetTagInfo, LogType::NFC); + cafeExportRegister("nfc", NFCSendRawData, LogType::NFC); + cafeExportRegister("nfc", NFCAbort, LogType::NFC); + cafeExportRegister("nfc", NFCRead, LogType::NFC); + cafeExportRegister("nfc", NFCWrite, LogType::NFC); + } + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError) + { + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nnNfp_touchNfcTagFromFile(filePath, nfcError); + } + + NFCContext* ctx = &gNFCContexts[0]; + + auto nfcData = FileStream::LoadIntoMemory(filePath); + if (!nfcData) + { + *nfcError = NFC_TOUCH_TAG_ERROR_NO_ACCESS; + return false; + } + + ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); + if (!ctx->tag) + { + *nfcError = NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT; + return false; + } + + ctx->nfcStatus |= NFC_STATUS_HAS_TAG; + ctx->tagPath = filePath; + ctx->touchTime = std::chrono::system_clock::now(); + + *nfcError = NFC_TOUCH_TAG_ERROR_NONE; + return true; + } +} diff --git a/src/Cafe/OS/libs/nfc/nfc.h b/src/Cafe/OS/libs/nfc/nfc.h new file mode 100644 index 00000000..ea959cd1 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.h @@ -0,0 +1,92 @@ +#pragma once + +// CEMU NFC error codes +#define NFC_TOUCH_TAG_ERROR_NONE (0) +#define NFC_TOUCH_TAG_ERROR_NO_ACCESS (1) +#define NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT (2) + +// NFC result base +#define NFC_RESULT_BASE_INIT (-0x100) +#define NFC_RESULT_BASE_READ (-0x200) +#define NFC_RESULT_BASE_WRITE (-0x300) +#define NFC_RESULT_BASE_FORMAT (-0x400) +#define NFC_RESULT_BASE_SET_READ_ONLY (-0x500) +#define NFC_RESULT_BASE_IS_TAG_PRESENT (-0x600) +#define NFC_RESULT_BASE_ABORT (-0x700) +#define NFC_RESULT_BASE_SHUTDOWN (-0x800) +#define NFC_RESULT_BASE_DETECT (-0x900) +#define NFC_RESULT_BASE_SEND_RAW_DATA (-0xA00) +#define NFC_RESULT_BASE_SET_MODE (-0xB00) +#define NFC_RESULT_BASE_TAG_PARSE (-0xC00) +#define NFC_RESULT_BASE_GET_TAG_INFO (-0x1400) + +// NFC result status +#define NFC_RESULT_NO_TAG (0x01) +#define NFC_RESULT_INVALID_TAG (0x02) +#define NFC_RESULT_UID_MISMATCH (0x0A) +#define NFC_RESULT_UNINITIALIZED (0x20) +#define NFC_RESULT_INVALID_STATE (0x21) +#define NFC_RESULT_INVALID_MODE (0x24) +#define NFC_RESULT_TAG_INFO_TIMEOUT (0x7A) + +// Result macros +#define NFC_RESULT_SUCCESS (0) +#define NFC_RESULT_BASE_MASK (0xFFFFFF00) +#define NFC_RESULT_MASK (0x000000FF) +#define NFC_MAKE_RESULT(base, result) ((base) | (result)) + +#define NFC_PROTOCOL_T1T 0x1 +#define NFC_PROTOCOL_T2T 0x2 + +#define NFC_TECHNOLOGY_A 0x0 +#define NFC_TECHNOLOGY_B 0x1 +#define NFC_TECHNOLOGY_F 0x2 + +namespace nfc +{ + struct NFCUid + { + /* +0x00 */ uint8 uid[7]; + }; + static_assert(sizeof(NFCUid) == 0x7); + + struct NFCTagInfo + { + /* +0x00 */ uint8 uidSize; + /* +0x01 */ uint8 uid[10]; + /* +0x0B */ uint8 technology; + /* +0x0C */ uint8 protocol; + /* +0x0D */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NFCTagInfo) == 0x2D); + + sint32 NFCInit(uint32 chan); + + sint32 NFCInitEx(uint32 chan, uint32 powerMode); + + sint32 NFCShutdown(uint32 chan); + + bool NFCIsInit(uint32 chan); + + void NFCProc(uint32 chan); + + sint32 NFCGetMode(uint32 chan); + + sint32 NFCSetMode(uint32 chan, sint32 mode); + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context); + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context); + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context); + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context); + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context); + + void Initialize(); + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError); +} diff --git a/src/Cafe/OS/libs/nfc/stream.cpp b/src/Cafe/OS/libs/nfc/stream.cpp new file mode 100644 index 00000000..dd6de7ad --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.cpp @@ -0,0 +1,201 @@ +#include "stream.h" + +#include + +Stream::Stream(std::endian endianness) + : mError(ERROR_OK), mEndianness(endianness) +{ +} + +Stream::~Stream() +{ +} + +Stream::Error Stream::GetError() const +{ + return mError; +} + +void Stream::SetEndianness(std::endian endianness) +{ + mEndianness = endianness; +} + +std::endian Stream::GetEndianness() const +{ + return mEndianness; +} + +Stream& Stream::operator>>(bool& val) +{ + uint8 i; + *this >> i; + val = !!i; + + return *this; +} + +Stream& Stream::operator>>(float& val) +{ + uint32 i; + *this >> i; + val = std::bit_cast(i); + + return *this; +} + +Stream& Stream::operator>>(double& val) +{ + uint64 i; + *this >> i; + val = std::bit_cast(i); + + return *this; +} + +Stream& Stream::operator<<(bool val) +{ + uint8 i = val; + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(float val) +{ + uint32 i = std::bit_cast(val); + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(double val) +{ + uint64 i = std::bit_cast(val); + *this >> i; + + return *this; +} + +void Stream::SetError(Error error) +{ + mError = error; +} + +bool Stream::NeedsSwap() +{ + return mEndianness != std::endian::native; +} + +VectorStream::VectorStream(std::vector& vector, std::endian endianness) + : Stream(endianness), mVector(vector), mPosition(0) +{ +} + +VectorStream::~VectorStream() +{ +} + +std::size_t VectorStream::Read(const std::span& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mVector.get().begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t VectorStream::Write(const std::span& data) +{ + // Resize vector if not enough bytes remain + if (mPosition + data.size() > mVector.get().size()) + { + mVector.get().resize(mPosition + data.size()); + } + + std::copy(data.begin(), data.end(), mVector.get().begin() + mPosition); + mPosition += data.size(); + return data.size(); +} + +bool VectorStream::SetPosition(std::size_t position) +{ + if (position >= mVector.get().size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t VectorStream::GetPosition() const +{ + return mPosition; +} + +std::size_t VectorStream::GetRemaining() const +{ + return mVector.get().size() - mPosition; +} + +SpanStream::SpanStream(std::span span, std::endian endianness) + : Stream(endianness), mSpan(std::move(span)), mPosition(0) +{ +} + +SpanStream::~SpanStream() +{ +} + +std::size_t SpanStream::Read(const std::span& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mSpan.begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t SpanStream::Write(const std::span& data) +{ + // Cannot write to const span + SetError(ERROR_WRITE_FAILED); + return 0; +} + +bool SpanStream::SetPosition(std::size_t position) +{ + if (position >= mSpan.size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t SpanStream::GetPosition() const +{ + return mPosition; +} + +std::size_t SpanStream::GetRemaining() const +{ + if (mPosition > mSpan.size()) + { + return 0; + } + + return mSpan.size() - mPosition; +} diff --git a/src/Cafe/OS/libs/nfc/stream.h b/src/Cafe/OS/libs/nfc/stream.h new file mode 100644 index 00000000..e666b480 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include + +#include "Common/precompiled.h" + +class Stream +{ +public: + enum Error + { + ERROR_OK, + ERROR_READ_FAILED, + ERROR_WRITE_FAILED, + }; + +public: + Stream(std::endian endianness = std::endian::native); + virtual ~Stream(); + + Error GetError() const; + + void SetEndianness(std::endian endianness); + std::endian GetEndianness() const; + + virtual std::size_t Read(const std::span& data) = 0; + virtual std::size_t Write(const std::span& data) = 0; + + virtual bool SetPosition(std::size_t position) = 0; + virtual std::size_t GetPosition() const = 0; + + virtual std::size_t GetRemaining() const = 0; + + // Stream read operators + template + Stream& operator>>(T& val) + { + val = 0; + if (Read(std::as_writable_bytes(std::span(std::addressof(val), 1))) == sizeof(val)) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + } + + return *this; + } + Stream& operator>>(bool& val); + Stream& operator>>(float& val); + Stream& operator>>(double& val); + + // Stream write operators + template + Stream& operator<<(T val) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + + Write(std::as_bytes(std::span(std::addressof(val), 1))); + return *this; + } + Stream& operator<<(bool val); + Stream& operator<<(float val); + Stream& operator<<(double val); + +protected: + void SetError(Error error); + + bool NeedsSwap(); + + Error mError; + std::endian mEndianness; +}; + +class VectorStream : public Stream +{ +public: + VectorStream(std::vector& vector, std::endian endianness = std::endian::native); + virtual ~VectorStream(); + + virtual std::size_t Read(const std::span& data) override; + virtual std::size_t Write(const std::span& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::reference_wrapper> mVector; + std::size_t mPosition; +}; + +class SpanStream : public Stream +{ +public: + SpanStream(std::span span, std::endian endianness = std::endian::native); + virtual ~SpanStream(); + + virtual std::size_t Read(const std::span& data) override; + virtual std::size_t Write(const std::span& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::span mSpan; + std::size_t mPosition; +}; diff --git a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp index fc757ea9..86ca4708 100644 --- a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp +++ b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp @@ -464,6 +464,14 @@ namespace nn return ipcCtx->Submit(std::move(ipcCtx)); } + nnResult GetMyPlayingGame(iosu::fpd::GameKey* myPlayingGame) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyPlayingGame); + ipcCtx->AddOutput(myPlayingGame, sizeof(iosu::fpd::GameKey)); + return ipcCtx->Submit(std::move(ipcCtx)); + } + nnResult GetMyPreference(iosu::fpd::FPDPreference* myPreference) { FP_API_BASE(); @@ -472,6 +480,14 @@ namespace nn return ipcCtx->Submit(std::move(ipcCtx)); } + nnResult GetMyComment(uint16be* myComment) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::GetMyComment); + ipcCtx->AddOutput(myComment, iosu::fpd::MY_COMMENT_LENGTH * sizeof(uint16be)); + return ipcCtx->Submit(std::move(ipcCtx)); + } + nnResult GetMyMii(FFLData_t* fflData) { FP_API_BASE(); @@ -607,6 +623,20 @@ namespace nn return resultBuf != 0 ? 1 : 0; } + nnResult UpdateCommentAsync(uint16be* newComment, void* funcPtr, void* customParam) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique(iosu::fpd::FPD_REQUEST_ID::UpdateCommentAsync); + uint32 commentLen = CafeStringHelpers::Length(newComment, iosu::fpd::MY_COMMENT_LENGTH-1); + if (commentLen >= iosu::fpd::MY_COMMENT_LENGTH-1) + { + cemuLog_log(LogType::Force, "UpdateCommentAsync: message too long"); + return FPResult_InvalidIPCParam; + } + ipcCtx->AddInput(newComment, sizeof(uint16be) * commentLen + 2); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); + } + nnResult UpdatePreferenceAsync(iosu::fpd::FPDPreference* newPreference, void* funcPtr, void* customParam) { FP_API_BASE(); @@ -763,7 +793,9 @@ namespace nn 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(GetMyPlayingGame, "nn_fp", "GetMyPlayingGame__Q2_2nn2fpFPQ3_2nn2fp7GameKey", LogType::NN_FP); cafeExportRegisterFunc(GetMyPreference, "nn_fp", "GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference", LogType::NN_FP); + cafeExportRegisterFunc(GetMyComment, "nn_fp", "GetMyComment__Q2_2nn2fpFPQ3_2nn2fp7Comment", LogType::NN_FP); cafeExportRegisterFunc(GetFriendAccountId, "nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetFriendScreenName, "nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", LogType::NN_FP); @@ -774,6 +806,7 @@ namespace nn cafeExportRegisterFunc(CheckSettingStatusAsync, "nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(IsPreferenceValid, "nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(UpdateCommentAsync, "nn_fp", "UpdateCommentAsync__Q2_2nn2fpFPCwPFQ2_2nn6ResultPv_vPv", 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); diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp index a69f32a3..eb0178fe 100644 --- a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp @@ -40,7 +40,7 @@ namespace nn static_assert(offsetof(nnIdbeEncryptedIcon_t, iconData) == 0x22, ""); static_assert(sizeof(nnIdbeEncryptedIcon_t) == 0x12082); - void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, OSThread_t* thread) + void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, coreinit::OSEvent* event) { std::vector idbeData = NAPI::IDBE_RequestRawEncrypted(ActiveSettings::GetNetworkService(), titleId); if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) @@ -48,11 +48,11 @@ namespace nn // icon does not exist or has the wrong size cemuLog_log(LogType::Force, "IDBE: Failed to retrieve icon for title {:016x}", titleId); memset(iconOut, 0, sizeof(nnIdbeEncryptedIcon_t)); - coreinit_resumeThread(thread); + coreinit::OSSignalEvent(event); return; } memcpy(iconOut, idbeData.data(), sizeof(nnIdbeEncryptedIcon_t)); - coreinit_resumeThread(thread); + coreinit::OSSignalEvent(event); } void export_DownloadIconFile(PPCInterpreter_t* hCPU) @@ -62,9 +62,10 @@ namespace nn ppcDefineParamU32(uknR7, 4); ppcDefineParamU32(uknR8, 5); - auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, coreinit::OSGetCurrentThread()); - coreinit::OSSuspendThread(coreinit::OSGetCurrentThread()); - PPCCore_switchToScheduler(); + StackAllocator event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, &event); + coreinit::OSWaitEvent(&event); osLib_returnFromFunction(hCPU, 1); } diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp index ad2ea203..10d9e7cb 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -293,41 +293,6 @@ void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } -typedef struct -{ - /* +0x00 */ uint8 uidLength; - /* +0x01 */ uint8 uid[0xA]; - /* +0x0B */ uint8 ukn0B; - /* +0x0C */ uint8 ukn0C; - /* +0x0D */ uint8 ukn0D; - // more? -}NFCTagInfoCallbackParam_t; - -uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) -{ - cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); - - - cemu_assert(index == 0); - - nnNfpLock(); - - StackAllocator _callbackParam; - NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); - - memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); - - memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); - callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; - - PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); - - nnNfpUnlock(); - - - return 0; // 0 -> success -} - void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Mount()"); @@ -769,6 +734,16 @@ void nnNfp_unloadAmiibo() nnNfpUnlock(); } +bool nnNfp_isInitialized() +{ + return nfp_data.nfpIsInitialized; +} + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) + bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; @@ -960,6 +935,41 @@ void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) namespace nn::nfp { + typedef struct + { + /* +0x00 */ uint8 uidLength; + /* +0x01 */ uint8 uid[0xA]; + /* +0x0B */ uint8 ukn0B; + /* +0x0C */ uint8 ukn0C; + /* +0x0D */ uint8 ukn0D; + // more? + }NFCTagInfoCallbackParam_t; + + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) + { + cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); + + + cemu_assert(index == 0); + + nnNfpLock(); + + StackAllocator _callbackParam; + NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); + + memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); + + memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); + callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; + + PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); + + nnNfpUnlock(); + + + return 0; // 0 -> success + } + uint32 GetErrorCode(uint32 result) { uint32 level = (result >> 0x1b) & 3; @@ -1019,9 +1029,6 @@ namespace nn::nfp nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); - - // NFC API - cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder); } } diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h index e8a1c55f..25b36cc9 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h @@ -2,12 +2,15 @@ namespace nn::nfp { + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam); + void load(); } void nnNfp_load(); void nnNfp_update(); +bool nnNfp_isInitialized(); bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_NONE (0) @@ -18,8 +21,3 @@ bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_RW_MOUNT (5) #define NFP_STATE_UNEXPECTED (6) #define NFP_STATE_RW_MOUNT_ROM (7) - -// CEMU NFC error codes -#define NFC_ERROR_NONE (0) -#define NFC_ERROR_NO_ACCESS (1) -#define NFC_ERROR_INVALID_FILE_FORMAT (2) diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 09d4413b..31f8ac8e 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -160,39 +160,60 @@ namespace save return FS_RESULT::FATAL_ERROR; } - typedef struct + struct AsyncResultData { - coreinit::OSEvent* event; - SAVEStatus returnStatus; + MEMPTR event; + betype returnStatus; + }; - MEMPTR thread; // own stuff until cond + event rewritten - } AsyncCallbackParam_t; + void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU); - void AsyncCallback(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 returnStatus, void* p) + struct AsyncToSyncWrapper : public FSAsyncParams { - cemu_assert_debug(p && ((AsyncCallbackParam_t*)p)->event); + AsyncToSyncWrapper() + { + coreinit::OSInitEvent(&_event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + ioMsgQueue = nullptr; + userContext = &_result; + userCallback = RPLLoader_MakePPCCallable(SaveAsyncFinishCallback); + _result.returnStatus = 0; + _result.event = &_event; + } - AsyncCallbackParam_t* param = (AsyncCallbackParam_t*)p; - param->returnStatus = returnStatus; - coreinit::OSSignalEvent(param->event); - } + ~AsyncToSyncWrapper() + { - void AsyncCallback(PPCInterpreter_t* hCPU) + } + + FSAsyncParams* GetAsyncParams() + { + return this; + } + + SAVEStatus GetResult() + { + return _result.returnStatus; + } + + void WaitForEvent() + { + coreinit::OSWaitEvent(&_event); + } + private: + coreinit::OSEvent _event; + AsyncResultData _result; + }; + + void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(client, coreinit::FSClient_t, 0); ppcDefineParamMEMPTR(block, coreinit::FSCmdBlock_t, 1); ppcDefineParamU32(returnStatus, 2); ppcDefineParamMEMPTR(userContext, void, 3); - MEMPTR param{ userContext }; - - // wait till thread is actually suspended - OSThread_t* thread = param->thread.GetPtr(); - while (thread->suspendCounter == 0 || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) - coreinit::OSYieldThread(); - - param->returnStatus = returnStatus; - coreinit_resumeThread(param->thread.GetPtr(), 1000); + MEMPTR resultPtr{ userContext }; + resultPtr->returnStatus = returnStatus; + coreinit::OSSignalEvent(resultPtr->event); osLib_returnFromFunction(hCPU, 0); } @@ -320,18 +341,16 @@ namespace save return SAVE_STATUS_OK; } - SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + SAVEStatus SAVEInitSaveDir(uint8 accountSlot) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); + acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); + result = ConvertACPToSaveStatus(status); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -340,27 +359,6 @@ namespace save return result; } - SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -383,6 +381,69 @@ namespace save return result; } + SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -430,25 +491,12 @@ namespace save SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetPointer(); - - SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; + StackAllocator asyncData; + SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) @@ -475,27 +523,6 @@ namespace save return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) - result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -517,6 +544,16 @@ namespace save return result; } + SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -538,12 +575,28 @@ namespace save return result; } + SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } + SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); + } + SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); @@ -562,644 +615,6 @@ namespace save return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEInitSaveDir(uint8 accountSlot) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); - result = ConvertACPToSaveStatus(status); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - return result; - } - - SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetFreeSpaceSize(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVEGetFreeSpaceSize(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSize(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetFreeSpaceSizeAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVEGetFreeSpaceSizeAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSizeAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEInit(PPCInterpreter_t* hCPU) - { - const SAVEStatus result = SAVEInit(); - cemuLog_log(LogType::Save, "SAVEInit() -> {:x}", result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVERemoveAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVERemoveAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVERemove(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVERemove(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullOldPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) - { - char fullNewPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParams*)asyncParams); - } - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVERenameAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(oldPath, const char, 3); - ppcDefineParamMEMPTR(newPath, const char, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVERenameAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVERenameAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullOldPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) - { - char fullNewPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRename(client, block, fullOldPath, fullNewPath, errHandling); - } - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVEOpenDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(hDir, betype, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVEOpenDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEOpenDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(hDir, betype, 4); - ppcDefineParamU32(errHandling, 5); - - const SAVEStatus result = SAVEOpenDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling); - cemuLog_log(LogType::Save, "SAVEOpenDir(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVEOpenDirOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); - - const SAVEStatus result = SAVEOpenDirOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplicationAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEOpenDirOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenDirOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling); - cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplication(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); - } - - void export_SAVEOpenDirOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); - } - - void export_SAVEOpenDirOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); - } - - void export_SAVEOpenDirOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(hDir, betype, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); - } - - void export_SAVEOpenDirOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(hDir, betype, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEMakeDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVEMakeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEMakeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEMakeDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVEMakeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEMakeDir(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetPointer(); - - SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEInitSaveDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamU8(accountSlot, 0); - const SAVEStatus result = SAVEInitSaveDir(accountSlot); - cemuLog_log(LogType::Save, "SAVEInitSaveDir({:x}) -> {:x}", accountSlot, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetStatAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(stat, FSStat_t, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVEGetStatAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEGetStatAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetStat(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(stat, FSStat_t, 4); - ppcDefineParamU32(errHandling, 5); - - const SAVEStatus result = SAVEGetStat(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEGetStat(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetStatOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetStatOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEGetStatOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - - void export_SAVEGetStatOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(stat, FSStat_t, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); - } - - void export_SAVEGetStatOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(stat, FSStat_t, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEGetStatOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - - - void export_SAVEGetStatOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEGetStatOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { //peterBreak(); @@ -1208,21 +623,140 @@ namespace save return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } - void export_SAVEGetStatOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) + result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; } + SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullOldPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) + { + char fullNewPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) + result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, asyncParams); + } + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVERenameAsync(client, block, accountSlot, oldPath, newPath, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) + { + StackAllocator asyncData; + SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } SAVEStatus SAVEGetSharedDataTitlePath(uint64 titleId, const char* dataFileName, char* output, sint32 outputLength) { @@ -1234,17 +768,6 @@ namespace save return result; } - void export_SAVEGetSharedDataTitlePath(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(titleId, 0); - ppcDefineParamMEMPTR(dataFileName, const char, 2); - ppcDefineParamMEMPTR(output, char, 3); - ppcDefineParamS32(outputLength, 4); - const SAVEStatus result = SAVEGetSharedDataTitlePath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); - cemuLog_log(LogType::Save, "SAVEGetSharedDataTitlePath(0x{:x}, {}, {}, 0x{:x}) -> {:x}", titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEGetSharedSaveDataPath(uint64 titleId, const char* dataFileName, char* output, uint32 outputLength) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1255,16 +778,6 @@ namespace save return result; } - void export_SAVEGetSharedSaveDataPath(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(titleId, 0); - ppcDefineParamMEMPTR(dataFileName, const char, 2); - ppcDefineParamMEMPTR(output, char, 3); - ppcDefineParamU32(outputLength, 4); - const SAVEStatus result = SAVEGetSharedSaveDataPath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1284,52 +797,14 @@ namespace save return result; } - void export_SAVEChangeDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - const SAVEStatus result = SAVEChangeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEChangeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEChangeDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - const SAVEStatus result = SAVEChangeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEChangeDir(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); + StackAllocator asyncData; + SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) @@ -1355,71 +830,45 @@ namespace save return result; } - void export_SAVEFlushQuotaAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamU32(errHandling, 3); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 4); - const SAVEStatus result = SAVEFlushQuotaAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEFlushQuotaAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { - MEMPTR currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - return status; - } - - void export_SAVEFlushQuota(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamU32(errHandling, 3); - const SAVEStatus result = SAVEFlushQuota(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling); - cemuLog_log(LogType::Save, "SAVEFlushQuota(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); + StackAllocator asyncData; + SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } void load() { + cafeExportRegister("nn_save", SAVEInit, LogType::Save); + cafeExportRegister("nn_save", SAVEInitSaveDir, LogType::Save); + cafeExportRegister("nn_save", SAVEGetSharedDataTitlePath, LogType::Save); + cafeExportRegister("nn_save", SAVEGetSharedSaveDataPath, LogType::Save); - osLib_addFunction("nn_save", "SAVEInit", export_SAVEInit); - osLib_addFunction("nn_save", "SAVEInitSaveDir", export_SAVEInitSaveDir); - osLib_addFunction("nn_save", "SAVEGetSharedDataTitlePath", export_SAVEGetSharedDataTitlePath); - osLib_addFunction("nn_save", "SAVEGetSharedSaveDataPath", export_SAVEGetSharedSaveDataPath); + cafeExportRegister("nn_save", SAVEGetFreeSpaceSize, LogType::Save); + cafeExportRegister("nn_save", SAVEGetFreeSpaceSizeAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEMakeDir, LogType::Save); + cafeExportRegister("nn_save", SAVEMakeDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVERemove, LogType::Save); + cafeExportRegister("nn_save", SAVERemoveAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEChangeDir, LogType::Save); + cafeExportRegister("nn_save", SAVEChangeDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVERename, LogType::Save); + cafeExportRegister("nn_save", SAVERenameAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEFlushQuota, LogType::Save); + cafeExportRegister("nn_save", SAVEFlushQuotaAsync, LogType::Save); - // sync functions - osLib_addFunction("nn_save", "SAVEGetFreeSpaceSize", export_SAVEGetFreeSpaceSize); - osLib_addFunction("nn_save", "SAVEMakeDir", export_SAVEMakeDir); - osLib_addFunction("nn_save", "SAVERemove", export_SAVERemove); - osLib_addFunction("nn_save", "SAVEChangeDir", export_SAVEChangeDir); - osLib_addFunction("nn_save", "SAVEFlushQuota", export_SAVEFlushQuota); - - osLib_addFunction("nn_save", "SAVEGetStat", export_SAVEGetStat); - osLib_addFunction("nn_save", "SAVEGetStatOtherApplication", export_SAVEGetStatOtherApplication); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplication", export_SAVEGetStatOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariation", export_SAVEGetStatOtherNormalApplicationVariation); + cafeExportRegister("nn_save", SAVEGetStat, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFile, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileAsync, LogType::Save); @@ -1430,30 +879,14 @@ namespace save cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariation, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariationAsync, LogType::Save); - osLib_addFunction("nn_save", "SAVEOpenDir", export_SAVEOpenDir); - osLib_addFunction("nn_save", "SAVEOpenDirOtherApplication", export_SAVEOpenDirOtherApplication); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplication", export_SAVEOpenDirOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariation", export_SAVEOpenDirOtherNormalApplicationVariation); - - // async functions - osLib_addFunction("nn_save", "SAVEGetFreeSpaceSizeAsync", export_SAVEGetFreeSpaceSizeAsync); - osLib_addFunction("nn_save", "SAVEMakeDirAsync", export_SAVEMakeDirAsync); - osLib_addFunction("nn_save", "SAVERemoveAsync", export_SAVERemoveAsync); - osLib_addFunction("nn_save", "SAVERenameAsync", export_SAVERenameAsync); - cafeExportRegister("nn_save", SAVERename, LogType::Save); - - osLib_addFunction("nn_save", "SAVEChangeDirAsync", export_SAVEChangeDirAsync); - osLib_addFunction("nn_save", "SAVEFlushQuotaAsync", export_SAVEFlushQuotaAsync); - - osLib_addFunction("nn_save", "SAVEGetStatAsync", export_SAVEGetStatAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherApplicationAsync", export_SAVEGetStatOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationAsync", export_SAVEGetStatOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariationAsync", export_SAVEGetStatOtherNormalApplicationVariationAsync); - - osLib_addFunction("nn_save", "SAVEOpenDirAsync", export_SAVEOpenDirAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherApplicationAsync", export_SAVEOpenDirOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariationAsync", export_SAVEOpenDirOtherNormalApplicationVariationAsync); + cafeExportRegister("nn_save", SAVEOpenDir, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariationAsync, LogType::Save); } void ResetToDefaultState() diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp index 6e6cb123..fc8e496c 100644 --- a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -1,5 +1,6 @@ #include "nsyshid.h" #include "Backend.h" +#include "BackendEmulated.h" #if NSYSHID_ENABLE_BACKEND_LIBUSB @@ -37,5 +38,13 @@ namespace nsyshid::backend } } #endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID + // add emulated backend + { + auto backendEmulated = std::make_shared(); + if (backendEmulated->IsInitialisedOk()) + { + AttachBackend(backendEmulated); + } + } } } // namespace nsyshid::backend diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 641104f5..12362773 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -23,6 +23,55 @@ namespace nsyshid /* +0x12 */ uint16be maxPacketSizeTX; } HID_t; + struct TransferCommand + { + uint8* data; + sint32 length; + + TransferCommand(uint8* data, sint32 length) + : data(data), length(length) + { + } + virtual ~TransferCommand() = default; + }; + + struct ReadMessage final : TransferCommand + { + sint32 bytesRead; + + ReadMessage(uint8* data, sint32 length, sint32 bytesRead) + : bytesRead(bytesRead), TransferCommand(data, length) + { + } + using TransferCommand::TransferCommand; + }; + + struct WriteMessage final : TransferCommand + { + sint32 bytesWritten; + + WriteMessage(uint8* data, sint32 length, sint32 bytesWritten) + : bytesWritten(bytesWritten), TransferCommand(data, length) + { + } + using TransferCommand::TransferCommand; + }; + + struct ReportMessage final : TransferCommand + { + uint8* reportData; + sint32 length; + uint8* originalData; + sint32 originalLength; + + ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + : reportData(reportData), length(length), originalData(originalData), + originalLength(originalLength), TransferCommand(reportData, length) + { + } + using TransferCommand::TransferCommand; + }; + static_assert(offsetof(HID_t, vendorId) == 0x8, ""); static_assert(offsetof(HID_t, productId) == 0xA, ""); static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); @@ -69,7 +118,7 @@ namespace nsyshid ErrorTimeout, }; - virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0; + virtual ReadResult Read(ReadMessage* message) = 0; enum class WriteResult { @@ -78,7 +127,7 @@ namespace nsyshid ErrorTimeout, }; - virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0; + virtual WriteResult Write(WriteMessage* message) = 0; virtual bool GetDescriptor(uint8 descType, uint8 descIndex, @@ -86,9 +135,9 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) = 0; - virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; + virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0; - virtual bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) = 0; + virtual bool SetReport(ReportMessage* message) = 0; }; class Backend { @@ -121,6 +170,8 @@ namespace nsyshid std::shared_ptr FindDevice(std::function&)> isWantedDevice); + bool FindDeviceById(uint16 vendorId, uint16 productId); + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); // called from OnAttach() - attach devices that your backend can see here diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp new file mode 100644 index 00000000..95eaf06a --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -0,0 +1,37 @@ +#include "BackendEmulated.h" +#include "Infinity.h" +#include "Skylander.h" +#include "config/CemuConfig.h" + +namespace nsyshid::backend::emulated +{ + BackendEmulated::BackendEmulated() + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendEmulated: emulated backend initialised"); + } + + BackendEmulated::~BackendEmulated() = default; + + bool BackendEmulated::IsInitialisedOk() + { + return true; + } + + void BackendEmulated::AttachVisibleDevices() + { + if (GetConfig().emulated_usb_devices.emulate_skylander_portal && !FindDeviceById(0x1430, 0x0150)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Portal"); + // Add Skylander Portal + auto device = std::make_shared(); + AttachDevice(device); + } + if (GetConfig().emulated_usb_devices.emulate_infinity_base && !FindDeviceById(0x0E6F, 0x0129)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Base"); + // Add Infinity Base + auto device = std::make_shared(); + AttachDevice(device); + } + } +} // namespace nsyshid::backend::emulated \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.h b/src/Cafe/OS/libs/nsyshid/BackendEmulated.h new file mode 100644 index 00000000..cf38a8b7 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.h @@ -0,0 +1,16 @@ +#include "nsyshid.h" +#include "Backend.h" + +namespace nsyshid::backend::emulated +{ + class BackendEmulated : public nsyshid::Backend { + public: + BackendEmulated(); + ~BackendEmulated(); + + bool IsInitialisedOk() override; + + protected: + void AttachVisibleDevices() override; + }; +} // namespace nsyshid::backend::emulated diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 4f88b7ed..7548c998 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -230,6 +230,17 @@ namespace nsyshid::backend::libusb return nullptr; } + std::pair MakeConfigDescriptor(libusb_device* device, uint8 config_num) + { + libusb_config_descriptor* descriptor = nullptr; + const int ret = libusb_get_config_descriptor(device, config_num, &descriptor); + if (ret == LIBUSB_SUCCESS) + return {ret, ConfigDescriptor{descriptor, libusb_free_config_descriptor}}; + + return {ret, ConfigDescriptor{nullptr, [](auto) { + }}}; + } + std::shared_ptr BackendLibusb::CheckAndCreateDevice(libusb_device* dev) { struct libusb_device_descriptor desc; @@ -241,6 +252,20 @@ namespace nsyshid::backend::libusb ret); return nullptr; } + std::vector config_descriptors{}; + for (uint8 i = 0; i < desc.bNumConfigurations; ++i) + { + auto [ret, config_descriptor] = MakeConfigDescriptor(dev, i); + if (ret != LIBUSB_SUCCESS || !config_descriptor) + { + cemuLog_log(LogType::Force, "Failed to make config descriptor {} for {:04x}:{:04x}: {}", + i, desc.idVendor, desc.idProduct, libusb_error_name(ret)); + } + else + { + config_descriptors.emplace_back(std::move(config_descriptor)); + } + } if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) { cemuLog_logDebug(LogType::Force, @@ -253,7 +278,8 @@ namespace nsyshid::backend::libusb 2, 0, libusb_get_bus_number(dev), - libusb_get_device_address(dev)); + libusb_get_device_address(dev), + std::move(config_descriptors)); // figure out device endpoints if (!FindDefaultDeviceEndpoints(dev, device->m_libusbHasEndpointIn, @@ -335,7 +361,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress) + uint8 libusbDeviceAddress, + std::vector configs) : Device(vendorId, productId, interfaceIndex, @@ -351,6 +378,7 @@ namespace nsyshid::backend::libusb m_libusbHasEndpointOut(false), m_libusbEndpointOut(0) { + m_config_descriptors = std::move(configs); } DeviceLibusb::~DeviceLibusb() @@ -418,20 +446,8 @@ namespace nsyshid::backend::libusb } this->m_handleInUseCounter = 0; } - if (libusb_kernel_driver_active(this->m_libusbHandle, 0) == 1) { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver active"); - if (libusb_detach_kernel_driver(this->m_libusbHandle, 0) == 0) - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver detached"); - } - else - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to detach kernel driver"); - } - } - { - int ret = libusb_claim_interface(this->m_libusbHandle, 0); + int ret = ClaimAllInterfaces(0); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); @@ -471,7 +487,7 @@ namespace nsyshid::backend::libusb return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; } - Device::ReadResult DeviceLibusb::Read(uint8* data, sint32 length, sint32& bytesRead) + Device::ReadResult DeviceLibusb::Read(ReadMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -488,8 +504,8 @@ namespace nsyshid::backend::libusb { ret = libusb_bulk_transfer(handleLock->GetHandle(), this->m_libusbEndpointIn, - data, - length, + message->data, + message->length, &actualLength, timeout); } @@ -500,8 +516,8 @@ namespace nsyshid::backend::libusb // success cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", actualLength, - length); - bytesRead = actualLength; + message->length); + message->bytesRead = actualLength; return ReadResult::Success; } cemuLog_logDebug(LogType::Force, @@ -510,7 +526,7 @@ namespace nsyshid::backend::libusb return ReadResult::Error; } - Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten) + Device::WriteResult DeviceLibusb::Write(WriteMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -520,23 +536,23 @@ namespace nsyshid::backend::libusb return WriteResult::Error; } - bytesWritten = 0; + message->bytesWritten = 0; int actualLength = 0; int ret = libusb_bulk_transfer(handleLock->GetHandle(), this->m_libusbEndpointOut, - data, - length, + message->data, + message->length, &actualLength, 0); if (ret == 0) { // success - bytesWritten = actualLength; + message->bytesWritten = actualLength; cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", - bytesWritten, - length); + message->bytesWritten, + message->length); return WriteResult::Success; } cemuLog_logDebug(LogType::Force, @@ -685,7 +701,65 @@ namespace nsyshid::backend::libusb return false; } - bool DeviceLibusb::SetProtocol(uint32 ifIndex, uint32 protocol) + template + static int DoForEachInterface(const Configs& configs, uint8 config_num, Function action) + { + int ret = LIBUSB_ERROR_NOT_FOUND; + if (configs.size() <= config_num || !configs[config_num]) + return ret; + for (uint8 i = 0; i < configs[config_num]->bNumInterfaces; ++i) + { + ret = action(i); + if (ret < LIBUSB_SUCCESS) + break; + } + return ret; + } + + int DeviceLibusb::ClaimAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + if (libusb_kernel_driver_active(this->m_libusbHandle, i)) + { + const int ret2 = libusb_detach_kernel_driver(this->m_libusbHandle, i); + if (ret2 < LIBUSB_SUCCESS && ret2 != LIBUSB_ERROR_NOT_FOUND && + ret2 != LIBUSB_ERROR_NOT_SUPPORTED) + { + cemuLog_log(LogType::Force, "Failed to detach kernel driver {}", libusb_error_name(ret2)); + return ret2; + } + } + return libusb_claim_interface(this->m_libusbHandle, i); + }); + if (ret < LIBUSB_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + return libusb_release_interface(AquireHandleLock()->GetHandle(), i); + }); + if (ret < LIBUSB_SUCCESS && ret != LIBUSB_ERROR_NO_DEVICE && ret != LIBUSB_ERROR_NOT_FOUND) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfacesForCurrentConfig() + { + int config_num; + const int get_config_ret = libusb_get_configuration(AquireHandleLock()->GetHandle(), &config_num); + if (get_config_ret < LIBUSB_SUCCESS) + return get_config_ret; + return ReleaseAllInterfaces(config_num); + } + + bool DeviceLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -693,28 +767,21 @@ namespace nsyshid::backend::libusb cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); return false; } + if (m_interfaceIndex != ifIndex) + m_interfaceIndex = ifIndex; - // ToDo: implement this -#if 0 - // is this correct? Discarding "ifIndex" seems like a bad idea - int ret = libusb_set_configuration(handleLock->getHandle(), protocol); - if (ret == 0) { - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): success"); + ReleaseAllInterfacesForCurrentConfig(); + int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol); + if (ret == LIBUSB_SUCCESS) + ret = ClaimAllInterfaces(protocol); + + if (ret == LIBUSB_SUCCESS) return true; - } - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): failed with error code: {}", - ret); - return false; -#endif - // pretend that everything is fine - return true; + return false; } - bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData, - sint32 originalLength) + bool DeviceLibusb::SetReport(ReportMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -723,20 +790,20 @@ namespace nsyshid::backend::libusb return false; } - // ToDo: implement this -#if 0 - // not sure if libusb_control_transfer() is the right candidate for this - int ret = libusb_control_transfer(handleLock->getHandle(), - bmRequestType, - bRequest, - wValue, - wIndex, - reportData, - length, - timeout); -#endif + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + LIBUSB_REQUEST_SET_CONFIGURATION, + 512, + 0, + message->originalData, + message->originalLength, + 0); - // pretend that everything is fine + if (ret != message->originalLength) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } return true; } diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index 216be6ce..a7b23769 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -44,6 +44,11 @@ namespace nsyshid::backend::libusb bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); }; + template + using UniquePtr = std::unique_ptr; + + using ConfigDescriptor = UniquePtr; + class DeviceLibusb : public nsyshid::Device { public: DeviceLibusb(libusb_context* ctx, @@ -53,7 +58,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress); + uint8 libusbDeviceAddress, + std::vector configs); ~DeviceLibusb() override; @@ -63,9 +69,9 @@ namespace nsyshid::backend::libusb bool IsOpened() override; - ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + ReadResult Read(ReadMessage* message) override; - WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, @@ -73,9 +79,13 @@ namespace nsyshid::backend::libusb uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; - bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + int ClaimAllInterfaces(uint8 config_num); + int ReleaseAllInterfaces(uint8 config_num); + int ReleaseAllInterfacesForCurrentConfig(); + + bool SetReport(ReportMessage* message) override; uint8 m_libusbBusNumber; uint8 m_libusbDeviceAddress; @@ -92,6 +102,7 @@ namespace nsyshid::backend::libusb std::atomic m_handleInUseCounter; std::condition_variable m_handleInUseCounterDecremented; libusb_device_handle* m_libusbHandle; + std::vector m_config_descriptors; class HandleLock { public: diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 23da5798..267111b2 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -67,13 +67,6 @@ namespace nsyshid::backend::windows device->m_productId); } } - else - { - cemuLog_log(LogType::Force, - "nsyshid::BackendWindowsHID: device not on whitelist: {:04x}:{:04x}", - device->m_vendorId, - device->m_productId); - } } CloseHandle(hHIDDevice); } @@ -125,14 +118,12 @@ namespace nsyshid::backend::windows } if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) { - cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", - maxPacketInputLength); + cemuLog_logDebug(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); + cemuLog_logDebug(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength); maxPacketOutputLength = 0x20; } @@ -196,20 +187,20 @@ namespace nsyshid::backend::windows return m_hFile != INVALID_HANDLE_VALUE; } - Device::ReadResult DeviceWindowsHID::Read(uint8* data, sint32 length, sint32& bytesRead) + Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message) { - bytesRead = 0; + message->bytesRead = 0; DWORD bt; OVERLAPPED ovlp = {0}; ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - uint8* tempBuffer = (uint8*)malloc(length + 1); + uint8* tempBuffer = (uint8*)malloc(message->length + 1); sint32 transferLength = 0; // minus report byte - _debugPrintHex("HID_READ_BEFORE", data, length); + _debugPrintHex("HID_READ_BEFORE", message->data, message->length); - cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length); - BOOL readResult = ReadFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length); + BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); if (readResult != FALSE) { // sometimes we get the result immediately @@ -247,7 +238,7 @@ namespace nsyshid::backend::windows ReadResult result = ReadResult::Success; if (bt != 0) { - memcpy(data, tempBuffer + 1, transferLength); + memcpy(message->data, tempBuffer + 1, transferLength); sint32 hidReadLength = transferLength; char debugOutput[1024] = {0}; @@ -257,7 +248,7 @@ namespace nsyshid::backend::windows } cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); - bytesRead = transferLength; + message->bytesRead = transferLength; result = ReadResult::Success; } else @@ -270,19 +261,19 @@ namespace nsyshid::backend::windows return result; } - Device::WriteResult DeviceWindowsHID::Write(uint8* data, sint32 length, sint32& bytesWritten) + Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message) { - bytesWritten = 0; + message->bytesWritten = 0; DWORD bt; OVERLAPPED ovlp = {0}; ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - uint8* tempBuffer = (uint8*)malloc(length + 1); - memcpy(tempBuffer + 1, data, length); + uint8* tempBuffer = (uint8*)malloc(message->length + 1); + memcpy(tempBuffer + 1, message->data, message->length); tempBuffer[0] = 0; // report byte? - cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", length); - BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length); + BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); if (writeResult != FALSE) { // sometimes we get the result immediately @@ -314,7 +305,7 @@ namespace nsyshid::backend::windows if (bt != 0) { - bytesWritten = length; + message->bytesWritten = message->length; return WriteResult::Success; } return WriteResult::Error; @@ -400,19 +391,19 @@ namespace nsyshid::backend::windows return false; } - bool DeviceWindowsHID::SetProtocol(uint32 ifIndef, uint32 protocol) + bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol) { // ToDo: implement this // pretend that everything is fine return true; } - bool DeviceWindowsHID::SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + bool DeviceWindowsHID::SetReport(ReportMessage* message) { sint32 retryCount = 0; while (true) { - BOOL r = HidD_SetOutputReport(this->m_hFile, reportData, length); + BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length); if (r != FALSE) break; Sleep(20); // retry diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h index 049b33e4..9a8a78e9 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -41,15 +41,15 @@ namespace nsyshid::backend::windows bool IsOpened() override; - ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + ReadResult Read(ReadMessage* message) override; - WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndef, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; - bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + bool SetReport(ReportMessage* message) override; private: wchar_t* m_devicePath; diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.cpp b/src/Cafe/OS/libs/nsyshid/Infinity.cpp new file mode 100644 index 00000000..ab44ef4a --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Infinity.cpp @@ -0,0 +1,1102 @@ +#include "Infinity.h" + +#include + +#include "nsyshid.h" +#include "Backend.h" + +#include "util/crypto/aes128.h" + +#include +#include "openssl/sha.h" + +namespace nsyshid +{ + static constexpr std::array SHA1_CONSTANT = { + 0xAF, 0x62, 0xD2, 0xEC, 0x04, 0x91, 0x96, 0x8C, 0xC5, 0x2A, 0x1A, 0x71, 0x65, 0xF8, 0x65, 0xFE, + 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73, 0x6e, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33}; + + InfinityUSB g_infinitybase; + + const std::map> s_listFigures = { + {0x0F4241, {1, "Mr. Incredible"}}, + {0x0F4242, {1, "Sulley"}}, + {0x0F4243, {1, "Jack Sparrow"}}, + {0x0F4244, {1, "Lone Ranger"}}, + {0x0F4245, {1, "Tonto"}}, + {0x0F4246, {1, "Lightning McQueen"}}, + {0x0F4247, {1, "Holley Shiftwell"}}, + {0x0F4248, {1, "Buzz Lightyear"}}, + {0x0F4249, {1, "Jessie"}}, + {0x0F424A, {1, "Mike"}}, + {0x0F424B, {1, "Mrs. Incredible"}}, + {0x0F424C, {1, "Hector Barbossa"}}, + {0x0F424D, {1, "Davy Jones"}}, + {0x0F424E, {1, "Randy"}}, + {0x0F424F, {1, "Syndrome"}}, + {0x0F4250, {1, "Woody"}}, + {0x0F4251, {1, "Mater"}}, + {0x0F4252, {1, "Dash"}}, + {0x0F4253, {1, "Violet"}}, + {0x0F4254, {1, "Francesco Bernoulli"}}, + {0x0F4255, {1, "Sorcerer's Apprentice Mickey"}}, + {0x0F4256, {1, "Jack Skellington"}}, + {0x0F4257, {1, "Rapunzel"}}, + {0x0F4258, {1, "Anna"}}, + {0x0F4259, {1, "Elsa"}}, + {0x0F425A, {1, "Phineas"}}, + {0x0F425B, {1, "Agent P"}}, + {0x0F425C, {1, "Wreck-It Ralph"}}, + {0x0F425D, {1, "Vanellope"}}, + {0x0F425E, {1, "Mr. Incredible (Crystal)"}}, + {0x0F425F, {1, "Jack Sparrow (Crystal)"}}, + {0x0F4260, {1, "Sulley (Crystal)"}}, + {0x0F4261, {1, "Lightning McQueen (Crystal)"}}, + {0x0F4262, {1, "Lone Ranger (Crystal)"}}, + {0x0F4263, {1, "Buzz Lightyear (Crystal)"}}, + {0x0F4264, {1, "Agent P (Crystal)"}}, + {0x0F4265, {1, "Sorcerer's Apprentice Mickey (Crystal)"}}, + {0x0F4266, {1, "Buzz Lightyear (Glowing)"}}, + {0x0F42A4, {2, "Captain America"}}, + {0x0F42A5, {2, "Hulk"}}, + {0x0F42A6, {2, "Iron Man"}}, + {0x0F42A7, {2, "Thor"}}, + {0x0F42A8, {2, "Groot"}}, + {0x0F42A9, {2, "Rocket Raccoon"}}, + {0x0F42AA, {2, "Star-Lord"}}, + {0x0F42AB, {2, "Spider-Man"}}, + {0x0F42AC, {2, "Nick Fury"}}, + {0x0F42AD, {2, "Black Widow"}}, + {0x0F42AE, {2, "Hawkeye"}}, + {0x0F42AF, {2, "Drax"}}, + {0x0F42B0, {2, "Gamora"}}, + {0x0F42B1, {2, "Iron Fist"}}, + {0x0F42B2, {2, "Nova"}}, + {0x0F42B3, {2, "Venom"}}, + {0x0F42B4, {2, "Donald Duck"}}, + {0x0F42B5, {2, "Aladdin"}}, + {0x0F42B6, {2, "Stitch"}}, + {0x0F42B7, {2, "Merida"}}, + {0x0F42B8, {2, "Tinker Bell"}}, + {0x0F42B9, {2, "Maleficent"}}, + {0x0F42BA, {2, "Hiro"}}, + {0x0F42BB, {2, "Baymax"}}, + {0x0F42BC, {2, "Loki"}}, + {0x0F42BD, {2, "Ronan"}}, + {0x0F42BE, {2, "Green Goblin"}}, + {0x0F42BF, {2, "Falcon"}}, + {0x0F42C0, {2, "Yondu"}}, + {0x0F42C1, {2, "Jasmine"}}, + {0x0F42C6, {2, "Black Suit Spider-Man"}}, + {0x0F42D6, {3, "Sam Flynn"}}, + {0x0F42D7, {3, "Quorra"}}, + {0x0F4308, {3, "Anakin Skywalker"}}, + {0x0F4309, {3, "Obi-Wan Kenobi"}}, + {0x0F430A, {3, "Yoda"}}, + {0x0F430B, {3, "Ahsoka Tano"}}, + {0x0F430C, {3, "Darth Maul"}}, + {0x0F430E, {3, "Luke Skywalker"}}, + {0x0F430F, {3, "Han Solo"}}, + {0x0F4310, {3, "Princess Leia"}}, + {0x0F4311, {3, "Chewbacca"}}, + {0x0F4312, {3, "Darth Vader"}}, + {0x0F4313, {3, "Boba Fett"}}, + {0x0F4314, {3, "Ezra Bridger"}}, + {0x0F4315, {3, "Kanan Jarrus"}}, + {0x0F4316, {3, "Sabine Wren"}}, + {0x0F4317, {3, "Zeb Orrelios"}}, + {0x0F4318, {3, "Joy"}}, + {0x0F4319, {3, "Anger"}}, + {0x0F431A, {3, "Fear"}}, + {0x0F431B, {3, "Sadness"}}, + {0x0F431C, {3, "Disgust"}}, + {0x0F431D, {3, "Mickey Mouse"}}, + {0x0F431E, {3, "Minnie Mouse"}}, + {0x0F431F, {3, "Mulan"}}, + {0x0F4320, {3, "Olaf"}}, + {0x0F4321, {3, "Vision"}}, + {0x0F4322, {3, "Ultron"}}, + {0x0F4323, {3, "Ant-Man"}}, + {0x0F4325, {3, "Captain America - The First Avenger"}}, + {0x0F4326, {3, "Finn"}}, + {0x0F4327, {3, "Kylo Ren"}}, + {0x0F4328, {3, "Poe Dameron"}}, + {0x0F4329, {3, "Rey"}}, + {0x0F432B, {3, "Spot"}}, + {0x0F432C, {3, "Nick Wilde"}}, + {0x0F432D, {3, "Judy Hopps"}}, + {0x0F432E, {3, "Hulkbuster"}}, + {0x0F432F, {3, "Anakin Skywalker (Light FX)"}}, + {0x0F4330, {3, "Obi-Wan Kenobi (Light FX)"}}, + {0x0F4331, {3, "Yoda (Light FX)"}}, + {0x0F4332, {3, "Luke Skywalker (Light FX)"}}, + {0x0F4333, {3, "Darth Vader (Light FX)"}}, + {0x0F4334, {3, "Kanan Jarrus (Light FX)"}}, + {0x0F4335, {3, "Kylo Ren (Light FX)"}}, + {0x0F4336, {3, "Black Panther"}}, + {0x0F436C, {3, "Nemo"}}, + {0x0F436D, {3, "Dory"}}, + {0x0F436E, {3, "Baloo"}}, + {0x0F436F, {3, "Alice"}}, + {0x0F4370, {3, "Mad Hatter"}}, + {0x0F4371, {3, "Time"}}, + {0x0F4372, {3, "Peter Pan"}}, + {0x1E8481, {1, "Starter Play Set"}}, + {0x1E8482, {1, "Lone Ranger Play Set"}}, + {0x1E8483, {1, "Cars Play Set"}}, + {0x1E8484, {1, "Toy Story in Space Play Set"}}, + {0x1E84E4, {2, "Marvel's The Avengers Play Set"}}, + {0x1E84E5, {2, "Marvel's Spider-Man Play Set"}}, + {0x1E84E6, {2, "Marvel's Guardians of the Galaxy Play Set"}}, + {0x1E84E7, {2, "Assault on Asgard"}}, + {0x1E84E8, {2, "Escape from the Kyln"}}, + {0x1E84E9, {2, "Stitch's Tropical Rescue"}}, + {0x1E84EA, {2, "Brave Forest Siege"}}, + {0x1E8548, {3, "Inside Out Play Set"}}, + {0x1E8549, {3, "Star Wars: Twilight of the Republic Play Set"}}, + {0x1E854A, {3, "Star Wars: Rise Against the Empire Play Set"}}, + {0x1E854B, {3, "Star Wars: The Force Awakens Play Set"}}, + {0x1E854C, {3, "Marvel Battlegrounds Play Set"}}, + {0x1E854D, {3, "Toy Box Speedway"}}, + {0x1E854E, {3, "Toy Box Takeover"}}, + {0x1E85AC, {3, "Finding Dory Play Set"}}, + {0x2DC6C3, {1, "Bolt's Super Strength"}}, + {0x2DC6C4, {1, "Ralph's Power of Destruction"}}, + {0x2DC6C5, {1, "Chernabog's Power"}}, + {0x2DC6C6, {1, "C.H.R.O.M.E. Damage Increaser"}}, + {0x2DC6C7, {1, "Dr. Doofenshmirtz's Damage-Inator!"}}, + {0x2DC6C8, {1, "Electro-Charge"}}, + {0x2DC6C9, {1, "Fix-It Felix's Repair Power"}}, + {0x2DC6CA, {1, "Rapunzel's Healing"}}, + {0x2DC6CB, {1, "C.H.R.O.M.E. Armor Shield"}}, + {0x2DC6CC, {1, "Star Command Shield"}}, + {0x2DC6CD, {1, "Violet's Force Field"}}, + {0x2DC6CE, {1, "Pieces of Eight"}}, + {0x2DC6CF, {1, "Scrooge McDuck's Lucky Dime"}}, + {0x2DC6D0, {1, "User Control"}}, + {0x2DC6D1, {1, "Sorcerer Mickey's Hat"}}, + {0x2DC6FE, {1, "Emperor Zurg's Wrath"}}, + {0x2DC6FF, {1, "Merlin's Summon"}}, + {0x2DC765, {2, "Enchanted Rose"}}, + {0x2DC766, {2, "Mulan's Training Uniform"}}, + {0x2DC767, {2, "Flubber"}}, + {0x2DC768, {2, "S.H.I.E.L.D. Helicarrier Strike"}}, + {0x2DC769, {2, "Zeus' Thunderbolts"}}, + {0x2DC76A, {2, "King Louie's Monkeys"}}, + {0x2DC76B, {2, "Infinity Gauntlet"}}, + {0x2DC76D, {2, "Sorcerer Supreme"}}, + {0x2DC76E, {2, "Maleficent's Spell Cast"}}, + {0x2DC76F, {2, "Chernabog's Spirit Cyclone"}}, + {0x2DC770, {2, "Marvel Team-Up: Capt. Marvel"}}, + {0x2DC771, {2, "Marvel Team-Up: Iron Patriot"}}, + {0x2DC772, {2, "Marvel Team-Up: Ant-Man"}}, + {0x2DC773, {2, "Marvel Team-Up: White Tiger"}}, + {0x2DC774, {2, "Marvel Team-Up: Yondu"}}, + {0x2DC775, {2, "Marvel Team-Up: Winter Soldier"}}, + {0x2DC776, {2, "Stark Arc Reactor"}}, + {0x2DC777, {2, "Gamma Rays"}}, + {0x2DC778, {2, "Alien Symbiote"}}, + {0x2DC779, {2, "All for One"}}, + {0x2DC77A, {2, "Sandy Claws Surprise"}}, + {0x2DC77B, {2, "Glory Days"}}, + {0x2DC77C, {2, "Cursed Pirate Gold"}}, + {0x2DC77D, {2, "Sentinel of Liberty"}}, + {0x2DC77E, {2, "The Immortal Iron Fist"}}, + {0x2DC77F, {2, "Space Armor"}}, + {0x2DC780, {2, "Rags to Riches"}}, + {0x2DC781, {2, "Ultimate Falcon"}}, + {0x2DC788, {3, "Tomorrowland Time Bomb"}}, + {0x2DC78E, {3, "Galactic Team-Up: Mace Windu"}}, + {0x2DC791, {3, "Luke's Rebel Alliance Flight Suit Costume"}}, + {0x2DC798, {3, "Finn's Stormtrooper Costume"}}, + {0x2DC799, {3, "Poe's Resistance Jacket"}}, + {0x2DC79A, {3, "Resistance Tactical Strike"}}, + {0x2DC79E, {3, "Officer Nick Wilde"}}, + {0x2DC79F, {3, "Meter Maid Judy"}}, + {0x2DC7A2, {3, "Darkhawk's Blast"}}, + {0x2DC7A3, {3, "Cosmic Cube Blast"}}, + {0x2DC7A4, {3, "Princess Leia's Boushh Disguise"}}, + {0x2DC7A6, {3, "Nova Corps Strike"}}, + {0x2DC7A7, {3, "King Mickey"}}, + {0x3D0912, {1, "Mickey's Car"}}, + {0x3D0913, {1, "Cinderella's Coach"}}, + {0x3D0914, {1, "Electric Mayhem Bus"}}, + {0x3D0915, {1, "Cruella De Vil's Car"}}, + {0x3D0916, {1, "Pizza Planet Delivery Truck"}}, + {0x3D0917, {1, "Mike's New Car"}}, + {0x3D0919, {1, "Parking Lot Tram"}}, + {0x3D091A, {1, "Captain Hook's Ship"}}, + {0x3D091B, {1, "Dumbo"}}, + {0x3D091C, {1, "Calico Helicopter"}}, + {0x3D091D, {1, "Maximus"}}, + {0x3D091E, {1, "Angus"}}, + {0x3D091F, {1, "Abu the Elephant"}}, + {0x3D0920, {1, "Headless Horseman's Horse"}}, + {0x3D0921, {1, "Phillipe"}}, + {0x3D0922, {1, "Khan"}}, + {0x3D0923, {1, "Tantor"}}, + {0x3D0924, {1, "Dragon Firework Cannon"}}, + {0x3D0925, {1, "Stitch's Blaster"}}, + {0x3D0926, {1, "Toy Story Mania Blaster"}}, + {0x3D0927, {1, "Flamingo Croquet Mallet"}}, + {0x3D0928, {1, "Carl Fredricksen's Cane"}}, + {0x3D0929, {1, "Hangin' Ten Stitch With Surfboard"}}, + {0x3D092A, {1, "Condorman Glider"}}, + {0x3D092B, {1, "WALL-E's Fire Extinguisher"}}, + {0x3D092C, {1, "On the Grid"}}, + {0x3D092D, {1, "WALL-E's Collection"}}, + {0x3D092E, {1, "King Candy's Dessert Toppings"}}, + {0x3D0930, {1, "Victor's Experiments"}}, + {0x3D0931, {1, "Jack's Scary Decorations"}}, + {0x3D0933, {1, "Frozen Flourish"}}, + {0x3D0934, {1, "Rapunzel's Kingdom"}}, + {0x3D0935, {1, "TRON Interface"}}, + {0x3D0936, {1, "Buy N Large Atmosphere"}}, + {0x3D0937, {1, "Sugar Rush Sky"}}, + {0x3D0939, {1, "New Holland Skyline"}}, + {0x3D093A, {1, "Halloween Town Sky"}}, + {0x3D093C, {1, "Chill in the Air"}}, + {0x3D093D, {1, "Rapunzel's Birthday Sky"}}, + {0x3D0940, {1, "Astro Blasters Space Cruiser"}}, + {0x3D0941, {1, "Marlin's Reef"}}, + {0x3D0942, {1, "Nemo's Seascape"}}, + {0x3D0943, {1, "Alice's Wonderland"}}, + {0x3D0944, {1, "Tulgey Wood"}}, + {0x3D0945, {1, "Tri-State Area Terrain"}}, + {0x3D0946, {1, "Danville Sky"}}, + {0x3D0965, {2, "Stark Tech"}}, + {0x3D0966, {2, "Spider-Streets"}}, + {0x3D0967, {2, "World War Hulk"}}, + {0x3D0968, {2, "Gravity Falls Forest"}}, + {0x3D0969, {2, "Neverland"}}, + {0x3D096A, {2, "Simba's Pridelands"}}, + {0x3D096C, {2, "Calhoun's Command"}}, + {0x3D096D, {2, "Star-Lord's Galaxy"}}, + {0x3D096E, {2, "Dinosaur World"}}, + {0x3D096F, {2, "Groot's Roots"}}, + {0x3D0970, {2, "Mulan's Countryside"}}, + {0x3D0971, {2, "The Sands of Agrabah"}}, + {0x3D0974, {2, "A Small World"}}, + {0x3D0975, {2, "View from the Suit"}}, + {0x3D0976, {2, "Spider-Sky"}}, + {0x3D0977, {2, "World War Hulk Sky"}}, + {0x3D0978, {2, "Gravity Falls Sky"}}, + {0x3D0979, {2, "Second Star to the Right"}}, + {0x3D097A, {2, "The King's Domain"}}, + {0x3D097C, {2, "CyBug Swarm"}}, + {0x3D097D, {2, "The Rip"}}, + {0x3D097E, {2, "Forgotten Skies"}}, + {0x3D097F, {2, "Groot's View"}}, + {0x3D0980, {2, "The Middle Kingdom"}}, + {0x3D0984, {2, "Skies of the World"}}, + {0x3D0985, {2, "S.H.I.E.L.D. Containment Truck"}}, + {0x3D0986, {2, "Main Street Electrical Parade Float"}}, + {0x3D0987, {2, "Mr. Toad's Motorcar"}}, + {0x3D0988, {2, "Le Maximum"}}, + {0x3D0989, {2, "Alice in Wonderland's Caterpillar"}}, + {0x3D098A, {2, "Eglantine's Motorcycle"}}, + {0x3D098B, {2, "Medusa's Swamp Mobile"}}, + {0x3D098C, {2, "Hydra Motorcycle"}}, + {0x3D098D, {2, "Darkwing Duck's Ratcatcher"}}, + {0x3D098F, {2, "The USS Swinetrek"}}, + {0x3D0991, {2, "Spider-Copter"}}, + {0x3D0992, {2, "Aerial Area Rug"}}, + {0x3D0993, {2, "Jack-O-Lantern's Glider"}}, + {0x3D0994, {2, "Spider-Buggy"}}, + {0x3D0995, {2, "Jack Skellington's Reindeer"}}, + {0x3D0996, {2, "Fantasyland Carousel Horse"}}, + {0x3D0997, {2, "Odin's Horse"}}, + {0x3D0998, {2, "Gus the Mule"}}, + {0x3D099A, {2, "Darkwing Duck's Grappling Gun"}}, + {0x3D099C, {2, "Ghost Rider's Chain Whip"}}, + {0x3D099D, {2, "Lew Zealand's Boomerang Fish"}}, + {0x3D099E, {2, "Sergeant Calhoun's Blaster"}}, + {0x3D09A0, {2, "Falcon's Wings"}}, + {0x3D09A1, {2, "Mabel's Kittens for Fists"}}, + {0x3D09A2, {2, "Jim Hawkins' Solar Board"}}, + {0x3D09A3, {2, "Black Panther's Vibranium Knives"}}, + {0x3D09A4, {2, "Cloak of Levitation"}}, + {0x3D09A5, {2, "Aladdin's Magic Carpet"}}, + {0x3D09A6, {2, "Honey Lemon's Ice Capsules"}}, + {0x3D09A7, {2, "Jasmine's Palace View"}}, + {0x3D09C1, {2, "Lola"}}, + {0x3D09C2, {2, "Spider-Cycle"}}, + {0x3D09C3, {2, "The Avenjet"}}, + {0x3D09C4, {2, "Spider-Glider"}}, + {0x3D09C5, {2, "Light Cycle"}}, + {0x3D09C6, {2, "Light Jet"}}, + {0x3D09C9, {3, "Retro Ray Gun"}}, + {0x3D09CA, {3, "Tomorrowland Futurescape"}}, + {0x3D09CB, {3, "Tomorrowland Stratosphere"}}, + {0x3D09CC, {3, "Skies Over Felucia"}}, + {0x3D09CD, {3, "Forests of Felucia"}}, + {0x3D09CF, {3, "General Grievous' Wheel Bike"}}, + {0x3D09D2, {3, "Slave I Flyer"}}, + {0x3D09D3, {3, "Y-Wing Fighter"}}, + {0x3D09D4, {3, "Arlo"}}, + {0x3D09D5, {3, "Nash"}}, + {0x3D09D6, {3, "Butch"}}, + {0x3D09D7, {3, "Ramsey"}}, + {0x3D09DC, {3, "Stars Over Sahara Square"}}, + {0x3D09DD, {3, "Sahara Square Sands"}}, + {0x3D09E0, {3, "Ghost Rider's Motorcycle"}}, + {0x3D09E5, {3, "Quad Jumper"}}}; + + InfinityBaseDevice::InfinityBaseDevice() + : Device(0x0E6F, 0x0129, 1, 2, 0) + { + m_IsOpened = false; + } + + bool InfinityBaseDevice::Open() + { + if (!IsOpened()) + { + m_IsOpened = true; + } + return true; + } + + void InfinityBaseDevice::Close() + { + if (IsOpened()) + { + m_IsOpened = false; + } + } + + bool InfinityBaseDevice::IsOpened() + { + return m_IsOpened; + } + + Device::ReadResult InfinityBaseDevice::Read(ReadMessage* message) + { + memcpy(message->data, g_infinitybase.GetStatus().data(), message->length); + message->bytesRead = message->length; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return Device::ReadResult::Success; + } + + Device::WriteResult InfinityBaseDevice::Write(WriteMessage* message) + { + g_infinitybase.SendCommand(message->data, message->length); + message->bytesWritten = message->length; + return Device::WriteResult::Success; + } + + bool InfinityBaseDevice::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 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))); + return true; + } + + bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol) + { + return true; + } + + bool InfinityBaseDevice::SetReport(ReportMessage* message) + { + return true; + } + + std::array InfinityUSB::GetStatus() + { + std::array response = {}; + + bool responded = false; + + do + { + if (!m_figureAddedRemovedResponses.empty()) + { + memcpy(response.data(), m_figureAddedRemovedResponses.front().data(), + 0x20); + m_figureAddedRemovedResponses.pop(); + responded = true; + } + else if (!m_queries.empty()) + { + memcpy(response.data(), m_queries.front().data(), 0x20); + m_queries.pop(); + responded = true; + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + /* code */ + } + while (!responded); + + return response; + } + + void InfinityUSB::SendCommand(uint8* buf, sint32 originalLength) + { + const uint8 command = buf[2]; + const uint8 sequence = buf[3]; + + std::array q_result{}; + + switch (command) + { + case 0x80: + { + q_result = {0xaa, 0x15, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x03, + 0x02, 0x09, 0x09, 0x43, 0x20, 0x32, 0x62, 0x36, + 0x36, 0x4b, 0x34, 0x99, 0x67, 0x31, 0x93, 0x8c}; + break; + } + case 0x81: + { + // Initiate Challenge + g_infinitybase.DescrambleAndSeed(buf, sequence, q_result); + break; + } + case 0x83: + { + // Challenge Response + g_infinitybase.GetNextAndScramble(sequence, q_result); + break; + } + case 0x90: + case 0x92: + case 0x93: + case 0x95: + case 0x96: + { + // Color commands + g_infinitybase.GetBlankResponse(sequence, q_result); + break; + } + case 0xA1: + { + // Get Present Figures + g_infinitybase.GetPresentFigures(sequence, q_result); + break; + } + case 0xA2: + { + // Read Block from Figure + g_infinitybase.QueryBlock(buf[4], buf[5], q_result, sequence); + break; + } + case 0xA3: + { + // Write block to figure + g_infinitybase.WriteBlock(buf[4], buf[5], &buf[7], q_result, sequence); + break; + } + case 0xB4: + { + // Get figure ID + g_infinitybase.GetFigureIdentifier(buf[4], sequence, q_result); + break; + } + case 0xB5: + { + // Get status? + g_infinitybase.GetBlankResponse(sequence, q_result); + break; + } + default: + cemu_assert_error(); + break; + } + + m_queries.push(q_result); + } + + uint8 InfinityUSB::GenerateChecksum(const std::array& data, + int numOfBytes) const + { + int checksum = 0; + for (int i = 0; i < numOfBytes; i++) + { + checksum += data[i]; + } + return (checksum & 0xFF); + } + + void InfinityUSB::GetBlankResponse(uint8 sequence, + std::array& replyBuf) + { + replyBuf[0] = 0xaa; + replyBuf[1] = 0x01; + replyBuf[2] = sequence; + replyBuf[3] = GenerateChecksum(replyBuf, 3); + } + + void InfinityUSB::DescrambleAndSeed(uint8* buf, uint8 sequence, + std::array& replyBuf) + { + uint64 value = uint64(buf[4]) << 56 | uint64(buf[5]) << 48 | + uint64(buf[6]) << 40 | uint64(buf[7]) << 32 | + uint64(buf[8]) << 24 | uint64(buf[9]) << 16 | + uint64(buf[10]) << 8 | uint64(buf[11]); + uint32 seed = Descramble(value); + GenerateSeed(seed); + GetBlankResponse(sequence, replyBuf); + } + + void InfinityUSB::GetNextAndScramble(uint8 sequence, + std::array& replyBuf) + { + const uint32 nextRandom = GetNext(); + const uint64 scrambledNextRandom = Scramble(nextRandom, 0); + replyBuf = {0xAA, 0x09, sequence}; + replyBuf[3] = uint8((scrambledNextRandom >> 56) & 0xFF); + replyBuf[4] = uint8((scrambledNextRandom >> 48) & 0xFF); + replyBuf[5] = uint8((scrambledNextRandom >> 40) & 0xFF); + replyBuf[6] = uint8((scrambledNextRandom >> 32) & 0xFF); + replyBuf[7] = uint8((scrambledNextRandom >> 24) & 0xFF); + replyBuf[8] = uint8((scrambledNextRandom >> 16) & 0xFF); + replyBuf[9] = uint8((scrambledNextRandom >> 8) & 0xFF); + replyBuf[10] = uint8(scrambledNextRandom & 0xFF); + replyBuf[11] = GenerateChecksum(replyBuf, 11); + } + + uint32 InfinityUSB::Descramble(uint64 numToDescramble) + { + uint64 mask = 0x8E55AA1B3999E8AA; + uint32 ret = 0; + + for (int i = 0; i < 64; i++) + { + if (mask & 0x8000000000000000) + { + ret = (ret << 1) | (numToDescramble & 0x01); + } + + numToDescramble >>= 1; + mask <<= 1; + } + + return ret; + } + + uint64 InfinityUSB::Scramble(uint32 numToScramble, uint32 garbage) + { + uint64 mask = 0x8E55AA1B3999E8AA; + uint64 ret = 0; + + for (int i = 0; i < 64; i++) + { + ret <<= 1; + + if ((mask & 1) != 0) + { + ret |= (numToScramble & 1); + numToScramble >>= 1; + } + else + { + ret |= (garbage & 1); + garbage >>= 1; + } + + mask >>= 1; + } + + return ret; + } + + void InfinityUSB::GenerateSeed(uint32 seed) + { + m_randomA = 0xF1EA5EED; + m_randomB = seed; + m_randomC = seed; + m_randomD = seed; + + for (int i = 0; i < 23; i++) + { + GetNext(); + } + } + + uint32 InfinityUSB::GetNext() + { + uint32 a = m_randomA; + uint32 b = m_randomB; + uint32 c = m_randomC; + uint32 ret = std::rotl(m_randomB, 27); + + const uint32 temp = (a + ((ret ^ 0xFFFFFFFF) + 1)); + b ^= std::rotl(c, 17); + a = m_randomD; + c += a; + ret = b + temp; + a += temp; + + m_randomC = a; + m_randomA = b; + m_randomB = c; + m_randomD = ret; + + return ret; + } + + void InfinityUSB::GetPresentFigures(uint8 sequence, + std::array& replyBuf) + { + int x = 3; + for (uint8 i = 0; i < m_figures.size(); i++) + { + uint8 slot = i == 0 ? 0x10 : (i < 4) ? 0x20 + : 0x30; + if (m_figures[i].present) + { + replyBuf[x] = slot + m_figures[i].orderAdded; + replyBuf[x + 1] = 0x09; + x += 2; + } + } + replyBuf[0] = 0xaa; + replyBuf[1] = x - 2; + replyBuf[2] = sequence; + replyBuf[x] = GenerateChecksum(replyBuf, x); + } + + InfinityUSB::InfinityFigure& + InfinityUSB::GetFigureByOrder(uint8 orderAdded) + { + for (uint8 i = 0; i < m_figures.size(); i++) + { + if (m_figures[i].orderAdded == orderAdded) + { + return m_figures[i]; + } + } + return m_figures[0]; + } + + void InfinityUSB::QueryBlock(uint8 fig_num, uint8 block, + std::array& replyBuf, + uint8 sequence) + { + std::lock_guard lock(m_infinityMutex); + + InfinityFigure& figure = GetFigureByOrder(fig_num); + + replyBuf[0] = 0xaa; + replyBuf[1] = 0x12; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + const uint8 file_block = (block == 0) ? 1 : (block * 4); + if (figure.present && file_block < 20) + { + memcpy(&replyBuf[4], figure.data.data() + (16 * file_block), 16); + } + replyBuf[20] = GenerateChecksum(replyBuf, 20); + } + + void InfinityUSB::WriteBlock(uint8 fig_num, uint8 block, + const uint8* to_write_buf, + std::array& replyBuf, + uint8 sequence) + { + std::lock_guard lock(m_infinityMutex); + + InfinityFigure& figure = GetFigureByOrder(fig_num); + + replyBuf[0] = 0xaa; + replyBuf[1] = 0x02; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + const uint8 file_block = (block == 0) ? 1 : (block * 4); + if (figure.present && file_block < 20) + { + memcpy(figure.data.data() + (file_block * 16), to_write_buf, 16); + figure.Save(); + } + replyBuf[4] = GenerateChecksum(replyBuf, 4); + } + + void InfinityUSB::GetFigureIdentifier(uint8 fig_num, uint8 sequence, + std::array& replyBuf) + { + std::lock_guard lock(m_infinityMutex); + + InfinityFigure& figure = GetFigureByOrder(fig_num); + + replyBuf[0] = 0xaa; + replyBuf[1] = 0x09; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + + if (figure.present) + { + memcpy(&replyBuf[4], figure.data.data(), 7); + } + replyBuf[11] = GenerateChecksum(replyBuf, 11); + } + + std::pair InfinityUSB::FindFigure(uint32 figNum) + { + for (const auto& it : GetFigureList()) + { + if (it.first == figNum) + { + return it.second; + } + } + return {0, fmt::format("Unknown Figure ({})", figNum)}; + } + + std::map> InfinityUSB::GetFigureList() + { + return s_listFigures; + } + + void InfinityUSB::InfinityFigure::Save() + { + if (!infFile) + return; + + infFile->SetPosition(0); + infFile->writeData(data.data(), data.size()); + } + + bool InfinityUSB::RemoveFigure(uint8 position) + { + std::lock_guard lock(m_infinityMutex); + InfinityFigure& figure = m_figures[position]; + + figure.Save(); + figure.infFile.reset(); + + if (figure.present) + { + figure.present = false; + + position = DeriveFigurePosition(position); + if (position == 0) + { + return false; + } + + std::array figureChangeResponse = {0xab, 0x04, position, 0x09, figure.orderAdded, + 0x01}; + figureChangeResponse[6] = GenerateChecksum(figureChangeResponse, 6); + m_figureAddedRemovedResponses.push(figureChangeResponse); + + return true; + } + return false; + } + + uint32 + InfinityUSB::LoadFigure(const std::array& buf, + std::unique_ptr inFile, uint8 position) + { + std::lock_guard lock(m_infinityMutex); + uint8 orderAdded; + + std::vector sha1Calc = {SHA1_CONSTANT.begin(), SHA1_CONSTANT.end() - 1}; + for (int i = 0; i < 7; i++) + { + sha1Calc.push_back(buf[i]); + } + + std::array key = GenerateInfinityFigureKey(sha1Calc); + + std::array infinity_decrypted_block = {}; + std::array encryptedBlock = {}; + memcpy(encryptedBlock.data(), &buf[16], 16); + + AES128_ECB_decrypt(encryptedBlock.data(), key.data(), infinity_decrypted_block.data()); + + uint32 number = uint32(infinity_decrypted_block[1]) << 16 | uint32(infinity_decrypted_block[2]) << 8 | + uint32(infinity_decrypted_block[3]); + + InfinityFigure& figure = m_figures[position]; + + figure.infFile = std::move(inFile); + memcpy(figure.data.data(), buf.data(), figure.data.size()); + figure.present = true; + if (figure.orderAdded == 255) + { + figure.orderAdded = m_figureOrder; + m_figureOrder++; + } + orderAdded = figure.orderAdded; + + position = DeriveFigurePosition(position); + if (position == 0) + { + return 0; + } + + std::array figureChangeResponse = {0xab, 0x04, position, 0x09, orderAdded, 0x00}; + figureChangeResponse[6] = GenerateChecksum(figureChangeResponse, 6); + m_figureAddedRemovedResponses.push(figureChangeResponse); + + return number; + } + + static uint32 InfinityCRC32(const std::array& buffer) + { + static constexpr std::array CRC32_TABLE{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, + 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, + 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, + 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, + 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, + 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, + 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, + 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, + 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, + 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, + 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, + 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, + 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, + 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, + 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, + 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, + 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, + 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + + // Infinity m_figures calculate their CRC32 based on 12 bytes in the block of 16 + uint32 ret = 0; + for (uint32 i = 0; i < 12; ++i) + { + uint8 index = uint8(ret & 0xFF) ^ buffer[i]; + ret = ((ret >> 8) ^ CRC32_TABLE[index]); + } + + return ret; + } + + bool InfinityUSB::CreateFigure(fs::path pathName, uint32 figureNum, uint8 series) + { + FileStream* infFile(FileStream::createFile2(pathName)); + if (!infFile) + { + return false; + } + std::array fileData{}; + uint32 firstBlock = 0x17878E; + uint32 otherBlocks = 0x778788; + for (sint8 i = 2; i >= 0; i--) + { + fileData[0x38 - i] = uint8((firstBlock >> i * 8) & 0xFF); + } + for (uint32 index = 1; index < 0x05; index++) + { + for (sint8 i = 2; i >= 0; i--) + { + fileData[((index * 0x40) + 0x38) - i] = uint8((otherBlocks >> i * 8) & 0xFF); + } + } + // Create the vector to calculate the SHA1 hash with + std::vector sha1Calc = {SHA1_CONSTANT.begin(), SHA1_CONSTANT.end() - 1}; + + // Generate random UID, used for AES encrypt/decrypt + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0, 255); + std::array uid_data = {0, 0, 0, 0, 0, 0, 0, 0x89, 0x44, 0x00, 0xC2}; + uid_data[0] = dist(mt); + uid_data[1] = dist(mt); + uid_data[2] = dist(mt); + uid_data[3] = dist(mt); + uid_data[4] = dist(mt); + uid_data[5] = dist(mt); + uid_data[6] = dist(mt); + for (sint8 i = 0; i < 7; i++) + { + sha1Calc.push_back(uid_data[i]); + } + std::array figureData = GenerateBlankFigureData(figureNum, series); + if (figureData[1] == 0x00) + return false; + + std::array key = GenerateInfinityFigureKey(sha1Calc); + + std::array encryptedBlock = {}; + std::array blankBlock = {}; + std::array encryptedBlank = {}; + + AES128_ECB_encrypt(figureData.data(), key.data(), encryptedBlock.data()); + AES128_ECB_encrypt(blankBlock.data(), key.data(), encryptedBlank.data()); + + memcpy(&fileData[0], uid_data.data(), uid_data.size()); + memcpy(&fileData[16], encryptedBlock.data(), encryptedBlock.size()); + memcpy(&fileData[16 * 0x04], encryptedBlank.data(), encryptedBlank.size()); + memcpy(&fileData[16 * 0x08], encryptedBlank.data(), encryptedBlank.size()); + memcpy(&fileData[16 * 0x0C], encryptedBlank.data(), encryptedBlank.size()); + memcpy(&fileData[16 * 0x0D], encryptedBlank.data(), encryptedBlank.size()); + + infFile->writeData(fileData.data(), fileData.size()); + + delete infFile; + + return true; + } + + std::array InfinityUSB::GenerateInfinityFigureKey(const std::vector& sha1Data) + { + std::array digest = {}; + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, sha1Data.data(), sha1Data.size()); + SHA1_Final(digest.data(), &ctx); + OPENSSL_cleanse(&ctx, sizeof(ctx)); + // Infinity AES keys are the first 16 bytes of the SHA1 Digest, every set of 4 bytes need to be + // reversed due to endianness + std::array key = {}; + for (int i = 0; i < 4; i++) + { + for (int x = 3; x >= 0; x--) + { + key[(3 - x) + (i * 4)] = digest[x + (i * 4)]; + } + } + return key; + } + + std::array InfinityUSB::GenerateBlankFigureData(uint32 figureNum, uint8 series) + { + std::array figureData = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xD1, 0x1F}; + + // Figure Number, input by end user + figureData[1] = uint8((figureNum >> 16) & 0xFF); + figureData[2] = uint8((figureNum >> 8) & 0xFF); + figureData[3] = uint8(figureNum & 0xFF); + + // Manufacture date, formatted as YY/MM/DD. Set to release date of figure's series + if (series == 1) + { + figureData[4] = 0x0D; + figureData[5] = 0x08; + figureData[6] = 0x12; + } + else if (series == 2) + { + figureData[4] = 0x0E; + figureData[5] = 0x09; + figureData[6] = 0x12; + } + else if (series == 3) + { + figureData[4] = 0x0F; + figureData[5] = 0x08; + figureData[6] = 0x1C; + } + + uint32 checksum = InfinityCRC32(figureData); + for (sint8 i = 3; i >= 0; i--) + { + figureData[15 - i] = uint8((checksum >> i * 8) & 0xFF); + } + return figureData; + } + + uint8 InfinityUSB::DeriveFigurePosition(uint8 position) + { + // In the added/removed response, position needs to be 1 for the hexagon, 2 for Player 1 and + // Player 1's abilities, and 3 for Player 2 and Player 2's abilities. In the UI, positions 0, 1 + // and 2 represent the hexagon slot, 3, 4 and 5 represent Player 1's slot and 6, 7 and 8 represent + // Player 2's slot. + + switch (position) + { + case 0: + case 1: + case 2: + return 1; + case 3: + case 4: + case 5: + return 2; + case 6: + case 7: + case 8: + return 3; + + default: + return 0; + } + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.h b/src/Cafe/OS/libs/nsyshid/Infinity.h new file mode 100644 index 00000000..aa98fd15 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Infinity.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class InfinityBaseDevice final : public Device { + public: + InfinityBaseDevice(); + ~InfinityBaseDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + constexpr uint16 INF_BLOCK_COUNT = 0x14; + constexpr uint16 INF_BLOCK_SIZE = 0x10; + constexpr uint16 INF_FIGURE_SIZE = INF_BLOCK_COUNT * INF_BLOCK_SIZE; + constexpr uint8 MAX_FIGURES = 9; + class InfinityUSB { + public: + struct InfinityFigure final + { + std::unique_ptr infFile; + std::array data{}; + bool present = false; + uint8 orderAdded = 255; + void Save(); + }; + + void SendCommand(uint8* buf, sint32 originalLength); + std::array GetStatus(); + + void GetBlankResponse(uint8 sequence, std::array& replyBuf); + void DescrambleAndSeed(uint8* buf, uint8 sequence, + std::array& replyBuf); + void GetNextAndScramble(uint8 sequence, std::array& replyBuf); + void GetPresentFigures(uint8 sequence, std::array& replyBuf); + void QueryBlock(uint8 figNum, uint8 block, std::array& replyBuf, + uint8 sequence); + void WriteBlock(uint8 figNum, uint8 block, const uint8* toWriteBuf, + std::array& replyBuf, uint8 sequence); + void GetFigureIdentifier(uint8 figNum, uint8 sequence, + std::array& replyBuf); + + bool RemoveFigure(uint8 position); + uint32 LoadFigure(const std::array& buf, + std::unique_ptr, uint8 position); + bool CreateFigure(fs::path pathName, uint32 figureNum, uint8 series); + static std::map> GetFigureList(); + std::pair FindFigure(uint32 figNum); + + protected: + std::shared_mutex m_infinityMutex; + std::array m_figures; + + private: + uint8 GenerateChecksum(const std::array& data, + int numOfBytes) const; + uint32 Descramble(uint64 numToDescramble); + uint64 Scramble(uint32 numToScramble, uint32 garbage); + void GenerateSeed(uint32 seed); + uint32 GetNext(); + InfinityFigure& GetFigureByOrder(uint8 orderAdded); + uint8 DeriveFigurePosition(uint8 position); + std::array GenerateInfinityFigureKey(const std::vector& sha1Data); + std::array GenerateBlankFigureData(uint32 figureNum, uint8 series); + + uint32 m_randomA; + uint32 m_randomB; + uint32 m_randomC; + uint32 m_randomD; + + uint8 m_figureOrder = 0; + std::queue> m_figureAddedRemovedResponses; + std::queue> m_queries; + }; + extern InfinityUSB g_infinitybase; + +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp new file mode 100644 index 00000000..1b4515ef --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -0,0 +1,1002 @@ +#include "Skylander.h" + +#include + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + SkylanderUSB g_skyportal; + + const std::map, const char*> + s_listSkylanders = { + {{0, 0x0000}, "Whirlwind"}, + {{0, 0x1801}, "Series 2 Whirlwind"}, + {{0, 0x1C02}, "Polar Whirlwind"}, + {{0, 0x2805}, "Horn Blast Whirlwind"}, + {{0, 0x3810}, "Eon's Elite Whirlwind"}, + {{1, 0x0000}, "Sonic Boom"}, + {{1, 0x1801}, "Series 2 Sonic Boom"}, + {{2, 0x0000}, "Warnado"}, + {{2, 0x2206}, "LightCore Warnado"}, + {{3, 0x0000}, "Lightning Rod"}, + {{3, 0x1801}, "Series 2 Lightning Rod"}, + {{4, 0x0000}, "Bash"}, + {{4, 0x1801}, "Series 2 Bash"}, + {{5, 0x0000}, "Terrafin"}, + {{5, 0x1801}, "Series 2 Terrafin"}, + {{5, 0x2805}, "Knockout Terrafin"}, + {{5, 0x3810}, "Eon's Elite Terrafin"}, + {{6, 0x0000}, "Dino Rang"}, + {{6, 0x4810}, "Eon's Elite Dino Rang"}, + {{7, 0x0000}, "Prism Break"}, + {{7, 0x1801}, "Series 2 Prism Break"}, + {{7, 0x2805}, "Hyper Beam Prism Break"}, + {{7, 0x1206}, "LightCore Prism Break"}, + {{8, 0x0000}, "Sunburn"}, + {{9, 0x0000}, "Eruptor"}, + {{9, 0x1801}, "Series 2 Eruptor"}, + {{9, 0x2C02}, "Volcanic Eruptor"}, + {{9, 0x2805}, "Lava Barf Eruptor"}, + {{9, 0x1206}, "LightCore Eruptor"}, + {{9, 0x3810}, "Eon's Elite Eruptor"}, + {{10, 0x0000}, "Ignitor"}, + {{10, 0x1801}, "Series 2 Ignitor"}, + {{10, 0x1C03}, "Legendary Ignitor"}, + {{11, 0x0000}, "Flameslinger"}, + {{11, 0x1801}, "Series 2 Flameslinger"}, + {{12, 0x0000}, "Zap"}, + {{12, 0x1801}, "Series 2 Zap"}, + {{13, 0x0000}, "Wham Shell"}, + {{13, 0x2206}, "LightCore Wham Shell"}, + {{14, 0x0000}, "Gill Grunt"}, + {{14, 0x1801}, "Series 2 Gill Grunt"}, + {{14, 0x2805}, "Anchors Away Gill Grunt"}, + {{14, 0x3805}, "Tidal Wave Gill Grunt"}, + {{14, 0x3810}, "Eon's Elite Gill Grunt"}, + {{15, 0x0000}, "Slam Bam"}, + {{15, 0x1801}, "Series 2 Slam Bam"}, + {{15, 0x1C03}, "Legendary Slam Bam"}, + {{15, 0x4810}, "Eon's Elite Slam Bam"}, + {{16, 0x0000}, "Spyro"}, + {{16, 0x1801}, "Series 2 Spyro"}, + {{16, 0x2C02}, "Dark Mega Ram Spyro"}, + {{16, 0x2805}, "Mega Ram Spyro"}, + {{16, 0x3810}, "Eon's Elite Spyro"}, + {{17, 0x0000}, "Voodood"}, + {{17, 0x4810}, "Eon's Elite Voodood"}, + {{18, 0x0000}, "Double Trouble"}, + {{18, 0x1801}, "Series 2 Double Trouble"}, + {{18, 0x1C02}, "Royal Double Trouble"}, + {{19, 0x0000}, "Trigger Happy"}, + {{19, 0x1801}, "Series 2 Trigger Happy"}, + {{19, 0x2C02}, "Springtime Trigger Happy"}, + {{19, 0x2805}, "Big Bang Trigger Happy"}, + {{19, 0x3810}, "Eon's Elite Trigger Happy"}, + {{20, 0x0000}, "Drobot"}, + {{20, 0x1801}, "Series 2 Drobot"}, + {{20, 0x1206}, "LightCore Drobot"}, + {{21, 0x0000}, "Drill Seargeant"}, + {{21, 0x1801}, "Series 2 Drill Seargeant"}, + {{22, 0x0000}, "Boomer"}, + {{22, 0x4810}, "Eon's Elite Boomer"}, + {{23, 0x0000}, "Wrecking Ball"}, + {{23, 0x1801}, "Series 2 Wrecking Ball"}, + {{24, 0x0000}, "Camo"}, + {{24, 0x2805}, "Thorn Horn Camo"}, + {{25, 0x0000}, "Zook"}, + {{25, 0x1801}, "Series 2 Zook"}, + {{25, 0x4810}, "Eon's Elite Zook"}, + {{26, 0x0000}, "Stealth Elf"}, + {{26, 0x1801}, "Series 2 Stealth Elf"}, + {{26, 0x2C02}, "Dark Stealth Elf"}, + {{26, 0x1C03}, "Legendary Stealth Elf"}, + {{26, 0x2805}, "Ninja Stealth Elf"}, + {{26, 0x3810}, "Eon's Elite Stealth Elf"}, + {{27, 0x0000}, "Stump Smash"}, + {{27, 0x1801}, "Series 2 Stump Smash"}, + {{28, 0x0000}, "Dark Spyro"}, + {{29, 0x0000}, "Hex"}, + {{29, 0x1801}, "Series 2 Hex"}, + {{29, 0x1206}, "LightCore Hex"}, + {{30, 0x0000}, "Chop Chop"}, + {{30, 0x1801}, "Series 2 Chop Chop"}, + {{30, 0x2805}, "Twin Blade Chop Chop"}, + {{30, 0x3810}, "Eon's Elite Chop Chop"}, + {{31, 0x0000}, "Ghost Roaster"}, + {{31, 0x4810}, "Eon's Elite Ghost Roaster"}, + {{32, 0x0000}, "Cynder"}, + {{32, 0x1801}, "Series 2 Cynder"}, + {{32, 0x2805}, "Phantom Cynder"}, + {{100, 0x0000}, "Jet Vac"}, + {{100, 0x1403}, "Legendary Jet Vac"}, + {{100, 0x2805}, "Turbo Jet Vac"}, + {{100, 0x3805}, "Full Blast Jet Vac"}, + {{100, 0x1206}, "LightCore Jet Vac"}, + {{101, 0x0000}, "Swarm"}, + {{102, 0x0000}, "Crusher"}, + {{102, 0x1602}, "Granite Crusher"}, + {{103, 0x0000}, "Flashwing"}, + {{103, 0x1402}, "Jade Flash Wing"}, + {{103, 0x2206}, "LightCore Flashwing"}, + {{104, 0x0000}, "Hot Head"}, + {{105, 0x0000}, "Hot Dog"}, + {{105, 0x1402}, "Molten Hot Dog"}, + {{105, 0x2805}, "Fire Bone Hot Dog"}, + {{106, 0x0000}, "Chill"}, + {{106, 0x1603}, "Legendary Chill"}, + {{106, 0x2805}, "Blizzard Chill"}, + {{106, 0x1206}, "LightCore Chill"}, + {{107, 0x0000}, "Thumpback"}, + {{108, 0x0000}, "Pop Fizz"}, + {{108, 0x1402}, "Punch Pop Fizz"}, + {{108, 0x3C02}, "Love Potion Pop Fizz"}, + {{108, 0x2805}, "Super Gulp Pop Fizz"}, + {{108, 0x3805}, "Fizzy Frenzy Pop Fizz"}, + {{108, 0x1206}, "LightCore Pop Fizz"}, + {{109, 0x0000}, "Ninjini"}, + {{109, 0x1602}, "Scarlet Ninjini"}, + {{110, 0x0000}, "Bouncer"}, + {{110, 0x1603}, "Legendary Bouncer"}, + {{111, 0x0000}, "Sprocket"}, + {{111, 0x2805}, "Heavy Duty Sprocket"}, + {{112, 0x0000}, "Tree Rex"}, + {{112, 0x1602}, "Gnarly Tree Rex"}, + {{113, 0x0000}, "Shroomboom"}, + {{113, 0x3805}, "Sure Shot Shroomboom"}, + {{113, 0x1206}, "LightCore Shroomboom"}, + {{114, 0x0000}, "Eye Brawl"}, + {{115, 0x0000}, "Fright Rider"}, + {{200, 0x0000}, "Anvil Rain"}, + {{201, 0x0000}, "Hidden Treasure"}, + {{201, 0x2000}, "Platinum Hidden Treasure"}, + {{202, 0x0000}, "Healing Elixir"}, + {{203, 0x0000}, "Ghost Pirate Swords"}, + {{204, 0x0000}, "Time Twist Hourglass"}, + {{205, 0x0000}, "Sky Iron Shield"}, + {{206, 0x0000}, "Winged Boots"}, + {{207, 0x0000}, "Sparx the Dragonfly"}, + {{208, 0x0000}, "Dragonfire Cannon"}, + {{208, 0x1602}, "Golden Dragonfire Cannon"}, + {{209, 0x0000}, "Scorpion Striker"}, + {{210, 0x3002}, "Biter's Bane"}, + {{210, 0x3008}, "Sorcerous Skull"}, + {{210, 0x300B}, "Axe of Illusion"}, + {{210, 0x300E}, "Arcane Hourglass"}, + {{210, 0x3012}, "Spell Slapper"}, + {{210, 0x3014}, "Rune Rocket"}, + {{211, 0x3001}, "Tidal Tiki"}, + {{211, 0x3002}, "Wet Walter"}, + {{211, 0x3006}, "Flood Flask"}, + {{211, 0x3406}, "Legendary Flood Flask"}, + {{211, 0x3007}, "Soaking Staff"}, + {{211, 0x300B}, "Aqua Axe"}, + {{211, 0x3016}, "Frost Helm"}, + {{212, 0x3003}, "Breezy Bird"}, + {{212, 0x3006}, "Drafty Decanter"}, + {{212, 0x300D}, "Tempest Timer"}, + {{212, 0x3010}, "Cloudy Cobra"}, + {{212, 0x3011}, "Storm Warning"}, + {{212, 0x3018}, "Cyclone Saber"}, + {{213, 0x3004}, "Spirit Sphere"}, + {{213, 0x3404}, "Legendary Spirit Sphere"}, + {{213, 0x3008}, "Spectral Skull"}, + {{213, 0x3408}, "Legendary Spectral Skull"}, + {{213, 0x300B}, "Haunted Hatchet"}, + {{213, 0x300C}, "Grim Gripper"}, + {{213, 0x3010}, "Spooky Snake"}, + {{213, 0x3017}, "Dream Piercer"}, + {{214, 0x3000}, "Tech Totem"}, + {{214, 0x3007}, "Automatic Angel"}, + {{214, 0x3009}, "Factory Flower"}, + {{214, 0x300C}, "Grabbing Gadget"}, + {{214, 0x3016}, "Makers Mana"}, + {{214, 0x301A}, "Topsy Techy"}, + {{215, 0x3005}, "Eternal Flame"}, + {{215, 0x3009}, "Fire Flower"}, + {{215, 0x3011}, "Scorching Stopper"}, + {{215, 0x3012}, "Searing Spinner"}, + {{215, 0x3017}, "Spark Spear"}, + {{215, 0x301B}, "Blazing Belch"}, + {{216, 0x3000}, "Banded Boulder"}, + {{216, 0x3003}, "Rock Hawk"}, + {{216, 0x300A}, "Slag Hammer"}, + {{216, 0x300E}, "Dust Of Time"}, + {{216, 0x3013}, "Spinning Sandstorm"}, + {{216, 0x301A}, "Rubble Trouble"}, + {{217, 0x3003}, "Oak Eagle"}, + {{217, 0x3005}, "Emerald Energy"}, + {{217, 0x300A}, "Weed Whacker"}, + {{217, 0x3010}, "Seed Serpent"}, + {{217, 0x3018}, "Jade Blade"}, + {{217, 0x301B}, "Shrub Shrieker"}, + {{218, 0x3000}, "Dark Dagger"}, + {{218, 0x3014}, "Shadow Spider"}, + {{218, 0x301A}, "Ghastly Grimace"}, + {{219, 0x3000}, "Shining Ship"}, + {{219, 0x300F}, "Heavenly Hawk"}, + {{219, 0x301B}, "Beam Scream"}, + {{220, 0x301E}, "Kaos Trap"}, + {{220, 0x351F}, "Ultimate Kaos Trap"}, + {{230, 0x0000}, "Hand of Fate"}, + {{230, 0x3403}, "Legendary Hand of Fate"}, + {{231, 0x0000}, "Piggy Bank"}, + {{232, 0x0000}, "Rocket Ram"}, + {{233, 0x0000}, "Tiki Speaky"}, + {{300, 0x0000}, "Dragon’s Peak"}, + {{301, 0x0000}, "Empire of Ice"}, + {{302, 0x0000}, "Pirate Seas"}, + {{303, 0x0000}, "Darklight Crypt"}, + {{304, 0x0000}, "Volcanic Vault"}, + {{305, 0x0000}, "Mirror of Mystery"}, + {{306, 0x0000}, "Nightmare Express"}, + {{307, 0x0000}, "Sunscraper Spire"}, + {{308, 0x0000}, "Midnight Museum"}, + {{404, 0x0000}, "Legendary Bash"}, + {{416, 0x0000}, "Legendary Spyro"}, + {{419, 0x0000}, "Legendary Trigger Happy"}, + {{430, 0x0000}, "Legendary Chop Chop"}, + {{450, 0x0000}, "Gusto"}, + {{451, 0x0000}, "Thunderbolt"}, + {{452, 0x0000}, "Fling Kong"}, + {{453, 0x0000}, "Blades"}, + {{453, 0x3403}, "Legendary Blades"}, + {{454, 0x0000}, "Wallop"}, + {{455, 0x0000}, "Head Rush"}, + {{455, 0x3402}, "Nitro Head Rush"}, + {{456, 0x0000}, "Fist Bump"}, + {{457, 0x0000}, "Rocky Roll"}, + {{458, 0x0000}, "Wildfire"}, + {{458, 0x3402}, "Dark Wildfire"}, + {{459, 0x0000}, "Ka Boom"}, + {{460, 0x0000}, "Trail Blazer"}, + {{461, 0x0000}, "Torch"}, + {{462, 0x3000}, "Snap Shot"}, + {{462, 0x3402}, "Dark Snap Shot"}, + {{463, 0x0000}, "Lob Star"}, + {{463, 0x3402}, "Winterfest Lob-Star"}, + {{464, 0x0000}, "Flip Wreck"}, + {{465, 0x0000}, "Echo"}, + {{466, 0x0000}, "Blastermind"}, + {{467, 0x0000}, "Enigma"}, + {{468, 0x0000}, "Deja Vu"}, + {{468, 0x3403}, "Legendary Deja Vu"}, + {{469, 0x0000}, "Cobra Candabra"}, + {{469, 0x3402}, "King Cobra Cadabra"}, + {{470, 0x0000}, "Jawbreaker"}, + {{470, 0x3403}, "Legendary Jawbreaker"}, + {{471, 0x0000}, "Gearshift"}, + {{472, 0x0000}, "Chopper"}, + {{473, 0x0000}, "Tread Head"}, + {{474, 0x0000}, "Bushwack"}, + {{474, 0x3403}, "Legendary Bushwack"}, + {{475, 0x0000}, "Tuff Luck"}, + {{476, 0x0000}, "Food Fight"}, + {{476, 0x3402}, "Dark Food Fight"}, + {{477, 0x0000}, "High Five"}, + {{478, 0x0000}, "Krypt King"}, + {{478, 0x3402}, "Nitro Krypt King"}, + {{479, 0x0000}, "Short Cut"}, + {{480, 0x0000}, "Bat Spin"}, + {{481, 0x0000}, "Funny Bone"}, + {{482, 0x0000}, "Knight Light"}, + {{483, 0x0000}, "Spotlight"}, + {{484, 0x0000}, "Knight Mare"}, + {{485, 0x0000}, "Blackout"}, + {{502, 0x0000}, "Bop"}, + {{505, 0x0000}, "Terrabite"}, + {{506, 0x0000}, "Breeze"}, + {{508, 0x0000}, "Pet Vac"}, + {{508, 0x3402}, "Power Punch Pet Vac"}, + {{507, 0x0000}, "Weeruptor"}, + {{507, 0x3402}, "Eggcellent Weeruptor"}, + {{509, 0x0000}, "Small Fry"}, + {{510, 0x0000}, "Drobit"}, + {{519, 0x0000}, "Trigger Snappy"}, + {{526, 0x0000}, "Whisper Elf"}, + {{540, 0x0000}, "Barkley"}, + {{540, 0x3402}, "Gnarly Barkley"}, + {{541, 0x0000}, "Thumpling"}, + {{514, 0x0000}, "Gill Runt"}, + {{542, 0x0000}, "Mini-Jini"}, + {{503, 0x0000}, "Spry"}, + {{504, 0x0000}, "Hijinx"}, + {{543, 0x0000}, "Eye Small"}, + {{601, 0x0000}, "King Pen"}, + {{602, 0x0000}, "Tri-Tip"}, + {{603, 0x0000}, "Chopscotch"}, + {{604, 0x0000}, "Boom Bloom"}, + {{605, 0x0000}, "Pit Boss"}, + {{606, 0x0000}, "Barbella"}, + {{607, 0x0000}, "Air Strike"}, + {{608, 0x0000}, "Ember"}, + {{609, 0x0000}, "Ambush"}, + {{610, 0x0000}, "Dr. Krankcase"}, + {{611, 0x0000}, "Hood Sickle"}, + {{612, 0x0000}, "Tae Kwon Crow"}, + {{613, 0x0000}, "Golden Queen"}, + {{614, 0x0000}, "Wolfgang"}, + {{615, 0x0000}, "Pain-Yatta"}, + {{616, 0x0000}, "Mysticat"}, + {{617, 0x0000}, "Starcast"}, + {{618, 0x0000}, "Buckshot"}, + {{619, 0x0000}, "Aurora"}, + {{620, 0x0000}, "Flare Wolf"}, + {{621, 0x0000}, "Chompy Mage"}, + {{622, 0x0000}, "Bad Juju"}, + {{623, 0x0000}, "Grave Clobber"}, + {{624, 0x0000}, "Blaster-Tron"}, + {{625, 0x0000}, "Ro-Bow"}, + {{626, 0x0000}, "Chain Reaction"}, + {{627, 0x0000}, "Kaos"}, + {{628, 0x0000}, "Wild Storm"}, + {{629, 0x0000}, "Tidepool"}, + {{630, 0x0000}, "Crash Bandicoot"}, + {{631, 0x0000}, "Dr. Neo Cortex"}, + {{1000, 0x0000}, "Boom Jet (Bottom)"}, + {{1001, 0x0000}, "Free Ranger (Bottom)"}, + {{1001, 0x2403}, "Legendary Free Ranger (Bottom)"}, + {{1002, 0x0000}, "Rubble Rouser (Bottom)"}, + {{1003, 0x0000}, "Doom Stone (Bottom)"}, + {{1004, 0x0000}, "Blast Zone (Bottom)"}, + {{1004, 0x2402}, "Dark Blast Zone (Bottom)"}, + {{1005, 0x0000}, "Fire Kraken (Bottom)"}, + {{1005, 0x2402}, "Jade Fire Kraken (Bottom)"}, + {{1006, 0x0000}, "Stink Bomb (Bottom)"}, + {{1007, 0x0000}, "Grilla Drilla (Bottom)"}, + {{1008, 0x0000}, "Hoot Loop (Bottom)"}, + {{1008, 0x2402}, "Enchanted Hoot Loop (Bottom)"}, + {{1009, 0x0000}, "Trap Shadow (Bottom)"}, + {{1010, 0x0000}, "Magna Charge (Bottom)"}, + {{1010, 0x2402}, "Nitro Magna Charge (Bottom)"}, + {{1011, 0x0000}, "Spy Rise (Bottom)"}, + {{1012, 0x0000}, "Night Shift (Bottom)"}, + {{1012, 0x2403}, "Legendary Night Shift (Bottom)"}, + {{1013, 0x0000}, "Rattle Shake (Bottom)"}, + {{1013, 0x2402}, "Quick Draw Rattle Shake (Bottom)"}, + {{1014, 0x0000}, "Freeze Blade (Bottom)"}, + {{1014, 0x2402}, "Nitro Freeze Blade (Bottom)"}, + {{1015, 0x0000}, "Wash Buckler (Bottom)"}, + {{1015, 0x2402}, "Dark Wash Buckler (Bottom)"}, + {{2000, 0x0000}, "Boom Jet (Top)"}, + {{2001, 0x0000}, "Free Ranger (Top)"}, + {{2001, 0x2403}, "Legendary Free Ranger (Top)"}, + {{2002, 0x0000}, "Rubble Rouser (Top)"}, + {{2003, 0x0000}, "Doom Stone (Top)"}, + {{2004, 0x0000}, "Blast Zone (Top)"}, + {{2004, 0x2402}, "Dark Blast Zone (Top)"}, + {{2005, 0x0000}, "Fire Kraken (Top)"}, + {{2005, 0x2402}, "Jade Fire Kraken (Top)"}, + {{2006, 0x0000}, "Stink Bomb (Top)"}, + {{2007, 0x0000}, "Grilla Drilla (Top)"}, + {{2008, 0x0000}, "Hoot Loop (Top)"}, + {{2008, 0x2402}, "Enchanted Hoot Loop (Top)"}, + {{2009, 0x0000}, "Trap Shadow (Top)"}, + {{2010, 0x0000}, "Magna Charge (Top)"}, + {{2010, 0x2402}, "Nitro Magna Charge (Top)"}, + {{2011, 0x0000}, "Spy Rise (Top)"}, + {{2012, 0x0000}, "Night Shift (Top)"}, + {{2012, 0x2403}, "Legendary Night Shift (Top)"}, + {{2013, 0x0000}, "Rattle Shake (Top)"}, + {{2013, 0x2402}, "Quick Draw Rattle Shake (Top)"}, + {{2014, 0x0000}, "Freeze Blade (Top)"}, + {{2014, 0x2402}, "Nitro Freeze Blade (Top)"}, + {{2015, 0x0000}, "Wash Buckler (Top)"}, + {{2015, 0x2402}, "Dark Wash Buckler (Top)"}, + {{3000, 0x0000}, "Scratch"}, + {{3001, 0x0000}, "Pop Thorn"}, + {{3002, 0x0000}, "Slobber Tooth"}, + {{3002, 0x2402}, "Dark Slobber Tooth"}, + {{3003, 0x0000}, "Scorp"}, + {{3004, 0x0000}, "Fryno"}, + {{3004, 0x3805}, "Hog Wild Fryno"}, + {{3005, 0x0000}, "Smolderdash"}, + {{3005, 0x2206}, "LightCore Smolderdash"}, + {{3006, 0x0000}, "Bumble Blast"}, + {{3006, 0x2402}, "Jolly Bumble Blast"}, + {{3006, 0x2206}, "LightCore Bumble Blast"}, + {{3007, 0x0000}, "Zoo Lou"}, + {{3007, 0x2403}, "Legendary Zoo Lou"}, + {{3008, 0x0000}, "Dune Bug"}, + {{3009, 0x0000}, "Star Strike"}, + {{3009, 0x2602}, "Enchanted Star Strike"}, + {{3009, 0x2206}, "LightCore Star Strike"}, + {{3010, 0x0000}, "Countdown"}, + {{3010, 0x2402}, "Kickoff Countdown"}, + {{3010, 0x2206}, "LightCore Countdown"}, + {{3011, 0x0000}, "Wind Up"}, + {{3011, 0x2404}, "Gear Head VVind Up"}, + {{3012, 0x0000}, "Roller Brawl"}, + {{3013, 0x0000}, "Grim Creeper"}, + {{3013, 0x2603}, "Legendary Grim Creeper"}, + {{3013, 0x2206}, "LightCore Grim Creeper"}, + {{3014, 0x0000}, "Rip Tide"}, + {{3015, 0x0000}, "Punk Shock"}, + {{3200, 0x0000}, "Battle Hammer"}, + {{3201, 0x0000}, "Sky Diamond"}, + {{3202, 0x0000}, "Platinum Sheep"}, + {{3203, 0x0000}, "Groove Machine"}, + {{3204, 0x0000}, "UFO Hat"}, + {{3300, 0x0000}, "Sheep Wreck Island"}, + {{3301, 0x0000}, "Tower of Time"}, + {{3302, 0x0000}, "Fiery Forge"}, + {{3303, 0x0000}, "Arkeyan Crossbow"}, + {{3220, 0x0000}, "Jet Stream"}, + {{3221, 0x0000}, "Tomb Buggy"}, + {{3222, 0x0000}, "Reef Ripper"}, + {{3223, 0x0000}, "Burn Cycle"}, + {{3224, 0x0000}, "Hot Streak"}, + {{3224, 0x4402}, "Dark Hot Streak"}, + {{3224, 0x4004}, "E3 Hot Streak"}, + {{3224, 0x441E}, "Golden Hot Streak"}, + {{3225, 0x0000}, "Shark Tank"}, + {{3226, 0x0000}, "Thump Truck"}, + {{3227, 0x0000}, "Crypt Crusher"}, + {{3228, 0x0000}, "Stealth Stinger"}, + {{3228, 0x4402}, "Nitro Stealth Stinger"}, + {{3231, 0x0000}, "Dive Bomber"}, + {{3231, 0x4402}, "Spring Ahead Dive Bomber"}, + {{3232, 0x0000}, "Sky Slicer"}, + {{3233, 0x0000}, "Clown Cruiser (Nintendo Only)"}, + {{3233, 0x4402}, "Dark Clown Cruiser (Nintendo Only)"}, + {{3234, 0x0000}, "Gold Rusher"}, + {{3234, 0x4402}, "Power Blue Gold Rusher"}, + {{3235, 0x0000}, "Shield Striker"}, + {{3236, 0x0000}, "Sun Runner"}, + {{3236, 0x4403}, "Legendary Sun Runner"}, + {{3237, 0x0000}, "Sea Shadow"}, + {{3237, 0x4402}, "Dark Sea Shadow"}, + {{3238, 0x0000}, "Splatter Splasher"}, + {{3238, 0x4402}, "Power Blue Splatter Splasher"}, + {{3239, 0x0000}, "Soda Skimmer"}, + {{3239, 0x4402}, "Nitro Soda Skimmer"}, + {{3240, 0x0000}, "Barrel Blaster (Nintendo Only)"}, + {{3240, 0x4402}, "Dark Barrel Blaster (Nintendo Only)"}, + {{3241, 0x0000}, "Buzz Wing"}, + {{3400, 0x0000}, "Fiesta"}, + {{3400, 0x4515}, "Frightful Fiesta"}, + {{3401, 0x0000}, "High Volt"}, + {{3402, 0x0000}, "Splat"}, + {{3402, 0x4502}, "Power Blue Splat"}, + {{3406, 0x0000}, "Stormblade"}, + {{3411, 0x0000}, "Smash Hit"}, + {{3411, 0x4502}, "Steel Plated Smash Hit"}, + {{3412, 0x0000}, "Spitfire"}, + {{3412, 0x4502}, "Dark Spitfire"}, + {{3413, 0x0000}, "Hurricane Jet Vac"}, + {{3413, 0x4503}, "Legendary Hurricane Jet Vac"}, + {{3414, 0x0000}, "Double Dare Trigger Happy"}, + {{3414, 0x4502}, "Power Blue Double Dare Trigger Happy"}, + {{3415, 0x0000}, "Super Shot Stealth Elf"}, + {{3415, 0x4502}, "Dark Super Shot Stealth Elf"}, + {{3416, 0x0000}, "Shark Shooter Terrafin"}, + {{3417, 0x0000}, "Bone Bash Roller Brawl"}, + {{3417, 0x4503}, "Legendary Bone Bash Roller Brawl"}, + {{3420, 0x0000}, "Big Bubble Pop Fizz"}, + {{3420, 0x450E}, "Birthday Bash Big Bubble Pop Fizz"}, + {{3421, 0x0000}, "Lava Lance Eruptor"}, + {{3422, 0x0000}, "Deep Dive Gill Grunt"}, + {{3423, 0x0000}, "Turbo Charge Donkey Kong (Nintendo Only)"}, + {{3423, 0x4502}, "Dark Turbo Charge Donkey Kong (Nintendo Only)"}, + {{3424, 0x0000}, "Hammer Slam Bowser (Nintendo Only)"}, + {{3424, 0x4502}, "Dark Hammer Slam Bowser (Nintendo Only)"}, + {{3425, 0x0000}, "Dive-Clops"}, + {{3425, 0x450E}, "Missile-Tow Dive-Clops"}, + {{3426, 0x0000}, "Astroblast"}, + {{3426, 0x4503}, "Legendary Astroblast"}, + {{3427, 0x0000}, "Nightfall"}, + {{3428, 0x0000}, "Thrillipede"}, + {{3428, 0x450D}, "Eggcited Thrillipede"}, + {{3500, 0x0000}, "Sky Trophy"}, + {{3501, 0x0000}, "Land Trophy"}, + {{3502, 0x0000}, "Sea Trophy"}, + {{3503, 0x0000}, "Kaos Trophy"}, + }; + + uint16 SkylanderUSB::SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size) + { + const unsigned short CRC_CCITT_TABLE[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, + 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, + 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, + 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, + 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, + 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, + 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, + 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, + 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, + 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, + 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; + + uint16 crc = initValue; + + for (uint32 i = 0; i < size; i++) + { + const uint16 tmp = (crc >> 8) ^ buffer[i]; + crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp]; + } + + return crc; + } + SkylanderPortalDevice::SkylanderPortalDevice() + : Device(0x1430, 0x0150, 1, 2, 0) + { + m_IsOpened = false; + } + + bool SkylanderPortalDevice::Open() + { + if (!IsOpened()) + { + m_IsOpened = true; + } + return true; + } + + void SkylanderPortalDevice::Close() + { + if (IsOpened()) + { + m_IsOpened = false; + } + } + + bool SkylanderPortalDevice::IsOpened() + { + return m_IsOpened; + } + + Device::ReadResult SkylanderPortalDevice::Read(ReadMessage* message) + { + memcpy(message->data, g_skyportal.GetStatus().data(), message->length); + message->bytesRead = message->length; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return Device::ReadResult::Success; + } + + Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message) + { + message->bytesWritten = message->length; + return Device::WriteResult::Success; + } + + bool SkylanderPortalDevice::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + + bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol) + { + return true; + } + + bool SkylanderPortalDevice::SetReport(ReportMessage* message) + { + g_skyportal.ControlTransfer(message->originalData, message->originalLength); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return true; + } + + void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength) + { + std::array interruptResponse = {}; + switch (buf[0]) + { + case 'A': + { + interruptResponse = {buf[0], buf[1], 0xFF, 0x77}; + g_skyportal.Activate(); + break; + } + case 'C': + { + g_skyportal.SetLeds(0x01, buf[1], buf[2], buf[3]); + break; + } + case 'J': + { + g_skyportal.SetLeds(buf[1], buf[2], buf[3], buf[4]); + interruptResponse = {buf[0]}; + break; + } + case 'L': + { + uint8 side = buf[1]; + if (side == 0x02) + { + side = 0x04; + } + g_skyportal.SetLeds(side, buf[2], buf[3], buf[4]); + break; + } + case 'M': + { + interruptResponse = {buf[0], buf[1], 0x00, 0x19}; + break; + } + case 'Q': + { + const uint8 skyNum = buf[1] & 0xF; + const uint8 block = buf[2]; + g_skyportal.QueryBlock(skyNum, block, interruptResponse.data()); + break; + } + case 'R': + { + interruptResponse = {buf[0], 0x02, 0x1b}; + break; + } + case 'S': + case 'V': + { + // No response needed + break; + } + case 'W': + { + const uint8 skyNum = buf[1] & 0xF; + const uint8 block = buf[2]; + g_skyportal.WriteBlock(skyNum, block, &buf[3], interruptResponse.data()); + break; + } + default: + cemu_assert_error(); + break; + } + if (interruptResponse[0] != 0) + { + std::lock_guard lock(m_queryMutex); + m_queries.push(interruptResponse); + } + } + + void SkylanderUSB::Activate() + { + std::lock_guard lock(m_skyMutex); + if (m_activated) + { + // If the portal was already active no change is needed + return; + } + + // If not we need to advertise change to all the figures present on the portal + for (auto& s : m_skylanders) + { + if (s.status & 1) + { + s.queuedStatus.push(3); + s.queuedStatus.push(1); + } + } + + m_activated = true; + } + + void SkylanderUSB::Deactivate() + { + std::lock_guard lock(m_skyMutex); + + for (auto& s : m_skylanders) + { + // check if at the end of the updates there would be a figure on the portal + if (!s.queuedStatus.empty()) + { + s.status = s.queuedStatus.back(); + s.queuedStatus = std::queue(); + } + + s.status &= 1; + } + + m_activated = false; + } + + void SkylanderUSB::SetLeds(uint8 side, uint8 r, uint8 g, uint8 b) + { + std::lock_guard lock(m_skyMutex); + if (side == 0x00) + { + m_colorRight.red = r; + m_colorRight.green = g; + m_colorRight.blue = b; + } + else if (side == 0x01) + { + m_colorRight.red = r; + m_colorRight.green = g; + m_colorRight.blue = b; + + m_colorLeft.red = r; + m_colorLeft.green = g; + m_colorLeft.blue = b; + } + else if (side == 0x02) + { + m_colorLeft.red = r; + m_colorLeft.green = g; + m_colorLeft.blue = b; + } + else if (side == 0x03) + { + m_colorTrap.red = r; + m_colorTrap.green = g; + m_colorTrap.blue = b; + } + } + + uint8 SkylanderUSB::LoadSkylander(uint8* buf, std::unique_ptr file) + { + std::lock_guard lock(m_skyMutex); + + uint32 skySerial = 0; + for (int i = 3; i > -1; i--) + { + skySerial <<= 8; + skySerial |= buf[i]; + } + uint8 foundSlot = 0xFF; + + // mimics spot retaining on the portal + for (auto i = 0; i < 16; i++) + { + if ((m_skylanders[i].status & 1) == 0) + { + if (m_skylanders[i].lastId == skySerial) + { + foundSlot = i; + break; + } + + if (i < foundSlot) + { + foundSlot = i; + } + } + } + + if (foundSlot != 0xFF) + { + auto& skylander = m_skylanders[foundSlot]; + memcpy(skylander.data.data(), buf, skylander.data.size()); + skylander.skyFile = std::move(file); + skylander.status = Skylander::ADDED; + skylander.queuedStatus.push(Skylander::ADDED); + skylander.queuedStatus.push(Skylander::READY); + skylander.lastId = skySerial; + } + return foundSlot; + } + + bool SkylanderUSB::RemoveSkylander(uint8 skyNum) + { + std::lock_guard lock(m_skyMutex); + auto& thesky = m_skylanders[skyNum]; + + if (thesky.status & 1) + { + thesky.status = 2; + thesky.queuedStatus.push(2); + thesky.queuedStatus.push(0); + thesky.Save(); + thesky.skyFile.reset(); + return true; + } + + return false; + } + + bool SkylanderUSB::CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar) + { + FileStream* skyFile(FileStream::createFile2(pathName)); + if (!skyFile) + { + return false; + } + + std::array data{}; + + uint32 first_block = 0x690F0F0F; + uint32 other_blocks = 0x69080F7F; + memcpy(&data[0x36], &first_block, sizeof(first_block)); + for (size_t index = 1; index < 0x10; index++) + { + memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); + } + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0, 255); + data[0] = dist(mt); + data[1] = dist(mt); + data[2] = dist(mt); + data[3] = dist(mt); + data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; + data[5] = 0x81; + data[6] = 0x01; + data[7] = 0x0F; + + memcpy(&data[0x10], &skyId, sizeof(skyId)); + memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); + + uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); + + memcpy(&data[0x1E], &crc, sizeof(crc)); + + skyFile->writeData(data.data(), data.size()); + + delete skyFile; + + return true; + } + + void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf) + { + std::lock_guard lock(m_skyMutex); + + const auto& skylander = m_skylanders[skyNum]; + + replyBuf[0] = 'Q'; + replyBuf[2] = block; + if (skylander.status & 1) + { + replyBuf[1] = (0x10 | skyNum); + memcpy(replyBuf + 3, skylander.data.data() + (16 * block), 16); + } + else + { + replyBuf[1] = skyNum; + } + } + + void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block, + const uint8* toWriteBuf, uint8* replyBuf) + { + std::lock_guard lock(m_skyMutex); + + auto& skylander = m_skylanders[skyNum]; + + replyBuf[0] = 'W'; + replyBuf[2] = block; + + if (skylander.status & 1) + { + replyBuf[1] = (0x10 | skyNum); + memcpy(skylander.data.data() + (block * 16), toWriteBuf, 16); + skylander.Save(); + } + else + { + replyBuf[1] = skyNum; + } + } + + std::array SkylanderUSB::GetStatus() + { + std::lock_guard lock(m_queryMutex); + std::array interruptResponse = {}; + + if (!m_queries.empty()) + { + interruptResponse = m_queries.front(); + m_queries.pop(); + // This needs to happen after ~22 milliseconds + } + else + { + uint32 status = 0; + uint8 active = 0x00; + if (m_activated) + { + active = 0x01; + } + + for (int i = 16 - 1; i >= 0; i--) + { + auto& s = m_skylanders[i]; + + if (!s.queuedStatus.empty()) + { + s.status = s.queuedStatus.front(); + s.queuedStatus.pop(); + } + status <<= 2; + status |= s.status; + } + interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++, + active, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; + memcpy(&interruptResponse[1], &status, sizeof(status)); + } + return interruptResponse; + } + + std::string SkylanderUSB::FindSkylander(uint16 skyId, uint16 skyVar) + { + for (const auto& it : GetListSkylanders()) + { + if (it.first.first == skyId && it.first.second == skyVar) + { + return it.second; + } + } + return fmt::format("Unknown ({} {})", skyId, skyVar); + } + + std::map, const char*> SkylanderUSB::GetListSkylanders() + { + return s_listSkylanders; + } + + void SkylanderUSB::Skylander::Save() + { + if (!skyFile) + return; + + skyFile->SetPosition(0); + skyFile->writeData(data.data(), data.size()); + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h new file mode 100644 index 00000000..986ef185 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class SkylanderPortalDevice final : public Device { + public: + SkylanderPortalDevice(); + ~SkylanderPortalDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + constexpr uint16 SKY_BLOCK_COUNT = 0x40; + constexpr uint16 SKY_BLOCK_SIZE = 0x10; + constexpr uint16 SKY_FIGURE_SIZE = SKY_BLOCK_COUNT * SKY_BLOCK_SIZE; + constexpr uint8 MAX_SKYLANDERS = 16; + + class SkylanderUSB { + public: + struct Skylander final + { + std::unique_ptr skyFile; + uint8 status = 0; + std::queue queuedStatus; + std::array data{}; + uint32 lastId = 0; + void Save(); + + enum : uint8 + { + REMOVED = 0, + READY = 1, + REMOVING = 2, + ADDED = 3 + }; + }; + + struct SkylanderLEDColor final + { + uint8 red = 0; + uint8 green = 0; + uint8 blue = 0; + }; + + void ControlTransfer(uint8* buf, sint32 originalLength); + + void Activate(); + void Deactivate(); + void SetLeds(uint8 side, uint8 r, uint8 g, uint8 b); + + std::array GetStatus(); + void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf); + void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, + uint8* replyBuf); + + uint8 LoadSkylander(uint8* buf, std::unique_ptr file); + bool RemoveSkylander(uint8 skyNum); + bool CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar); + uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size); + static std::map, const char*> GetListSkylanders(); + std::string FindSkylander(uint16 skyId, uint16 skyVar); + + protected: + std::mutex m_skyMutex; + std::mutex m_queryMutex; + std::array m_skylanders; + + private: + std::queue> m_queries; + bool m_activated = true; + uint8 m_interruptCounter = 0; + SkylanderLEDColor m_colorRight = {}; + SkylanderLEDColor m_colorLeft = {}; + SkylanderLEDColor m_colorTrap = {}; + }; + extern SkylanderUSB g_skyportal; +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index ba3e3b96..99a736d9 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -256,6 +256,19 @@ namespace nsyshid device->m_productId); } + bool FindDeviceById(uint16 vendorId, uint16 productId) + { + std::lock_guard lock(hidMutex); + for (const auto& device : deviceList) + { + if (device->m_vendorId == vendorId && device->m_productId == productId) + { + return true; + } + } + return false; + } + void export_HIDAddClient(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); @@ -366,8 +379,8 @@ namespace nsyshid void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamU8(ifIndex, 1); // r4 + ppcDefineParamU8(protocol, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)"); @@ -406,7 +419,8 @@ namespace nsyshid sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); - if (device->SetReport(reportData, length, originalData, originalLength)) + ReportMessage message(reportData, length, originalData, originalLength); + if (device->SetReport(&message)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, @@ -429,18 +443,18 @@ namespace nsyshid // handler for synchronous HIDSetReport transfers sint32 _hidSetReportSync(std::shared_ptr device, uint8* reportData, sint32 length, - uint8* originalData, - sint32 originalLength, OSThread_t* osThread) + uint8* originalData, sint32 originalLength, coreinit::OSEvent* event) { _debugPrintHex("_hidSetReportSync Begin", reportData, length); sint32 returnCode = 0; - if (device->SetReport(reportData, length, originalData, originalLength)) + ReportMessage message(reportData, length, originalData, originalLength); + if (device->SetReport(&message)) { returnCode = originalLength; } free(reportData); cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -484,11 +498,12 @@ namespace nsyshid sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { + // synchronous + StackAllocator event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); 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(); + paddedLength + 1, data, dataLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } else @@ -511,17 +526,16 @@ namespace nsyshid return -1; } memset(data, 0, maxLength); - - sint32 bytesRead = 0; - Device::ReadResult readResult = device->Read(data, maxLength, bytesRead); + ReadMessage message(data, maxLength, 0); + Device::ReadResult readResult = device->Read(&message); switch (readResult) { case Device::ReadResult::Success: { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", - bytesRead, + message.bytesRead, maxLength); - return bytesRead; + return message.bytesRead; } break; case Device::ReadResult::Error: @@ -557,10 +571,10 @@ namespace nsyshid sint32 _hidReadSync(std::shared_ptr device, uint8* data, sint32 maxLength, - OSThread_t* osThread) + coreinit::OSEvent* event) { sint32 returnCode = _hidReadInternalSync(device, data, maxLength); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -591,10 +605,10 @@ namespace nsyshid else { // synchronous transfer - std::future res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + StackAllocator event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + std::future res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } @@ -609,15 +623,15 @@ namespace nsyshid cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); return -1; } - sint32 bytesWritten = 0; - Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten); + WriteMessage message(data, maxLength, 0); + Device::WriteResult writeResult = device->Write(&message); switch (writeResult) { case Device::WriteResult::Success: { - cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", bytesWritten, + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", message.bytesWritten, maxLength); - return bytesWritten; + return message.bytesWritten; } break; case Device::WriteResult::Error: @@ -654,10 +668,10 @@ namespace nsyshid sint32 _hidWriteSync(std::shared_ptr device, uint8* data, sint32 maxLength, - OSThread_t* osThread) + coreinit::OSEvent* event) { sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -688,10 +702,10 @@ namespace nsyshid else { // synchronous transfer - std::future res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + StackAllocator event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + std::future res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } @@ -758,6 +772,11 @@ namespace nsyshid return nullptr; } + bool Backend::FindDeviceById(uint16 vendorId, uint16 productId) + { + return nsyshid::FindDeviceById(vendorId, productId); + } + bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) { return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); diff --git a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp index 72cb9bd0..f1571cc0 100644 --- a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp +++ b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp @@ -3,6 +3,11 @@ namespace nsyskbd { + bool IsValidChannel(uint32 channel) + { + return channel >= 0 && channel < 4; + } + uint32 KBDGetChannelStatus(uint32 channel, uint32be* status) { static bool loggedError = false; @@ -16,8 +21,38 @@ namespace nsyskbd return 0; } +#pragma pack(push, 1) + struct KeyState + { + uint8be channel; + uint8be ukn1; + uint8be _padding[2]; + uint32be ukn4; + uint32be ukn8; + uint16be uknC; + }; +#pragma pack(pop) + static_assert(sizeof(KeyState) == 0xE); // actual size might be padded to 0x10? + + uint32 KBDGetKey(uint32 channel, KeyState* keyState) + { + // used by MSX VC + if(!IsValidChannel(channel) || !keyState) + { + cemuLog_log(LogType::APIErrors, "KBDGetKey(): Invalid parameter"); + return 0; + } + keyState->channel = channel; + keyState->ukn1 = 0; + keyState->ukn4 = 0; + keyState->ukn8 = 0; + keyState->uknC = 0; + return 0; + } + void nsyskbd_load() { cafeExportRegister("nsyskbd", KBDGetChannelStatus, LogType::Placeholder); + cafeExportRegister("nsyskbd", KBDGetKey, LogType::Placeholder); } } diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index dd7c9189..5a0ddc59 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -1210,6 +1210,14 @@ void nsysnetExport_select(PPCInterpreter_t* hCPU) timeval tv = { 0 }; + if (timeOut == NULL) + { + // return immediately + cemuLog_log(LogType::Socket, "select returned immediately because of null timeout"); + osLib_returnFromFunction(hCPU, 0); + return; + } + uint64 msTimeout = (_swapEndianU32(timeOut->tv_usec) / 1000) + (_swapEndianU32(timeOut->tv_sec) * 1000); uint32 startTime = GetTickCount(); while (true) diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp new file mode 100644 index 00000000..24617791 --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -0,0 +1,647 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/ntag/ntag.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" + +namespace ntag +{ + struct NTAGWriteData + { + uint16 size; + uint8 data[0x1C8]; + nfc::NFCUid uid; + nfc::NFCUid uidMask; + }; + NTAGWriteData gWriteData[2]; + + bool ccrNfcOpened = false; + IOSDevHandle gCcrNfcHandle; + + NTAGFormatSettings gFormatSettings; + + MPTR gDetectCallbacks[2]; + MPTR gAbortCallbacks[2]; + MPTR gReadCallbacks[2]; + MPTR gWriteCallbacks[2]; + + sint32 __NTAGConvertNFCResult(sint32 result) + { + if (result == NFC_RESULT_SUCCESS) + { + return NTAG_RESULT_SUCCESS; + } + + switch (result & NFC_RESULT_MASK) + { + case NFC_RESULT_UNINITIALIZED: + return NTAG_RESULT_UNINITIALIZED; + case NFC_RESULT_INVALID_STATE: + return NTAG_RESULT_INVALID_STATE; + case NFC_RESULT_NO_TAG: + return NTAG_RESULT_NO_TAG; + case NFC_RESULT_UID_MISMATCH: + return NTAG_RESULT_UID_MISMATCH; + } + + // TODO convert more errors + return NTAG_RESULT_INVALID; + } + + sint32 NTAGInit(uint32 chan) + { + return NTAGInitEx(chan); + } + + sint32 NTAGInitEx(uint32 chan) + { + sint32 result = nfc::NFCInitEx(chan, 1); + return __NTAGConvertNFCResult(result); + } + + sint32 NTAGShutdown(uint32 chan) + { + sint32 result = nfc::NFCShutdown(chan); + + if (ccrNfcOpened) + { + coreinit::IOS_Close(gCcrNfcHandle); + ccrNfcOpened = false; + } + + gDetectCallbacks[chan] = MPTR_NULL; + gAbortCallbacks[chan] = MPTR_NULL; + gReadCallbacks[chan] = MPTR_NULL; + gWriteCallbacks[chan] = MPTR_NULL; + + return __NTAGConvertNFCResult(result); + } + + bool NTAGIsInit(uint32 chan) + { + return nfc::NFCIsInit(chan); + } + + void NTAGProc(uint32 chan) + { + nfc::NFCProc(chan); + } + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings) + { + gFormatSettings.version = formatSettings->version; + gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); + gFormatSettings.identifyCode = _swapEndianU32(formatSettings->identifyCode); + } + + void __NTAGDetectCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamU32(hasTag, 1); + ppcDefineParamPtr(context, void, 2); + + cemuLog_log(LogType::NTAG, "__NTAGDetectCallback: {} {} {}", chan, hasTag, context); + + PPCCoreCallback(gDetectCallbacks[chan], chan, hasTag, context); + + osLib_returnFromFunction(hCPU, 0); + } + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gDetectCallbacks[chan] = callback; + nfc::NFCSetTagDetectCallback(chan, RPLLoader_MakePPCCallable(__NTAGDetectCallback), context); + } + + void __NTAGAbortCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO is it normal that Rumble U calls this? + + gAbortCallbacks[chan] = callback; + sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); + return __NTAGConvertNFCResult(result); + } + + bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) + { + memcpy(nfc, raw, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (raw->version == 0) + { + nfc->version = 0; + nfc->dataSize = 0x1C8; + nfc->seedOffset = 0x25; + nfc->keyGenSaltOffset = 0x1A8; + nfc->uuidOffset = 0x198; + nfc->unfixedInfosOffset = 0x28; + nfc->unfixedInfosSize = 0x120; + nfc->lockedSecretOffset = 0x168; + nfc->lockedSecretSize = 0x30; + nfc->unfixedInfosHmacOffset = 0; + nfc->lockedSecretHmacOffset = 0x148; + } + else if (raw->version == 2) + { + nfc->version = 2; + nfc->dataSize = 0x208; + nfc->seedOffset = 0x29; + nfc->keyGenSaltOffset = 0x1E8; + nfc->uuidOffset = 0x1D4; + nfc->unfixedInfosOffset = 0x2C; + nfc->unfixedInfosSize = 0x188; + nfc->lockedSecretOffset = 0x1DC; + nfc->lockedSecretSize = 0; + nfc->unfixedInfosHmacOffset = 0x8; + nfc->lockedSecretHmacOffset = 0x1B4; + + memcpy(nfc->data + 0x1d4, raw->data, 0x8); + memcpy(nfc->data, raw->data + 0x8, 0x8); + memcpy(nfc->data + 0x28, raw->data + 0x10, 0x4); + memcpy(nfc->data + nfc->unfixedInfosOffset, raw->data + 0x14, 0x20); + memcpy(nfc->data + nfc->lockedSecretHmacOffset, raw->data + 0x34, 0x20); + memcpy(nfc->data + nfc->lockedSecretOffset, raw->data + 0x54, 0xC); + memcpy(nfc->data + nfc->keyGenSaltOffset, raw->data + 0x60, 0x20); + memcpy(nfc->data + nfc->unfixedInfosHmacOffset, raw->data + 0x80, 0x20); + memcpy(nfc->data + nfc->unfixedInfosOffset + 0x20, raw->data + 0xa0, 0x168); + memcpy(nfc->data + 0x208, raw->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + bool __NTAGNfcDataToRawData(iosu::ccr_nfc::CCRNFCCryptData* nfc, iosu::ccr_nfc::CCRNFCCryptData* raw) + { + memcpy(raw, nfc, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (nfc->version == 0) + { + raw->version = 0; + raw->dataSize = 0x1C8; + raw->seedOffset = 0x25; + raw->keyGenSaltOffset = 0x1A8; + raw->uuidOffset = 0x198; + raw->unfixedInfosOffset = 0x28; + raw->unfixedInfosSize = 0x120; + raw->lockedSecretOffset = 0x168; + raw->lockedSecretSize = 0x30; + raw->unfixedInfosHmacOffset = 0; + raw->lockedSecretHmacOffset = 0x148; + } + else if (nfc->version == 2) + { + raw->version = 2; + raw->dataSize = 0x208; + raw->seedOffset = 0x11; + raw->keyGenSaltOffset = 0x60; + raw->uuidOffset = 0; + raw->unfixedInfosOffset = 0x14; + raw->unfixedInfosSize = 0x188; + raw->lockedSecretOffset = 0x54; + raw->lockedSecretSize = 0xC; + raw->unfixedInfosHmacOffset = 0x80; + raw->lockedSecretHmacOffset = 0x34; + + memcpy(raw->data + 0x8, nfc->data, 0x8); + memcpy(raw->data + raw->unfixedInfosHmacOffset, nfc->data + 0x8, 0x20); + memcpy(raw->data + 0x10, nfc->data + 0x28, 0x4); + memcpy(raw->data + raw->unfixedInfosOffset, nfc->data + 0x2C, 0x20); + memcpy(raw->data + 0xa0, nfc->data + 0x4C, 0x168); + memcpy(raw->data + raw->lockedSecretHmacOffset, nfc->data + 0x1B4, 0x20); + memcpy(raw->data + raw->uuidOffset, nfc->data + 0x1D4, 0x8); + memcpy(raw->data + raw->lockedSecretOffset, nfc->data + 0x1DC, 0xC); + memcpy(raw->data + raw->keyGenSaltOffset, nfc->data + 0x1E8, 0x20); + memcpy(raw->data + 0x208, nfc->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + sint32 __NTAGDecryptData(void* decryptedData, const void* rawData) + { + StackAllocator nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Decrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 2, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(decryptedData, nfcRawData->data, 0x1C8); + + // Convert result + if (result == CCR_NFC_INVALID_UNFIXED_INFOS) + { + return -0x2708; + } + else if (result == CCR_NFC_INVALID_LOCKED_SECRET) + { + return -0x2707; + } + + return result; + } + + sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + if (infoHeader->formatVersion != gFormatSettings.version || noftHeader->version != 0x1) + { + cemuLog_log(LogType::Force, "Invalid format version"); + return -0x2710; + } + + if (_swapEndianU32(noftHeader->magic) != 0x4E4F4654 /* 'NOFT' */ || + _swapEndianU16(rwHeader->magic) != 0x5257 /* 'RW' */ || + _swapEndianU16(roHeader->magic) != 0x524F /* 'RO' */) + { + cemuLog_log(LogType::Force, "Invalid header magic"); + return -0x270F; + } + + if (_swapEndianU32(rwHeader->makerCode) != gFormatSettings.makerCode || + _swapEndianU32(roHeader->makerCode) != gFormatSettings.makerCode) + { + cemuLog_log(LogType::Force, "Invalid maker code"); + return -0x270E; + } + + if (infoHeader->formatVersion != 0 && + (_swapEndianU32(rwHeader->identifyCode) != gFormatSettings.identifyCode || + _swapEndianU32(roHeader->identifyCode) != gFormatSettings.identifyCode)) + { + cemuLog_log(LogType::Force, "Invalid identify code"); + return -0x2709; + } + + if (_swapEndianU16(rwHeader->size) + _swapEndianU16(roHeader->size) != 0x130) + { + cemuLog_log(LogType::Force, "Invalid data size"); + return -0x270D; + } + + return 0; + } + + sint32 __NTAGParseHeaders(const uint8* data, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); + memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); + + cemu_assert(_swapEndianU16(infoHeader->rwHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + cemu_assert(_swapEndianU16(infoHeader->roHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + + memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); + memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); + + return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); + } + + sint32 __NTAGParseData(void* rawData, void* rwData, void* roData, nfc::NFCUid* uid, uint32 lockedDataSize, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + uint8 decryptedData[0x1C8]; + sint32 result = __NTAGDecryptData(decryptedData, rawData); + if (result < 0) + { + return result; + } + + result = __NTAGParseHeaders(decryptedData, noftHeader, infoHeader, rwHeader, roHeader); + if (result < 0) + { + return result; + } + + if (_swapEndianU16(roHeader->size) + 0x70 != lockedDataSize) + { + cemuLog_log(LogType::Force, "Invalid locked area size"); + return -0x270C; + } + + if (memcmp(infoHeader->uid.uid, uid->uid, sizeof(nfc::NFCUid)) != 0) + { + cemuLog_log(LogType::Force, "UID mismatch"); + return -0x270B; + } + + cemu_assert(_swapEndianU16(rwHeader->offset) + _swapEndianU16(rwHeader->size) < 0x200); + cemu_assert(_swapEndianU16(roHeader->offset) + _swapEndianU16(roHeader->size) < 0x200); + + memcpy(rwData, decryptedData + _swapEndianU16(rwHeader->offset), _swapEndianU16(rwHeader->size)); + memcpy(roData, decryptedData + _swapEndianU16(roHeader->offset), _swapEndianU16(roHeader->size)); + + return 0; + } + + void __NTAGReadCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]{}; + StackAllocator readResult; + StackAllocator rwData; + StackAllocator roData; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + + readResult->readOnly = readOnly; + + error = __NTAGConvertNFCResult(error); + if (error == 0) + { + memset(rwData.GetPointer(), 0, 0x1C8); + memset(roData.GetPointer(), 0, 0x1C8); + + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData.GetPointer(), roData.GetPointer(), uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error == 0) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + readResult->rwInfo.data = _swapEndianU32(rwData.GetMPTR()); + readResult->roInfo.data = _swapEndianU32(roData.GetMPTR()); + readResult->rwInfo.makerCode = rwHeader.makerCode; + readResult->rwInfo.size = rwHeader.size; + readResult->roInfo.makerCode = roHeader.makerCode; + readResult->rwInfo.identifyCode = rwHeader.identifyCode; + readResult->roInfo.identifyCode = roHeader.identifyCode; + readResult->formatVersion = infoHeader.formatVersion; + readResult->roInfo.size = roHeader.size; + + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + + PPCCoreCallback(gReadCallbacks[chan], chan, 0, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + return; + } + } + + if (uid) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + } + readResult->roInfo.size = 0; + readResult->rwInfo.size = 0; + readResult->roInfo.data = MPTR_NULL; + readResult->formatVersion = 0; + readResult->rwInfo.data = MPTR_NULL; + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + PPCCoreCallback(gReadCallbacks[chan], chan, error, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gReadCallbacks[chan] = callback; + + nfc::NFCUid _uid{}, _uidMask{}; + if (uid && uidMask) + { + memcpy(&_uid, uid, sizeof(*uid)); + memcpy(&_uidMask, uidMask, sizeof(*uidMask)); + } + + sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); + return __NTAGConvertNFCResult(result); + } + + sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) + { + StackAllocator nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Encrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 1, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(encryptedData, nfcRawData->data, 0x1C8); + + return result; + } + + sint32 __NTAGPrepareWriteData(void* outBuffer, uint32 dataSize, const void* data, const void* tagData, NTAGNoftHeader* noftHeader, NTAGAreaHeader* rwHeader) + { + uint8 decryptedBuffer[0x1C8]; + uint8 encryptedBuffer[0x1C8]; + + memcpy(decryptedBuffer, tagData, 0x1C8); + + // Fill the rest of the rw area with random data + if (dataSize < _swapEndianU16(rwHeader->size)) + { + uint8 randomBuffer[0x1C8]; + for (int i = 0; i < sizeof(randomBuffer); i++) + { + randomBuffer[i] = rand() & 0xFF; + } + + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset) + dataSize, randomBuffer, _swapEndianU16(rwHeader->size) - dataSize); + } + + // Make sure the data fits into the rw area + if (_swapEndianU16(rwHeader->size) < dataSize) + { + return -0x270D; + } + + // Update write count (check for overflow) + if ((_swapEndianU16(noftHeader->writeCount) & 0x7fff) == 0x7fff) + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) & 0x8000); + } + else + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); + } + + memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(noftHeader)); + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); + + // Encrypt + sint32 result = __NTAGEncryptData(encryptedBuffer, decryptedBuffer); + if (result < 0) + { + return result; + } + + memcpy(outBuffer, encryptedBuffer, _swapEndianU16(rwHeader->size) + 0x28); + return 0; + } + + void __NTAGWriteCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + + void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]{}; + uint8 rwData[0x1C8]{}; + uint8 roData[0x1C8]{}; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + uint8 writeBuffer[0x1C8]{}; + + error = __NTAGConvertNFCResult(error); + if (error == 0) + { + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData, roData, uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to parse data before write"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Prepare data + memcpy(rawData + _swapEndianU16(infoHeader.rwHeaderOffset), &rwHeader, sizeof(rwHeader)); + memcpy(rawData + _swapEndianU16(infoHeader.roHeaderOffset), &roHeader, sizeof(roHeader)); + memcpy(rawData + _swapEndianU16(roHeader.offset), roData, _swapEndianU16(roHeader.size)); + error = __NTAGPrepareWriteData(writeBuffer, gWriteData[chan].size, gWriteData[chan].data, rawData, &noftHeader, &rwHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to prepare write data"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Write data to tag + error = nfc::NFCWrite(chan, 200, &gWriteData[chan].uid, &gWriteData[chan].uidMask, + _swapEndianU16(rwHeader.size) + 0x28, writeBuffer, RPLLoader_MakePPCCallable(__NTAGWriteCallback), context); + if (error >= 0) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + error = __NTAGConvertNFCResult(error); + } + + PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + cemu_assert(rwSize < 0x1C8); + + gWriteCallbacks[chan] = callback; + + if (uid) + { + memcpy(&gWriteData[chan].uid, uid, sizeof(nfc::NFCUid)); + } + memset(&gWriteData[chan].uidMask, 0xff, sizeof(nfc::NFCUid)); + + gWriteData[chan].size = rwSize; + memcpy(gWriteData[chan].data, rwData, rwSize); + + sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); + return __NTAGConvertNFCResult(result); + } + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO + cemu_assert_debug(false); + + return NTAG_RESULT_INVALID; + } + + void Initialize() + { + cafeExportRegister("ntag", NTAGInit, LogType::NTAG); + cafeExportRegister("ntag", NTAGInitEx, LogType::NTAG); + cafeExportRegister("ntag", NTAGShutdown, LogType::NTAG); + cafeExportRegister("ntag", NTAGIsInit, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGProc, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGSetFormatSettings, LogType::NTAG); + cafeExportRegister("ntag", NTAGSetTagDetectCallback, LogType::NTAG); + cafeExportRegister("ntag", NTAGAbort, LogType::NTAG); + cafeExportRegister("ntag", NTAGRead, LogType::NTAG); + cafeExportRegister("ntag", NTAGWrite, LogType::NTAG); + cafeExportRegister("ntag", NTAGFormat, LogType::NTAG); + } +} diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h new file mode 100644 index 00000000..68f1801b --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -0,0 +1,101 @@ +#pragma once +#include "Cafe/OS/libs/nfc/nfc.h" + +#define NTAG_RESULT_SUCCESS (0) +#define NTAG_RESULT_UNINITIALIZED (-0x3E7) +#define NTAG_RESULT_INVALID_STATE (-0x3E6) +#define NTAG_RESULT_NO_TAG (-0x3E5) +#define NTAG_RESULT_INVALID (-0x3E1) +#define NTAG_RESULT_UID_MISMATCH (-0x3DB) + +namespace ntag +{ + struct NTAGFormatSettings + { + /* +0x00 */ uint8 version; + /* +0x04 */ uint32 makerCode; + /* +0x08 */ uint32 identifyCode; + /* +0x0C */ uint8 reserved[0x1C]; + }; + static_assert(sizeof(NTAGFormatSettings) == 0x28); + +#pragma pack(1) + struct NTAGNoftHeader + { + /* +0x00 */ uint32 magic; + /* +0x04 */ uint8 version; + /* +0x05 */ uint16 writeCount; + /* +0x07 */ uint8 unknown; + }; + static_assert(sizeof(NTAGNoftHeader) == 0x8); +#pragma pack() + + struct NTAGInfoHeader + { + /* +0x00 */ uint16 rwHeaderOffset; + /* +0x02 */ uint16 rwSize; + /* +0x04 */ uint16 roHeaderOffset; + /* +0x06 */ uint16 roSize; + /* +0x08 */ nfc::NFCUid uid; + /* +0x0F */ uint8 formatVersion; + }; + static_assert(sizeof(NTAGInfoHeader) == 0x10); + + struct NTAGAreaHeader + { + /* +0x00 */ uint16 magic; + /* +0x02 */ uint16 offset; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + }; + static_assert(sizeof(NTAGAreaHeader) == 0x10); + + struct NTAGAreaInfo + { + /* +0x00 */ MPTR data; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + /* +0x10 */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGAreaInfo) == 0x30); + + struct NTAGData + { + /* +0x00 */ nfc::NFCUid uid; + /* +0x07 */ uint8 readOnly; + /* +0x08 */ uint8 formatVersion; + /* +0x09 */ uint8 padding[3]; + /* +0x0C */ NTAGAreaInfo rwInfo; + /* +0x3C */ NTAGAreaInfo roInfo; + /* +0x6C */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGData) == 0x8C); + + sint32 NTAGInit(uint32 chan); + + sint32 NTAGInitEx(uint32 chan); + + sint32 NTAGShutdown(uint32 chan); + + bool NTAGIsInit(uint32 chan); + + void NTAGProc(uint32 chan); + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings); + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context); + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context); + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + void Initialize(); +} diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp index 47f3bc4f..0a577b97 100644 --- a/src/Cafe/OS/libs/padscore/padscore.cpp +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -12,6 +12,7 @@ enum class KPAD_ERROR : sint32 { NONE = 0, + NO_SAMPLE_DATA = -1, NO_CONTROLLER = -2, NOT_INITIALIZED = -5, }; @@ -106,6 +107,9 @@ void padscoreExport_WPADProbe(PPCInterpreter_t* hCPU) } else { + if(type) + *type = 253; + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } } @@ -420,9 +424,12 @@ void padscoreExport_KPADSetConnectCallback(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); } +uint64 g_kpadLastRead[InputManager::kMaxWPADControllers] = {0}; bool g_kpadIsInited = true; + sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype* errResult) { + if (channel >= InputManager::kMaxWPADControllers) { debugBreakpoint(); @@ -446,6 +453,19 @@ sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, bety return 0; } + // On console new input samples are only received every few ms and calling KPADRead(Ex) clears the internal queue regardless of length value + // thus calling KPADRead(Ex) again too soon on the same channel will result in no data being returned + // Games that depend on this: Affordable Space Adventures + uint64 currentTime = coreinit::OSGetTime(); + uint64 timeDif = currentTime - g_kpadLastRead[channel]; + if(length == 0 || timeDif < coreinit::EspressoTime::ConvertNsToTimerTicks(1000000)) + { + if (errResult) + *errResult = KPAD_ERROR::NO_SAMPLE_DATA; + return 0; + } + g_kpadLastRead[channel] = currentTime; + memset(samplingBufs, 0x00, sizeof(KPADStatus_t)); samplingBufs->wpadErr = WPAD_ERR_NONE; samplingBufs->data_format = controller->get_data_format(); @@ -474,7 +494,6 @@ void padscoreExport_KPADReadEx(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, samplesRead); } -bool debugUseDRC1 = true; void padscoreExport_KPADRead(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); @@ -726,7 +745,8 @@ namespace padscore // call sampling callback for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) { - if (g_padscore.controller_data[i].sampling_callback) { + if (g_padscore.controller_data[i].sampling_callback) + { if (const auto controller = instance.get_wpad_controller(i)) { cemuLog_log(LogType::InputAPI, "Calling WPADsamplingCallback({})", i); @@ -741,7 +761,7 @@ namespace padscore { OSCreateAlarm(&g_padscore.alarm); const uint64 start_tick = coreinit::coreinit_getOSTime(); - const uint64 period_tick = coreinit::EspressoTime::GetTimerClock(); // once a second + const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler); } diff --git a/src/Cafe/OS/libs/vpad/vpad.cpp b/src/Cafe/OS/libs/vpad/vpad.cpp index 94bb0ca2..ded4304d 100644 --- a/src/Cafe/OS/libs/vpad/vpad.cpp +++ b/src/Cafe/OS/libs/vpad/vpad.cpp @@ -50,7 +50,6 @@ extern bool isLaunchTypeELF; -bool debugUseDRC = true; VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] = { {{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}}, @@ -240,19 +239,20 @@ namespace vpad status->tpProcessed2.validity = VPAD_TP_VALIDITY_INVALID_XY; const auto controller = InputManager::instance().get_vpad_controller(channel); - if (!controller || debugUseDRC == false) + if (!controller) { - // no controller + // most games expect the Wii U GamePad to be connected, so even if the user has not set it up we should still return empty samples for channel 0 + if(channel != 0) + { + if (error) + *error = VPAD_READ_ERR_NO_CONTROLLER; + if (length > 0) + status->vpadErr = -1; + return 0; + } if (error) - *error = VPAD_READ_ERR_NONE; // VPAD_READ_ERR_NO_DATA; // VPAD_READ_ERR_NO_CONTROLLER; - + *error = VPAD_READ_ERR_NONE; return 1; - //osLib_returnFromFunction(hCPU, 1); return; - } - - if (channel != 0) - { - debugBreakpoint(); } const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled(); @@ -274,9 +274,7 @@ namespace vpad // not ready yet if (error) *error = VPAD_READ_ERR_NONE; - return 0; - //osLib_returnFromFunction(hCPU, 0); return; } else if (dif <= ESPRESSO_TIMER_CLOCK) { diff --git a/src/Cafe/TitleList/TitleId.h b/src/Cafe/TitleList/TitleId.h index b7f63b13..073472d9 100644 --- a/src/Cafe/TitleList/TitleId.h +++ b/src/Cafe/TitleList/TitleId.h @@ -13,6 +13,7 @@ public: /* 00 */ BASE_TITLE = 0x00, // eShop and disc titles /* 02 */ BASE_TITLE_DEMO = 0x02, /* 0E */ BASE_TITLE_UPDATE = 0x0E, // update for BASE_TITLE (and maybe BASE_TITLE_DEMO?) + /* 0F */ HOMEBREW = 0x0F, /* 0C */ AOC = 0x0C, // DLC /* 10 */ SYSTEM_TITLE = 0x10, // eShop etc /* 1B */ SYSTEM_DATA = 0x1B, @@ -43,6 +44,8 @@ public: return TITLE_TYPE::BASE_TITLE_DEMO; case 0x0E: return TITLE_TYPE::BASE_TITLE_UPDATE; + case 0x0F: + return TITLE_TYPE::HOMEBREW; case 0x0C: return TITLE_TYPE::AOC; case 0x10: diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 058ab07a..5cde2a7f 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -36,6 +36,7 @@ struct _LogContext const std::map g_logging_window_mapping { {LogType::UnsupportedAPI, "Unsupported API calls"}, + {LogType::APIErrors, "Invalid API usage"}, {LogType::CoreinitLogging, "Coreinit Logging"}, {LogType::CoreinitFile, "Coreinit File-Access"}, {LogType::CoreinitThreadSync, "Coreinit Thread-Synchronization"}, @@ -51,6 +52,8 @@ const std::map g_logging_window_mapping {LogType::Socket, "Socket"}, {LogType::Save, "Save"}, {LogType::H264, "H264"}, + {LogType::NFC, "NFC"}, + {LogType::NTAG, "NTAG"}, {LogType::Patches, "Graphic pack patches"}, {LogType::TextureCache, "Texture cache"}, {LogType::TextureReadback, "Texture readback"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 8fbb318c..a671ce51 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -7,7 +7,7 @@ enum class LogType : sint32 // note: IDs must be in range 1-64 Force = 63, // always enabled Placeholder = 62, // always disabled - APIErrors = Force, // alias for Force. Logs bad parameters or other API usage mistakes or unintended errors in OS libs + APIErrors = 61, // Logs bad parameters or other API usage mistakes or unintended errors in OS libs. Intended for homebrew developers CoreinitFile = 0, GX2 = 1, @@ -44,6 +44,9 @@ enum class LogType : sint32 nlibcurl = 41, PRUDP = 40, + + NFC = 41, + NTAG = 42, }; template <> diff --git a/src/Cemu/PPCAssembler/ppcAssembler.cpp b/src/Cemu/PPCAssembler/ppcAssembler.cpp index 5bab7b8b..df20b21d 100644 --- a/src/Cemu/PPCAssembler/ppcAssembler.cpp +++ b/src/Cemu/PPCAssembler/ppcAssembler.cpp @@ -2418,6 +2418,9 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* _ppcAssembler_translateAlias(instructionName); // parse operands internalInfo.listOperandStr.clear(); + + bool isInString = false; + while (currentPtr < endPtr) { currentPtr++; @@ -2425,7 +2428,10 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* // find end of operand while (currentPtr < endPtr) { - if (*currentPtr == ',') + if (*currentPtr == '"') + isInString=!isInString; + + if (*currentPtr == ',' && !isInString) break; currentPtr++; } diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index e498d07f..182c5371 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -107,6 +107,7 @@ CurlRequestHelper::CurlRequestHelper() curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, 2); + curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); if(GetConfig().proxy_server.GetValue() != "") { @@ -263,6 +264,7 @@ CurlSOAPHelper::CurlSOAPHelper(NetworkService service) m_curl = curl_easy_init(); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); // SSL if (!IsNetworkServiceSSLDisabled(service)) @@ -388,9 +390,8 @@ bool CurlSOAPHelper::submitRequest() headers = curl_slist_append(headers, "Accept-Charset: UTF-8"); headers = curl_slist_append(headers, fmt::format("SOAPAction: urn:{}.wsapi.broadon.com/{}", m_serviceType, m_requestMethod).c_str()); headers = curl_slist_append(headers, "Accept: */*"); - headers = curl_slist_append(headers, "User-Agent: EVL NUP 040800 Sep 18 2012 20:20:02"); - curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "EVL NUP 040800 Sep 18 2012 20:20:02"); // send request auto res = curl_easy_perform(m_curl); diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index 927418ca..36ba4a53 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -277,7 +277,8 @@ void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response } NexFriends* session = (NexFriends*)nexFriends; session->myPreference = nexPrincipalPreference(&response->data); - nexComment comment(&response->data); + auto comment = nexComment(&response->data); + session->myComment = comment; if (response->data.hasReadOutOfBounds()) return; // acquire lock on lists @@ -391,6 +392,28 @@ void NexFriends::getMyPreference(nexPrincipalPreference& preference) preference = myPreference; } +bool NexFriends::updateCommentAsync(nexComment newComment, std::function cb) +{ + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + newComment.writeData(&packetBuffer); + nexCon->callMethod( + NEX_PROTOCOL_FRIENDS_WIIU, 15, &packetBuffer, [this, cb, newComment](nexServiceResponse_t* response) -> void { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + this->myComment = newComment; + return cb(NexFriends::ERR_NONE); + }, + true); + // TEST + return true; +} + +void NexFriends::getMyComment(nexComment& comment) +{ + comment = myComment; +} + bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId) { uint8 tempNexBufferArray[512]; diff --git a/src/Cemu/nex/nexFriends.h b/src/Cemu/nex/nexFriends.h index 1077b0d5..05cc433f 100644 --- a/src/Cemu/nex/nexFriends.h +++ b/src/Cemu/nex/nexFriends.h @@ -297,7 +297,9 @@ public: void writeData(nexPacketBuffer* pb) const override { - cemu_assert_unimplemented(); + pb->writeU8(ukn0); + pb->writeString(commentString.c_str()); + pb->writeU64(ukn1); } void readData(nexPacketBuffer* pb) override @@ -554,6 +556,7 @@ public: bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId); bool isOnline(); void getMyPreference(nexPrincipalPreference& preference); + void getMyComment(nexComment& comment); // asynchronous API (data has to be requested) bool addProvisionalFriend(char* name, std::function cb); @@ -565,6 +568,7 @@ public: 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); + bool updateCommentAsync(const nexComment newComment, std::function cb); void updateMyPresence(nexPresenceV2& myPresence); void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)); @@ -619,6 +623,7 @@ private: // local friend state nexPresenceV2 myPresence; nexPrincipalPreference myPreference; + nexComment myComment; std::recursive_mutex mtx_lists; std::vector list_friends; diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp index b6755fd8..7530a2eb 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -6,8 +6,6 @@ #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "ExceptionHandler.h" -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - bool crashLogCreated = false; bool CrashLog_Create() @@ -97,7 +95,7 @@ void ExceptionHandler_LogGeneralInfo() MPTR currentStackVAddr = hCPU->gpr[1]; CrashLog_WriteLine(""); CrashLog_WriteHeader("PPC stack trace"); - DebugLogStackTrace(currentThread, currentStackVAddr); + DebugLogStackTrace(currentThread, currentStackVAddr, true); // stack dump CrashLog_WriteLine(""); diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index b2362d0b..142da7e4 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -11,7 +11,7 @@ using PAddr = uint32; // physical address extern uint8* memory_base; extern uint8* PPCInterpreterGetStackPointer(); -extern uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); extern void PPCInterpreterModifyStackPointer(sint32 offset); class MEMPTRBase {}; @@ -92,19 +92,6 @@ public: template explicit operator MEMPTR() const { return MEMPTR(this->m_value); } - //bool operator==(const MEMPTR& v) const { return m_value == v.m_value; } - //bool operator==(const T* rhs) const { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value) == rhs; } -> ambigious (implicit cast to T* allows for T* == T*) - //bool operator==(std::nullptr_t rhs) const { return m_value == 0; } - - //bool operator!=(const MEMPTR& v) const { return !(*this == v); } - //bool operator!=(const void* rhs) const { return !(*this == rhs); } - //bool operator!=(int rhs) const { return !(*this == rhs); } - - //bool operator==(const void* rhs) const { return (void*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value) == rhs; } - - //explicit bool operator==(int rhs) const { return *this == (const void*)(size_t)rhs; } - - MEMPTR operator+(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() + ptr.GetMPTR()); } MEMPTR operator-(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() - ptr.GetMPTR()); } @@ -120,6 +107,12 @@ public: return MEMPTR(this->GetMPTR() - v * 4); } + MEMPTR& operator+=(sint32 v) + { + m_value += v * sizeof(T); + return *this; + } + template typename std::enable_if::value, Q>::type& operator*() const { return *GetPtr(); } diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h index 1dc52d51..856224cf 100644 --- a/src/Common/StackAllocator.h +++ b/src/Common/StackAllocator.h @@ -10,14 +10,16 @@ public: explicit StackAllocator(const uint32 items) { + m_items = items; m_modified_size = count * sizeof(T) * items + kStaticMemOffset * 2; - - auto tmp = PPCInterpreterGetStackPointer(); - m_ptr = (T*)(PPCInterpreterGetAndModifyStackPointer(m_modified_size) + kStaticMemOffset); + m_modified_size = (m_modified_size/8+7) * 8; // pad to 8 bytes + m_ptr = new(PPCInterpreter_PushAndReturnStackPointer(m_modified_size) + kStaticMemOffset) T[count * items](); } ~StackAllocator() { + for (size_t i = 0; i < count * m_items; ++i) + m_ptr[i].~T(); PPCInterpreterModifyStackPointer(-m_modified_size); } @@ -64,4 +66,5 @@ private: T* m_ptr; sint32 m_modified_size; + uint32 m_items; }; \ No newline at end of file diff --git a/src/Common/betype.h b/src/Common/betype.h index e684fb93..60a64b7a 100644 --- a/src/Common/betype.h +++ b/src/Common/betype.h @@ -121,6 +121,12 @@ public: return *this; } + betype& operator+=(const T& v) requires std::integral + { + m_value = SwapEndian(T(value() + v)); + return *this; + } + betype& operator-=(const betype& v) { m_value = SwapEndian(T(value() - v.value())); @@ -188,17 +194,36 @@ public: return from_bevalue(T(~m_value)); } + // pre-increment betype& operator++() requires std::integral { m_value = SwapEndian(T(value() + 1)); return *this; } + // post-increment + betype operator++(int) requires std::integral + { + betype tmp(*this); + m_value = SwapEndian(T(value() + 1)); + return tmp; + } + + // pre-decrement betype& operator--() requires std::integral { m_value = SwapEndian(T(value() - 1)); return *this; } + + // post-decrement + betype operator--(int) requires std::integral + { + betype tmp(*this); + m_value = SwapEndian(T(value() - 1)); + return tmp; + } + private: //T m_value{}; // before 1.26.2 T m_value; diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 790a001a..61707519 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -552,6 +552,9 @@ inline uint32 GetTitleIdLow(uint64 titleId) #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/PPCCallback.h" +// PPC stack trace printer +void DebugLogStackTrace(struct OSThread_t* thread, MPTR sp, bool printSymbols = false); + // generic formatter for enums (to underlying) template requires std::is_enum_v diff --git a/src/Common/unix/FileStream_unix.cpp b/src/Common/unix/FileStream_unix.cpp index 2dba17b7..4bc9b526 100644 --- a/src/Common/unix/FileStream_unix.cpp +++ b/src/Common/unix/FileStream_unix.cpp @@ -1,4 +1,5 @@ #include "Common/unix/FileStream_unix.h" +#include fs::path findPathCI(const fs::path& path) { diff --git a/src/Common/version.h b/src/Common/version.h index 8c08f238..36925a52 100644 --- a/src/Common/version.h +++ b/src/Common/version.h @@ -1,36 +1,19 @@ #ifndef EMULATOR_NAME #define EMULATOR_NAME "Cemu" -#define EMULATOR_VERSION_LEAD 2 -#define EMULATOR_VERSION_MAJOR 0 -// the minor version is used for experimental builds to indicate the build index. Set by command line option from CI build script -// if zero, the version text will be constructed as LEAD.MAJOR, otherwise as LEAD.MAJOR-MINOR - -#if defined(EMULATOR_VERSION_MINOR) && EMULATOR_VERSION_MINOR == 0 #define EMULATOR_VERSION_SUFFIX "" -#else -#define EMULATOR_VERSION_SUFFIX " (experimental)" -#endif - -#ifndef EMULATOR_VERSION_MINOR -#define EMULATOR_VERSION_MINOR 0 -#endif #define _XSTRINGFY(s) _STRINGFY(s) #define _STRINGFY(s) #s -#if EMULATOR_VERSION_MINOR != 0 -#if defined(EMULATOR_HASH) && EMULATOR_VERSION_MINOR == 999999 -#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) -#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) +#if EMULATOR_VERSION_MAJOR != 0 +#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) +#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) #else -#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) -#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) -#endif -#else -#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) EMULATOR_VERSION_SUFFIX) -#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) EMULATOR_VERSION_SUFFIX) +// no version provided. Only show commit hash +#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) +#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) #endif #endif diff --git a/src/asm/CMakeLists.txt b/src/asm/CMakeLists.txt index 5d9f84c2..19a7ddd8 100644 --- a/src/asm/CMakeLists.txt +++ b/src/asm/CMakeLists.txt @@ -1,6 +1,12 @@ project(CemuAsm C) -if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") +if (CMAKE_OSX_ARCHITECTURES) + set(CEMU_ASM_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) +else() + set(CEMU_ASM_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +if (CEMU_ASM_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)") if (WIN32) @@ -40,8 +46,8 @@ if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") endif() -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") +elseif(CEMU_ASM_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") add_library(CemuAsm stub.cpp) else() - message(STATUS "CemuAsm - Unsupported arch: ${CMAKE_SYSTEM_PROCESSOR}") + message(STATUS "CemuAsm - Unsupported arch: ${CEMU_ASM_ARCHITECTURE}") endif() diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 07e6f16d..560f2986 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -7,41 +7,47 @@ #include "config/LaunchSettings.h" #include "util/helpers/helpers.h" -std::set -ActiveSettings::LoadOnce( - const fs::path& executablePath, - const fs::path& userDataPath, - const fs::path& configPath, - const fs::path& cachePath, - const fs::path& dataPath) +void ActiveSettings::SetPaths(bool isPortableMode, + const fs::path& executablePath, + const fs::path& userDataPath, + const fs::path& configPath, + const fs::path& cachePath, + const fs::path& dataPath, + std::set& failedWriteAccess) { + cemu_assert_debug(!s_setPathsCalled); // can only change paths before loading + s_isPortableMode = isPortableMode; s_executable_path = executablePath; s_user_data_path = userDataPath; s_config_path = configPath; s_cache_path = cachePath; s_data_path = dataPath; - std::set failed_write_access; + failedWriteAccess.clear(); for (auto&& path : {userDataPath, configPath, cachePath}) { - if (!fs::exists(path)) - { - std::error_code ec; + std::error_code ec; + if (!fs::exists(path, ec)) fs::create_directories(path, ec); - } if (!TestWriteAccess(path)) { cemuLog_log(LogType::Force, "Failed to write to {}", _pathToUtf8(path)); - failed_write_access.insert(path); + failedWriteAccess.insert(path); } } - s_executable_filename = s_executable_path.filename(); + s_setPathsCalled = true; +} - g_config.SetFilename(GetConfigPath("settings.xml").generic_wstring()); - g_config.Load(); +[[nodiscard]] bool ActiveSettings::IsPortableMode() +{ + return s_isPortableMode; +} + +void ActiveSettings::Init() +{ + cemu_assert_debug(s_setPathsCalled); std::string additionalErrorInfo; s_has_required_online_files = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo) == IOS_CRYPTO_ONLINE_REQ_OK; - return failed_write_access; } bool ActiveSettings::LoadSharedLibrariesEnabled() @@ -229,6 +235,7 @@ bool ActiveSettings::ForceSamplerRoundToPrecision() fs::path ActiveSettings::GetMlcPath() { + cemu_assert_debug(s_setPathsCalled); if(const auto launch_mlc = LaunchSettings::GetMLCPath(); launch_mlc.has_value()) return launch_mlc.value(); @@ -238,6 +245,17 @@ fs::path ActiveSettings::GetMlcPath() return GetDefaultMLCPath(); } +bool ActiveSettings::IsCustomMlcPath() +{ + cemu_assert_debug(s_setPathsCalled); + return !GetConfig().mlc_path.GetValue().empty(); +} + +bool ActiveSettings::IsCommandLineMlcPath() +{ + return LaunchSettings::GetMLCPath().has_value(); +} + fs::path ActiveSettings::GetDefaultMLCPath() { return GetUserDataPath("mlc01"); diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index 54052741..e672fbee 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -34,12 +34,16 @@ private: public: // Set directories and return all directories that failed write access test - static std::set - LoadOnce(const fs::path& executablePath, - const fs::path& userDataPath, - const fs::path& configPath, - const fs::path& cachePath, - const fs::path& dataPath); + static void + SetPaths(bool isPortableMode, + const fs::path& executablePath, + const fs::path& userDataPath, + const fs::path& configPath, + const fs::path& cachePath, + const fs::path& dataPath, + std::set& failedWriteAccess); + + static void Init(); [[nodiscard]] static fs::path GetExecutablePath() { return s_executable_path; } [[nodiscard]] static fs::path GetExecutableFilename() { return s_executable_filename; } @@ -56,11 +60,14 @@ public: template [[nodiscard]] static fs::path GetMlcPath(TArgs&&... args){ return GetPath(GetMlcPath(), std::forward(args)...); }; + static bool IsCustomMlcPath(); + static bool IsCommandLineMlcPath(); // get mlc path to default cemu root dir/mlc01 [[nodiscard]] static fs::path GetDefaultMLCPath(); private: + inline static bool s_isPortableMode{false}; inline static fs::path s_executable_path; inline static fs::path s_user_data_path; inline static fs::path s_config_path; @@ -70,6 +77,9 @@ private: inline static fs::path s_mlc_path; public: + // can be called before Init + [[nodiscard]] static bool IsPortableMode(); + // general [[nodiscard]] static bool LoadSharedLibrariesEnabled(); [[nodiscard]] static bool DisplayDRCEnabled(); @@ -111,6 +121,7 @@ public: [[nodiscard]] static bool ForceSamplerRoundToPrecision(); private: + inline static bool s_setPathsCalled = false; // dump options inline static bool s_dump_shaders = false; inline static bool s_dump_textures = false; diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt index f02b95d4..d53e8574 100644 --- a/src/config/CMakeLists.txt +++ b/src/config/CMakeLists.txt @@ -8,10 +8,6 @@ add_library(CemuConfig LaunchSettings.h NetworkSettings.cpp NetworkSettings.h - PermanentConfig.cpp - PermanentConfig.h - PermanentStorage.cpp - PermanentStorage.h XMLConfig.h ) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 9360ff68..d5db5970 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -5,7 +5,6 @@ #include -#include "PermanentConfig.h" #include "ActiveSettings.h" XMLCemuConfig_t g_config(L"settings.xml"); @@ -15,23 +14,6 @@ void CemuConfig::SetMLCPath(fs::path path, bool save) mlc_path.SetValue(_pathToUtf8(path)); if(save) g_config.Save(); - - // if custom mlc path has been selected, store it in permanent config - if (path != ActiveSettings::GetDefaultMLCPath()) - { - try - { - auto pconfig = PermanentConfig::Load(); - pconfig.custom_mlc_path = _pathToUtf8(path); - pconfig.Store(); - } - catch (const PSDisabledException&) {} - catch (const std::exception& ex) - { - cemuLog_log(LogType::Force, "can't store custom mlc path in permanent storage: {}", ex.what()); - } - } - Account::RefreshAccounts(); } @@ -56,6 +38,7 @@ void CemuConfig::Load(XMLConfigParser& parser) fullscreen_menubar = parser.get("fullscreen_menubar", false); feral_gamemode = parser.get("feral_gamemode", false); check_update = parser.get("check_update", check_update); + receive_untested_updates = parser.get("receive_untested_updates", check_update); save_screenshot = parser.get("save_screenshot", save_screenshot); did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); did_show_graphic_pack_download = parser.get("gp_download", did_show_graphic_pack_download); @@ -359,6 +342,11 @@ void CemuConfig::Load(XMLConfigParser& parser) auto dsuc = input.get("DSUC"); dsu_client.host = dsuc.get_attribute("host", dsu_client.host); dsu_client.port = dsuc.get_attribute("port", dsu_client.port); + + // emulatedusbdevices + auto usbdevices = parser.get("EmulatedUsbDevices"); + emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); + emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base); } void CemuConfig::Save(XMLConfigParser& parser) @@ -374,6 +362,7 @@ void CemuConfig::Save(XMLConfigParser& parser) config.set("fullscreen_menubar", fullscreen_menubar); config.set("feral_gamemode", feral_gamemode); config.set("check_update", check_update); + config.set("receive_untested_updates", receive_untested_updates); config.set("save_screenshot", save_screenshot); config.set("vk_warning", did_show_vulkan_warning); config.set("gp_download", did_show_graphic_pack_download); @@ -553,6 +542,11 @@ void CemuConfig::Save(XMLConfigParser& parser) auto dsuc = input.set("DSUC"); dsuc.set_attribute("host", dsu_client.host); dsuc.set_attribute("port", dsu_client.port); + + // emulated usb devices + auto usbdevices = config.set("EmulatedUsbDevices"); + usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); + usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 4e4f03d6..79413af5 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -414,11 +414,12 @@ struct CemuConfig Vector2i pad_size{ -1,-1 }; ConfigValue pad_maximized; - ConfigValue check_update{false}; + ConfigValue check_update{true}; + ConfigValue receive_untested_updates{false}; ConfigValue save_screenshot{true}; ConfigValue did_show_vulkan_warning{false}; - ConfigValue did_show_graphic_pack_download{false}; + ConfigValue did_show_graphic_pack_download{false}; // no longer used but we keep the config value around in case people downgrade Cemu. Despite the name this was used for the Getting Started dialog ConfigValue did_show_macos_disclaimer{false}; ConfigValue show_icon_column{ true }; @@ -442,7 +443,7 @@ struct CemuConfig ConfigValue vsync{ 0 }; // 0 = off, 1+ = on depending on render backend ConfigValue gx2drawdone_sync {true}; ConfigValue render_upside_down{ false }; - ConfigValue async_compile{ false }; + ConfigValue async_compile{ true }; ConfigValue vk_accurate_barriers{ true }; @@ -515,6 +516,13 @@ struct CemuConfig NetworkService GetAccountNetworkService(uint32 persistentId); void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); + + // emulated usb devices + struct + { + ConfigValue emulate_skylander_portal{false}; + ConfigValue emulate_infinity_base{false}; + }emulated_usb_devices{}; static int AudioChannelsToNChannels(AudioChannels kStereo) { diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index 1731f500..bf38b9cf 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -112,10 +112,10 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) { requireConsole(); std::string versionStr; -#if EMULATOR_VERSION_MINOR == 0 - versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); +#if EMULATOR_VERSION_PATCH == 0 + versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX); #else - versionStr = fmt::format("{}.{}-{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX); + versionStr = fmt::format("{}.{}-{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, EMULATOR_VERSION_SUFFIX); #endif std::cout << versionStr << std::endl; return false; // exit in main diff --git a/src/config/PermanentConfig.cpp b/src/config/PermanentConfig.cpp deleted file mode 100644 index 20a44d28..00000000 --- a/src/config/PermanentConfig.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "PermanentConfig.h" - -#include "pugixml.hpp" - -#include "PermanentStorage.h" - -struct xml_string_writer : pugi::xml_writer -{ - std::string result; - - void write(const void* data, size_t size) override - { - result.append(static_cast(data), size); - } -}; - -std::string PermanentConfig::ToXMLString() const noexcept -{ - pugi::xml_document doc; - doc.append_child(pugi::node_declaration).append_attribute("encoding") = "UTF-8"; - auto root = doc.append_child("config"); - root.append_child("MlcPath").text().set(this->custom_mlc_path.c_str()); - - xml_string_writer writer; - doc.save(writer); - return writer.result; -} - -PermanentConfig PermanentConfig::FromXMLString(std::string_view str) noexcept -{ - PermanentConfig result{}; - - pugi::xml_document doc; - if(doc.load_buffer(str.data(), str.size())) - { - result.custom_mlc_path = doc.select_node("/config/MlcPath").node().text().as_string(); - } - - return result; -} - -PermanentConfig PermanentConfig::Load() -{ - PermanentStorage storage; - - const auto str = storage.ReadFile(kFileName); - if (!str.empty()) - return FromXMLString(str); - - return {}; -} - -bool PermanentConfig::Store() noexcept -{ - try - { - PermanentStorage storage; - storage.WriteStringToFile(kFileName, ToXMLString()); - } - catch (...) - { - return false; - } - return true; -} diff --git a/src/config/PermanentConfig.h b/src/config/PermanentConfig.h deleted file mode 100644 index 8c134747..00000000 --- a/src/config/PermanentConfig.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "PermanentStorage.h" - -struct PermanentConfig -{ - static constexpr const char* kFileName = "perm_setting.xml"; - - std::string custom_mlc_path; - - [[nodiscard]] std::string ToXMLString() const noexcept; - static PermanentConfig FromXMLString(std::string_view str) noexcept; - - // gets from permanent storage - static PermanentConfig Load(); - // saves to permanent storage - bool Store() noexcept; -}; diff --git a/src/config/PermanentStorage.cpp b/src/config/PermanentStorage.cpp deleted file mode 100644 index e095ff4b..00000000 --- a/src/config/PermanentStorage.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "PermanentStorage.h" -#include "config/CemuConfig.h" -#include "util/helpers/SystemException.h" - -PermanentStorage::PermanentStorage() -{ - if (!GetConfig().permanent_storage) - throw PSDisabledException(); - - const char* appdata = std::getenv("LOCALAPPDATA"); - if (!appdata) - throw std::runtime_error("can't get LOCALAPPDATA"); - m_storage_path = appdata; - m_storage_path /= "Cemu"; - - fs::create_directories(m_storage_path); -} - -PermanentStorage::~PermanentStorage() -{ - if (m_remove_storage) - { - std::error_code ec; - fs::remove_all(m_storage_path, ec); - if (ec) - { - SystemException ex(ec); - cemuLog_log(LogType::Force, "can't remove permanent storage: {}", ex.what()); - } - } -} - -void PermanentStorage::ClearAllFiles() const -{ - fs::remove_all(m_storage_path); - fs::create_directories(m_storage_path); -} - -void PermanentStorage::RemoveStorage() -{ - m_remove_storage = true; -} - -void PermanentStorage::WriteStringToFile(std::string_view filename, std::string_view content) -{ - const auto name = m_storage_path.append(filename.data(), filename.data() + filename.size()); - std::ofstream file(name.string()); - file.write(content.data(), (uint32_t)content.size()); -} - -std::string PermanentStorage::ReadFile(std::string_view filename) noexcept -{ - try - { - const auto name = m_storage_path.append(filename.data(), filename.data() + filename.size()); - std::ifstream file(name, std::ios::in | std::ios::ate); - if (!file.is_open()) - return {}; - - const auto end = file.tellg(); - file.seekg(0, std::ios::beg); - const auto file_size = end - file.tellg(); - if (file_size == 0) - return {}; - - std::string result; - result.resize(file_size); - file.read(result.data(), file_size); - return result; - } - catch (...) - { - return {}; - } - -} diff --git a/src/config/PermanentStorage.h b/src/config/PermanentStorage.h deleted file mode 100644 index 3cda3d6d..00000000 --- a/src/config/PermanentStorage.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -// disabled by config -class PSDisabledException : public std::runtime_error -{ -public: - PSDisabledException() - : std::runtime_error("permanent storage is disabled by user") {} -}; - -class PermanentStorage -{ -public: - PermanentStorage(); - ~PermanentStorage(); - - void ClearAllFiles() const; - // flags storage to be removed on destruction - void RemoveStorage(); - - void WriteStringToFile(std::string_view filename, std::string_view content); - std::string ReadFile(std::string_view filename) noexcept; - -private: - fs::path m_storage_path; - bool m_remove_storage = false; -}; \ No newline at end of file diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 19ce95dc..02f96a9c 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -101,6 +101,8 @@ add_library(CemuGui PairingDialog.h TitleManager.cpp TitleManager.h + EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp + EmulatedUSBDevices/EmulatedUSBDeviceFrame.h windows/PPCThreadsViewer windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp windows/PPCThreadsViewer/DebugPPCThreadsWindow.h diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 505a09c6..f91c1e3a 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -3,11 +3,11 @@ #include "gui/wxgui.h" #include "config/CemuConfig.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "gui/guiWrapper.h" #include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" #include "gui/GettingStartedDialog.h" -#include "config/PermanentConfig.h" -#include "config/PermanentStorage.h" #include "input/InputManager.h" #include "gui/helpers/wxHelpers.h" #include "Cemu/ncrypto/ncrypto.h" @@ -30,7 +30,10 @@ wxIMPLEMENT_APP_NO_MAIN(CemuApp); extern WindowInfo g_window_info; extern std::shared_mutex g_mutex; -int mainEmulatorHLE(); +// forward declarations from main.cpp +void UnitTests(); +void CemuCommonInit(); + void HandlePostUpdate(); // Translation strings to extract for gettext: void unused_translation_dummy() @@ -54,34 +57,86 @@ void unused_translation_dummy() void(_("unknown")); } -bool CemuApp::OnInit() +#if BOOST_OS_WINDOWS +#include +fs::path GetAppDataRoamingPath() { + PWSTR path = nullptr; + HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &path); + if (result != S_OK || !path) + { + if (path) + CoTaskMemFree(path); + return {}; + } + std::string appDataPath = boost::nowide::narrow(path); + CoTaskMemFree(path); + return _utf8ToPath(appDataPath); +} +#endif + +#if BOOST_OS_WINDOWS +void CemuApp::DeterminePaths(std::set& failedWriteAccess) // for Windows +{ + std::error_code ec; + bool isPortable = false; fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); + fs::path portablePath = exePath.parent_path() / "portable"; + data_path = exePath.parent_path(); // the data path is always the same as the exe path + if (fs::exists(portablePath, ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = portablePath; + } + else + { + fs::path roamingPath = GetAppDataRoamingPath() / "Cemu"; + user_data_path = config_path = cache_path = roamingPath; + } + // on Windows Cemu used to be portable by default prior to 2.0-89 + // to remain backwards compatible with old installations we check for settings.xml in the Cemu directory + // if it exists, we use the exe path as the portable directory + if(!isPortable) // lower priority than portable directory + { + if (fs::exists(exePath.parent_path() / "settings.xml", ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = exePath.parent_path(); + } + } + ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); +} +#endif + #if BOOST_OS_LINUX +void CemuApp::DeterminePaths(std::set& failedWriteAccess) // for Linux +{ + std::error_code ec; + bool isPortable = false; + fs::path user_data_path, config_path, cache_path, data_path; + auto standardPaths = wxStandardPaths::Get(); + fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); + fs::path portablePath = exePath.parent_path() / "portable"; // GetExecutablePath returns the AppImage's temporary mount location wxString appImagePath; if (wxGetEnv(("APPIMAGE"), &appImagePath)) - exePath = wxHelper::MakeFSPath(appImagePath); -#endif - // Try a portable path first, if it exists. - user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; -#if BOOST_OS_MACOS - // If run from an app bundle, use its parent directory. - fs::path appPath = exePath.parent_path().parent_path().parent_path(); - if (appPath.extension() == ".app") - user_data_path = config_path = cache_path = data_path = appPath.parent_path() / "portable"; -#endif - - if (!fs::exists(user_data_path)) { -#if BOOST_OS_WINDOWS - user_data_path = config_path = cache_path = data_path = exePath.parent_path(); -#else + exePath = wxHelper::MakeFSPath(appImagePath); + portablePath = exePath.parent_path() / "portable"; + } + if (fs::exists(portablePath, ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = portablePath; + // in portable mode assume the data directories (resources, gameProfiles/default/) are next to the executable + data_path = exePath.parent_path(); + } + else + { SetAppName("Cemu"); - wxString appName=GetAppName(); -#if BOOST_OS_LINUX + wxString appName = GetAppName(); standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) { @@ -90,33 +145,154 @@ bool CemuApp::OnInit() return defaultValue; return dir; }; - wxString homeDir=wxFileName::GetHomeDir(); + wxString homeDir = wxFileName::GetHomeDir(); user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); -#else - user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); -#endif data_path = standardPaths.GetDataDir().ToStdString(); cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); cache_path /= appName.ToStdString(); -#endif } + ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); +} +#endif - auto failed_write_access = ActiveSettings::LoadOnce(exePath, user_data_path, config_path, cache_path, data_path); - for (auto&& path : failed_write_access) - wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), - _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); +#if BOOST_OS_MACOS +void CemuApp::DeterminePaths(std::set& failedWriteAccess) // for MacOS +{ + std::error_code ec; + bool isPortable = false; + fs::path user_data_path, config_path, cache_path, data_path; + auto standardPaths = wxStandardPaths::Get(); + fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); + // If run from an app bundle, use its parent directory + fs::path appPath = exePath.parent_path().parent_path().parent_path(); + fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable"; + if (fs::exists(portablePath, ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = portablePath; + data_path = exePath.parent_path(); + } + else + { + SetAppName("Cemu"); + wxString appName = GetAppName(); + user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); + data_path = standardPaths.GetDataDir().ToStdString(); + cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); + cache_path /= appName.ToStdString(); + } + ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); +} +#endif + +// create default MLC files or quit if it fails +void CemuApp::InitializeNewMLCOrFail(fs::path mlc) +{ + if( CemuApp::CreateDefaultMLCFiles(mlc) ) + return; // all good + cemu_assert_debug(!ActiveSettings::IsCustomMlcPath()); // should not be possible? + + if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath()) + { + // tell user that the custom path is not writable + wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); + } + wxMessageBox(formatWxString(_("Cemu failed to write to the mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); +} + +void CemuApp::InitializeExistingMLCOrFail(fs::path mlc) +{ + if(CreateDefaultMLCFiles(mlc)) + return; // all good + // failed to write mlc files + if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath()) + { + // tell user that the custom path is not writable + // if it's a command line path then just quit. Otherwise ask if user wants to reset the path + if(ActiveSettings::IsCommandLineMlcPath()) + { + wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); + } + // ask user if they want to reset the path + const wxString message = formatWxString(_("Cemu failed to write to the custom mlc directory.\n\nThe path is:\n{}\n\nCemu cannot start without a valid mlc path. Do you want to reset the path? You can later change it again in the General Settings."), + _pathToUtf8(mlc)); + wxMessageDialog dialog(nullptr, message, _("Error"), wxCENTRE | wxYES_NO | wxICON_WARNING); + dialog.SetYesNoLabels(_("Reset path"), _("Exit")); + const auto dialogResult = dialog.ShowModal(); + if (dialogResult == wxID_NO) + exit(0); + else // reset path + { + GetConfig().mlc_path = ""; + g_config.Save(); + } + } +} + +bool CemuApp::OnInit() +{ +#if __WXGTK__ + GTKSuppressDiagnostics(G_LOG_LEVEL_MASK & ~G_LOG_FLAG_FATAL); +#endif + std::set failedWriteAccess; + DeterminePaths(failedWriteAccess); + // make sure default cemu directories exist + CreateDefaultCemuFiles(); + + g_config.SetFilename(ActiveSettings::GetConfigPath("settings.xml").generic_wstring()); + + std::error_code ec; + bool isFirstStart = !fs::exists(ActiveSettings::GetConfigPath("settings.xml"), ec); NetworkConfig::LoadOnce(); - g_config.Load(); + if(!isFirstStart) + { + g_config.Load(); + LocalizeUI(static_cast(GetConfig().language == wxLANGUAGE_DEFAULT ? wxLocale::GetSystemLanguage() : GetConfig().language.GetValue())); + } + else + { + LocalizeUI(static_cast(wxLocale::GetSystemLanguage())); + } + for (auto&& path : failedWriteAccess) + { + wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), + _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); + } + + if (isFirstStart) + { + // show the getting started dialog + GettingStartedDialog dia(nullptr); + dia.ShowModal(); + // make sure config is created. Gfx pack UI and input UI may create it earlier already, but we still want to update it + g_config.Save(); + // create mlc, on failure the user can quit here. So do this after the Getting Started dialog + InitializeNewMLCOrFail(ActiveSettings::GetMlcPath()); + } + else + { + // check if mlc is valid and recreate default files + InitializeExistingMLCOrFail(ActiveSettings::GetMlcPath()); + } + + ActiveSettings::Init(); // this is a bit of a misnomer, right now this call only loads certs for online play. In the future we should move the logic to a more appropriate place HandlePostUpdate(); - mainEmulatorHLE(); + + LatteOverlay_init(); + // run a couple of tests if in non-release mode +#ifdef CEMU_DEBUG_ASSERT + UnitTests(); +#endif + CemuCommonInit(); wxInitAllImageHandlers(); - LocalizeUI(); - // fill colour db wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0)); wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0)); @@ -135,15 +311,8 @@ bool CemuApp::OnInit() Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this); auto& config = GetConfig(); - const bool first_start = !config.did_show_graphic_pack_download; - - CreateDefaultFiles(first_start); - m_mainFrame = new MainWindow(); - if (first_start) - m_mainFrame->ShowGettingStartedDialog(); - std::unique_lock lock(g_mutex); g_window_info.app_active = true; @@ -230,22 +399,22 @@ std::vector CemuApp::GetLanguages() const { return availableLanguages; } -void CemuApp::LocalizeUI() +void CemuApp::LocalizeUI(wxLanguage languageToUse) { 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) + [languageToUse](const wxLanguageInfo* info) { return info->Language == languageToUse; }); + if (languageToUse == wxLANGUAGE_DEFAULT || isTranslationAvailable) { - translationsMgr->SetLanguage(static_cast(configuredLanguage)); + translationsMgr->SetLanguage(static_cast(languageToUse)); translationsMgr->AddCatalog("cemu"); - if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(configuredLanguage)) - m_locale.Init(configuredLanguage); - + if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(languageToUse)) + { + m_locale.Init(languageToUse); + } // 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()); } @@ -264,55 +433,47 @@ std::vector CemuApp::GetAvailableTranslationLanguages(wxT return languages; } -void CemuApp::CreateDefaultFiles(bool first_start) +bool CemuApp::CheckMLCPath(const fs::path& mlc) { - fs::path mlc = ActiveSettings::GetMlcPath(); + std::error_code ec; + if (!fs::exists(mlc, ec)) + return false; + if (!fs::exists(mlc / "usr", ec) || !fs::exists(mlc / "sys", ec)) + return false; + return true; +} - // check for mlc01 folder missing if custom path has been set - if (!fs::exists(mlc) && !first_start) +bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc) +{ + auto CreateDirectoriesIfNotExist = [](const fs::path& path) { - 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); - dialog.SetYesNoCancelLabels(_("Yes"), _("No"), _("Select a custom path")); - const auto dialogResult = dialog.ShowModal(); - if (dialogResult == wxID_NO) - exit(0); - else if(dialogResult == wxID_CANCEL) - { - if (!SelectMLCPath()) - return; - mlc = ActiveSettings::GetMlcPath(); - } - else - { - GetConfig().mlc_path = ""; - g_config.Save(); - } + std::error_code ec; + if (!fs::exists(path, ec)) + return fs::create_directories(path, ec); + return true; + }; + // list of directories to create + const fs::path directories[] = { + mlc, + mlc / "sys", + mlc / "usr", + mlc / "usr/title/00050000", // base + mlc / "usr/title/0005000c", // dlc + mlc / "usr/title/0005000e", // update + mlc / "usr/save/00050010/1004a000/user/common/db", // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200} + mlc / "usr/save/00050010/1004a100/user/common/db", + mlc / "usr/save/00050010/1004a200/user/common/db", + mlc / "sys/title/0005001b/1005c000/content" // lang files + }; + for(auto& path : directories) + { + if(!CreateDirectoriesIfNotExist(path)) + return false; } - // create sys/usr folder in mlc01 try { - const auto sysFolder = fs::path(mlc).append("sys"); - fs::create_directories(sysFolder); - - const auto usrFolder = fs::path(mlc).append("usr"); - fs::create_directories(usrFolder); - fs::create_directories(fs::path(usrFolder).append("title/00050000")); // base - fs::create_directories(fs::path(usrFolder).append("title/0005000c")); // dlc - fs::create_directories(fs::path(usrFolder).append("title/0005000e")); // update - - // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200}, - fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a000/user/common/db")); - fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a100/user/common/db")); - fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a200/user/common/db")); - - // lang files const auto langDir = fs::path(mlc).append("sys/title/0005001b/1005c000/content"); - fs::create_directories(langDir); - auto langFile = fs::path(langDir).append("language.txt"); if (!fs::exists(langFile)) { @@ -346,18 +507,13 @@ void CemuApp::CreateDefaultFiles(bool first_start) } catch (const std::exception& ex) { - 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)); -#endif - - wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - exit(0); + return false; } + return true; +} +void CemuApp::CreateDefaultCemuFiles() +{ // cemu directories try { @@ -384,58 +540,6 @@ void CemuApp::CreateDefaultFiles(bool first_start) } } - -bool CemuApp::TrySelectMLCPath(fs::path path) -{ - if (path.empty()) - path = ActiveSettings::GetDefaultMLCPath(); - - if (!TestWriteAccess(path)) - return false; - - GetConfig().SetMLCPath(path); - CemuApp::CreateDefaultFiles(); - - // update TitleList and SaveList scanner with new MLC path - CafeTitleList::SetMLCPath(path); - CafeTitleList::Refresh(); - CafeSaveList::SetMLCPath(path); - CafeSaveList::Refresh(); - return true; -} - -bool CemuApp::SelectMLCPath(wxWindow* parent) -{ - auto& config = GetConfig(); - - fs::path default_path; - if (fs::exists(_utf8ToPath(config.mlc_path.GetValue()))) - default_path = _utf8ToPath(config.mlc_path.GetValue()); - - // try until users selects a valid path or aborts - while(true) - { - wxDirDialog path_dialog(parent, _("Select a mlc directory"), wxHelper::FromPath(default_path), wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); - if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) - return false; - - const auto path = path_dialog.GetPath().ToStdWstring(); - - if (!TrySelectMLCPath(path)) - { - const auto result = wxMessageBox(_("Cemu can't write to the selected mlc path!\nDo you want to select another path?"), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR); - if (result == wxYES) - continue; - - break; - } - - return true; - } - - return false; -} - void CemuApp::ActivateApp(wxActivateEvent& event) { g_window_info.app_active = event.GetActive(); diff --git a/src/gui/CemuApp.h b/src/gui/CemuApp.h index cfdab0a2..b73d627d 100644 --- a/src/gui/CemuApp.h +++ b/src/gui/CemuApp.h @@ -15,13 +15,18 @@ public: std::vector GetLanguages() const; - static void CreateDefaultFiles(bool first_start = false); - static bool TrySelectMLCPath(fs::path path); - static bool SelectMLCPath(wxWindow* parent = nullptr); + static bool CheckMLCPath(const fs::path& mlc); + static bool CreateDefaultMLCFiles(const fs::path& mlc); + static void CreateDefaultCemuFiles(); + static void InitializeNewMLCOrFail(fs::path mlc); + static void InitializeExistingMLCOrFail(fs::path mlc); private: + void LocalizeUI(wxLanguage languageToUse); + + void DeterminePaths(std::set& failedWriteAccess); + void ActivateApp(wxActivateEvent& event); - void LocalizeUI(); static std::vector GetAvailableTranslationLanguages(wxTranslations* translationsMgr); MainWindow* m_mainFrame = nullptr; diff --git a/src/gui/CemuUpdateWindow.cpp b/src/gui/CemuUpdateWindow.cpp index 445c7c17..6a4e6885 100644 --- a/src/gui/CemuUpdateWindow.cpp +++ b/src/gui/CemuUpdateWindow.cpp @@ -116,9 +116,11 @@ bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& #elif BOOST_OS_MACOS urlStr.append("&platform=macos_bundle_x86"); #elif - #error Name for current platform is missing #endif + const auto& config = GetConfig(); + if(config.receive_untested_updates) + urlStr.append("&allowNewUpdates=1"); curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str()); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp index 03f102d2..9ea9e1dd 100644 --- a/src/gui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -115,7 +115,7 @@ void DownloadGraphicPacksWindow::UpdateThread() curlDownloadFileState_t tempDownloadState; std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?"); char temp[64]; - sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); queryUrl.append(temp); queryUrl.append("&"); sprintf(temp, "t=%u", (uint32)std::chrono::seconds(std::time(NULL)).count()); // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do) diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp new file mode 100644 index 00000000..3a0f534a --- /dev/null +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -0,0 +1,522 @@ +#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" + +#include + +#include "config/CemuConfig.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/wxHelper.h" +#include "util/helpers/helpers.h" + +#include "Cafe/OS/libs/nsyshid/nsyshid.h" + +#include "Common/FileStream.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "resource/embedded/resources.h" +#include "EmulatedUSBDeviceFrame.h" + +EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) + : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) +{ + SetIcon(wxICON(X_BOX)); + + auto& config = GetConfig(); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + auto* notebook = new wxNotebook(this, wxID_ANY); + + notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); + notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base")); + + sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); + + SetSizerAndFit(sizer); + Layout(); + Centre(wxBOTH); +} + +EmulatedUSBDeviceFrame::~EmulatedUSBDeviceFrame() {} + +wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panelSizer = new wxBoxSizer(wxVERTICAL); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Skylanders Manager")); + auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulatePortal = + new wxCheckBox(box, wxID_ANY, _("Emulate Skylander Portal")); + m_emulatePortal->SetValue( + GetConfig().emulated_usb_devices.emulate_skylander_portal); + m_emulatePortal->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_skylander_portal = + m_emulatePortal->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); + for (int i = 0; i < nsyshid::MAX_SKYLANDERS; i++) + { + boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2); + } + panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panelSizer); + + return panel; +} + +wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panelSizer = new wxBoxSizer(wxBOTH); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Infinity Manager")); + auto* boxSizer = new wxStaticBoxSizer(box, wxBOTH); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulateBase = + new wxCheckBox(box, wxID_ANY, _("Emulate Infinity Base")); + m_emulateBase->SetValue( + GetConfig().emulated_usb_devices.emulate_infinity_base); + m_emulateBase->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_infinity_base = + m_emulateBase->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulateBase, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Play Set/Power Disc", 0, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Power Disc Two", 1, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Power Disc Three", 2, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player One", 3, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player One Ability One", 4, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player One Ability Two", 5, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player Two", 6, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player Two Ability One", 7, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player Two Ability Two", 8, box), 1, wxEXPAND | wxALL, 2); + + panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panelSizer); + + return panel; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, + wxStaticBox* box) +{ + auto* row = new wxBoxSizer(wxHORIZONTAL); + + row->Add(new wxStaticText(box, wxID_ANY, + fmt::format("{} {}", _("Skylander").ToStdString(), + (rowNumber + 1))), + 1, wxEXPAND | wxALL, 2); + m_skylanderSlots[rowNumber] = + new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + m_skylanderSlots[rowNumber]->SetMinSize(wxSize(150, -1)); + m_skylanderSlots[rowNumber]->Disable(); + row->Add(m_skylanderSlots[rowNumber], 1, wxEXPAND | wxALL, 2); + auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); + loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + LoadSkylander(rowNumber); + }); + auto* createButton = new wxButton(box, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + CreateSkylander(rowNumber); + }); + auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); + clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + ClearSkylander(rowNumber); + }); + row->Add(loadButton, 1, wxEXPAND | wxALL, 2); + row->Add(createButton, 1, wxEXPAND | wxALL, 2); + row->Add(clearButton, 1, wxEXPAND | wxALL, 2); + + return row; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumber, wxStaticBox* box) +{ + auto* row = new wxBoxSizer(wxHORIZONTAL); + + row->Add(new wxStaticText(box, wxID_ANY, name), 1, wxEXPAND | wxALL, 2); + m_infinitySlots[rowNumber] = + new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + m_infinitySlots[rowNumber]->SetMinSize(wxSize(150, -1)); + m_infinitySlots[rowNumber]->Disable(); + row->Add(m_infinitySlots[rowNumber], 1, wxALL | wxEXPAND, 5); + auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); + loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + LoadFigure(rowNumber); + }); + auto* createButton = new wxButton(box, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + CreateFigure(rowNumber); + }); + auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); + clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + ClearFigure(rowNumber); + }); + row->Add(loadButton, 1, wxEXPAND | wxALL, 2); + row->Add(createButton, 1, wxEXPAND | wxALL, 2); + row->Add(clearButton, 1, wxEXPAND | wxALL, 2); + + return row; +} + +void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", + "Skylander files (*.sky;*.bin;*.dump;*.dmp)|*.sky;*.bin;*.dump;*.dmp", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + return; + + LoadSkylanderPath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) +{ + std::unique_ptr skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!skyFile) + { + wxMessageDialog open_error(this, "Error Opening File: " + path.c_str()); + open_error.ShowModal(); + return; + } + + std::array fileData; + if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearSkylander(slot); + + uint16 skyId = uint16(fileData[0x11]) << 8 | uint16(fileData[0x10]); + uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]); + + uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(), + std::move(skyFile)); + m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar); + UpdateSkylanderEdits(); +} + +void EmulatedUSBDeviceFrame::CreateSkylander(uint8 slot) +{ + CreateSkylanderDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadSkylanderPath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearSkylander(uint8 slot) +{ + if (auto slotInfos = m_skySlots[slot]) + { + auto [curSlot, id, var] = slotInfos.value(); + nsyshid::g_skyportal.RemoveSkylander(curSlot); + m_skySlots[slot] = {}; + UpdateSkylanderEdits(); + } +} + +CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) + : wxDialog(parent, wxID_ANY, _("Skylander Figure Creator"), wxDefaultPosition, wxSize(500, 150)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast(0xFFFFFFFF)); + wxArrayString filterlist; + for (const auto& it : nsyshid::g_skyportal.GetListSkylanders()) + { + const uint32 variant = uint32(uint32(it.first.first) << 16) | uint32(it.first.second); + comboBox->Append(it.second, reinterpret_cast(variant)); + filterlist.Add(it.second); + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* idVarRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator validator; + + auto* labelId = new wxStaticText(this, wxID_ANY, "ID:"); + auto* labelVar = new wxStaticText(this, wxID_ANY, "Variant:"); + auto* editId = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + auto* editVar = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + idVarRow->Add(labelId, 1, wxALL, 5); + idVarRow->Add(editId, 1, wxALL, 5); + idVarRow->Add(labelVar, 1, wxALL, 5); + idVarRow->Add(editVar, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editId, editVar, this](wxCommandEvent&) { + long longSkyId; + if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF) + { + wxMessageDialog idError(this, "Error Converting ID!", "ID Entered is Invalid"); + idError.ShowModal(); + return; + } + long longSkyVar; + if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF) + { + wxMessageDialog idError(this, "Error Converting Variant!", "Variant Entered is Invalid"); + idError.ShowModal(); + return; + } + uint16 skyId = longSkyId & 0xFFFF; + uint16 skyVar = longSkyVar & 0xFFFF; + wxString predefName = nsyshid::g_skyportal.FindSkylander(skyId, skyVar) + ".sky"; + wxFileDialog + saveFileDialog(this, _("Create Skylander file"), "", predefName, + "SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + return; + + m_filePath = saveFileDialog.GetPath(); + + if(!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) + { + wxMessageDialog errorMessage(this, "Failed to create file"); + errorMessage.ShowModal(); + this->EndModal(0); + return; + } + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editId, editVar, this](wxCommandEvent&) { + const uint64 sky_info = reinterpret_cast(comboBox->GetClientData(comboBox->GetSelection())); + if (sky_info != 0xFFFFFFFF) + { + const uint16 skyId = sky_info >> 16; + const uint16 skyVar = sky_info & 0xFFFF; + + editId->SetValue(wxString::Format(wxT("%i"), skyId)); + editVar->SetValue(wxString::Format(wxT("%i"), skyVar)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(idVarRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateSkylanderDialog::GetFilePath() const +{ + return m_filePath; +} + +CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot) + : wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast(0xFFFFFF)); + wxArrayString filterlist; + for (const auto& it : nsyshid::g_infinitybase.GetFigureList()) + { + const uint32 figure = it.first; + if ((slot == 0 && + ((figure > 0x1E8480 && figure < 0x2DC6BF) || (figure > 0x3D0900 && figure < 0x4C4B3F))) || + ((slot == 1 || slot == 2) && (figure > 0x3D0900 && figure < 0x4C4B3F)) || + ((slot == 3 || slot == 6) && figure < 0x1E847F) || + ((slot == 4 || slot == 5 || slot == 7 || slot == 8) && + (figure > 0x2DC6C0 && figure < 0x3D08FF))) + { + comboBox->Append(it.second.second, reinterpret_cast(figure)); + filterlist.Add(it.second.second); + } + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* figNumRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator validator; + + auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:"); + auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + figNumRow->Add(labelFigNum, 1, wxALL, 5); + figNumRow->Add(editFigNum, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) { + long longFigNum; + if (!editFigNum->GetValue().ToLong(&longFigNum)) + { + wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); + idError.ShowModal(); + this->EndModal(0); + } + uint32 figNum = longFigNum & 0xFFFFFFFF; + auto figure = nsyshid::g_infinitybase.FindFigure(figNum); + wxString predefName = figure.second + ".bin"; + wxFileDialog + saveFileDialog(this, _("Create Infinity Figure file"), "", predefName, + "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + this->EndModal(0); + + m_filePath = saveFileDialog.GetPath(); + + nsyshid::g_infinitybase.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum, figure.first); + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) { + const uint64 fig_info = reinterpret_cast(comboBox->GetClientData(comboBox->GetSelection())); + if (fig_info != 0xFFFFFF) + { + const uint32 figNum = fig_info & 0xFFFFFFFF; + + editFigNum->SetValue(wxString::Format(wxT("%i"), figNum)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateInfinityFigureDialog::GetFilePath() const +{ + return m_filePath; +} + +void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", + "BIN files (*.bin)|*.bin", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + { + wxMessageDialog errorMessage(this, "File Okay Error"); + errorMessage.ShowModal(); + return; + } + + LoadFigurePath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) +{ + std::unique_ptr infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!infFile) + { + wxMessageDialog errorMessage(this, "File Open Error"); + errorMessage.ShowModal(); + return; + } + + std::array fileData; + if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearFigure(slot); + + uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); + m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); +} + +void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) +{ + cemuLog_log(LogType::Force, "Create Figure: {}", slot); + CreateInfinityFigureDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadFigurePath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) +{ + m_infinitySlots[slot]->ChangeValue("None"); + nsyshid::g_infinitybase.RemoveFigure(slot); +} + +void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() +{ + for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) + { + std::string displayString; + if (auto sd = m_skySlots[i]) + { + auto [portalSlot, skyId, skyVar] = sd.value(); + displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); + } + else + { + displayString = "None"; + } + + m_skylanderSlots[i]->ChangeValue(displayString); + } +} \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h new file mode 100644 index 00000000..ae29a036 --- /dev/null +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include +#include + +#include "Cafe/OS/libs/nsyshid/Infinity.h" +#include "Cafe/OS/libs/nsyshid/Skylander.h" + +class wxBoxSizer; +class wxCheckBox; +class wxFlexGridSizer; +class wxNotebook; +class wxPanel; +class wxStaticBox; +class wxString; +class wxTextCtrl; + +class EmulatedUSBDeviceFrame : public wxFrame { + public: + EmulatedUSBDeviceFrame(wxWindow* parent); + ~EmulatedUSBDeviceFrame(); + + private: + wxCheckBox* m_emulatePortal; + wxCheckBox* m_emulateBase; + std::array m_skylanderSlots; + std::array m_infinitySlots; + std::array>, nsyshid::MAX_SKYLANDERS> m_skySlots; + + wxPanel* AddSkylanderPage(wxNotebook* notebook); + wxPanel* AddInfinityPage(wxNotebook* notebook); + wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); + wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box); + void LoadSkylander(uint8 slot); + void LoadSkylanderPath(uint8 slot, wxString path); + void CreateSkylander(uint8 slot); + void ClearSkylander(uint8 slot); + void LoadFigure(uint8 slot); + void LoadFigurePath(uint8 slot, wxString path); + void CreateFigure(uint8 slot); + void ClearFigure(uint8 slot); + void UpdateSkylanderEdits(); +}; +class CreateSkylanderDialog : public wxDialog { + public: + explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); + wxString GetFilePath() const; + + protected: + wxString m_filePath; +}; + +class CreateInfinityFigureDialog : public wxDialog { + public: + explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); + wxString GetFilePath() const; + + protected: + wxString m_filePath; +}; \ No newline at end of file diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index a67e7bc4..14bf841b 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -32,7 +32,6 @@ #include #include "util/helpers/SystemException.h" #include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h" -#include "config/PermanentStorage.h" #if BOOST_OS_WINDOWS #include @@ -142,47 +141,59 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) second_row->SetFlexibleDirection(wxBOTH); second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + sint32 checkboxCount = 0; + auto CountRowElement = [&]() + { + checkboxCount++; + if(checkboxCount != 2) + return; + second_row->AddSpacer(10); + checkboxCount = 0; + }; + + auto InsertEmptyRow = [&]() + { + while(checkboxCount != 0) + CountRowElement(); + second_row->AddSpacer(10); + second_row->AddSpacer(10); + second_row->AddSpacer(10); + }; + const int topflag = wxALIGN_CENTER_VERTICAL | wxALL; m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position")); m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu")); second_row->Add(m_save_window_position_size, 0, topflag, 5); - second_row->AddSpacer(10); + CountRowElement(); + //second_row->AddSpacer(10); m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position")); m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it")); second_row->Add(m_save_padwindow_position_size, 0, topflag, 5); + CountRowElement(); const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM; m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence")); m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!")); second_row->Add(m_discord_presence, 0, botflag, 5); + CountRowElement(); #ifndef ENABLE_DISCORD_RPC m_discord_presence->Disable(); #endif - second_row->AddSpacer(10); + //second_row->AddSpacer(10); m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar")); m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top")); second_row->Add(m_fullscreen_menubar, 0, botflag, 5); + CountRowElement(); - 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 - if (!std::getenv("APPIMAGE")) { - m_auto_update->Disable(); - } -#endif - second_row->AddSpacer(10); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); second_row->Add(m_save_screenshot, 0, botflag, 5); + CountRowElement(); - m_permanent_storage = new wxCheckBox(box, wxID_ANY, _("Use permanent storage")); - m_permanent_storage->SetToolTip(_("Cemu will remember your custom mlc path in %LOCALAPPDATA%/Cemu for new installations.")); - second_row->Add(m_permanent_storage, 0, botflag, 5); - second_row->AddSpacer(10); m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver")); m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game.")); second_row->Add(m_disable_screensaver, 0, botflag, 5); + CountRowElement(); m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound")); m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines.")); @@ -194,6 +205,7 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); second_row->Add(m_feral_gamemode, 0, botflag, 5); + CountRowElement(); #endif // temporary workaround because feature crashes on macOS @@ -201,6 +213,22 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_disable_screensaver->Enable(false); #endif + // InsertEmptyRow(); + + 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); + CountRowElement(); + + m_receive_untested_releases = new wxCheckBox(box, wxID_ANY, _("Receive untested updates")); + m_receive_untested_releases->SetToolTip(_("When checking for updates, include brand new and untested releases. These may contain bugs!")); + second_row->Add(m_receive_untested_releases, 0, botflag, 5); +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_auto_update->Disable(); + } +#endif + box_sizer->Add(second_row, 0, wxEXPAND, 5); } @@ -208,23 +236,33 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) } { - auto* box = new wxStaticBox(panel, wxID_ANY, _("MLC Path")); - auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL); + auto* outerMlcBox = new wxStaticBox(panel, wxID_ANY, _("Custom MLC path")); - m_mlc_path = new wxTextCtrl(box, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); + auto* box_sizer_mlc = new wxStaticBoxSizer(outerMlcBox, wxVERTICAL); + box_sizer_mlc->Add(new wxStaticText(box_sizer_mlc->GetStaticBox(), wxID_ANY, _("You can configure a custom path for the emulated internal Wii U storage (MLC).\nThis is where Cemu stores saves, accounts and other Wii U system files."), wxDefaultPosition, wxDefaultSize, 0), 0, wxALL, 5); + + auto* mlcPathLineSizer = new wxBoxSizer(wxHORIZONTAL); + + m_mlc_path = new wxTextCtrl(outerMlcBox, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); m_mlc_path->SetMinSize(wxSize(150, -1)); - m_mlc_path->Bind(wxEVT_CHAR, &GeneralSettings2::OnMLCPathChar, this); m_mlc_path->SetToolTip(_("The mlc directory contains your save games and installed game update/dlc data")); - box_sizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); + mlcPathLineSizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); - 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); + auto* changePath = new wxButton(outerMlcBox, wxID_ANY, "Change"); + changePath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); + mlcPathLineSizer->Add(changePath, 0, wxALL, 5); if (LaunchSettings::GetMLCPath().has_value()) - change_path->Disable(); - general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + changePath->Disable(); + + auto* clearPath = new wxButton(outerMlcBox, wxID_ANY, "Clear custom path"); + clearPath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathClear, this); + mlcPathLineSizer->Add(clearPath, 0, wxALL, 5); + if (LaunchSettings::GetMLCPath().has_value() || !ActiveSettings::IsCustomMlcPath()) + clearPath->Disable(); + + box_sizer_mlc->Add(mlcPathLineSizer, 0, wxEXPAND, 5); + general_panel_sizer->Add(box_sizer_mlc, 0, wxEXPAND | wxALL, 5); } { @@ -899,33 +937,10 @@ void GeneralSettings2::StoreConfig() config.fullscreen_menubar = m_fullscreen_menubar->IsChecked(); config.check_update = m_auto_update->IsChecked(); config.save_screenshot = m_save_screenshot->IsChecked(); + config.receive_untested_updates = m_receive_untested_releases->IsChecked(); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) config.feral_gamemode = m_feral_gamemode->IsChecked(); #endif - const bool use_ps = m_permanent_storage->IsChecked(); - if(use_ps) - { - config.permanent_storage = use_ps; - try - { - - PermanentStorage storage; - storage.RemoveStorage(); - } - catch (...) {} - } - else - { - try - { - // delete permanent storage - PermanentStorage storage; - storage.RemoveStorage(); - } - catch (...) {} - config.permanent_storage = use_ps; - } - config.disable_screensaver = m_disable_screensaver->IsChecked(); // Toggle while a game is running if (CafeSystem::IsTitleRunning()) @@ -935,9 +950,6 @@ void GeneralSettings2::StoreConfig() config.play_boot_sound = m_play_boot_sound->IsChecked(); - if (!LaunchSettings::GetMLCPath().has_value()) - config.SetMLCPath(wxHelper::MakeFSPath(m_mlc_path->GetValue()), false); - // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; @@ -1565,9 +1577,9 @@ void GeneralSettings2::ApplyConfig() m_fullscreen_menubar->SetValue(config.fullscreen_menubar); m_auto_update->SetValue(config.check_update); + m_receive_untested_releases->SetValue(config.receive_untested_updates); m_save_screenshot->SetValue(config.save_screenshot); - m_permanent_storage->SetValue(config.permanent_storage); m_disable_screensaver->SetValue(config.disable_screensaver); m_play_boot_sound->SetValue(config.play_boot_sound); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) @@ -1578,6 +1590,7 @@ void GeneralSettings2::ApplyConfig() m_disable_screensaver->SetValue(false); #endif + m_game_paths->Clear(); for (auto& path : config.game_paths) { m_game_paths->Append(to_wxString(path)); @@ -1954,34 +1967,70 @@ void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) void GeneralSettings2::OnMLCPathSelect(wxCommandEvent& event) { - if (!CemuApp::SelectMLCPath(this)) + if(CafeSystem::IsTitleRunning()) + { + wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; - - m_mlc_path->SetValue(wxHelper::FromPath(ActiveSettings::GetMlcPath())); - m_reload_gamelist = true; - m_mlc_modified = true; + } + // show directory dialog + wxDirDialog path_dialog(this, _("Select MLC directory"), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) + return; + // check if the choosen MLC path is an already initialized MLC location + fs::path newMlc = wxHelper::MakeFSPath(path_dialog.GetPath()); + if(CemuApp::CheckMLCPath(newMlc)) + { + // ask user if they are sure they want to use this folder and let them know that accounts and saves wont transfer + wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); + wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); + if(dialog.ShowModal() == wxID_NO) + return; + if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) // creating also acts as a check for read+write access + { + wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + } + else + { + // ask user if they want to create a new mlc structure at the choosen location + wxString message = _("The selected directory does not contain the expected MLC structure. Do you want to create a new MLC structure in this directory?\nNote that changing the MLC location will not transfer any accounts or save files."); + wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); + if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) + { + wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + } + // update MLC path and store any other modified settings + GetConfig().SetMLCPath(newMlc); + StoreConfig(); + wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + // close settings and then cemu + wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); + wxPostEvent(this, closeEvent); + wxPostEvent(GetParent(), closeEvent); } -void GeneralSettings2::OnMLCPathChar(wxKeyEvent& event) +void GeneralSettings2::OnMLCPathClear(wxCommandEvent& event) { - if (LaunchSettings::GetMLCPath().has_value()) - return; - - if(event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK) + if(CafeSystem::IsTitleRunning()) { - fs::path newPath = ""; - if(!CemuApp::TrySelectMLCPath(newPath)) - { - const auto res = wxMessageBox(_("The default MLC path is inaccessible.\nDo you want to select a different path?"), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR); - if (res == wxYES && CemuApp::SelectMLCPath(this)) - newPath = ActiveSettings::GetMlcPath(); - else - return; - } - m_mlc_path->SetValue(wxHelper::FromPath(newPath)); - m_reload_gamelist = true; - m_mlc_modified = true; + wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; } + wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); + wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); + if(dialog.ShowModal() == wxID_NO) + return; + GetConfig().SetMLCPath(""); + StoreConfig(); + g_config.Save(); + wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + // close settings and then cemu + wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); + wxPostEvent(this, closeEvent); + wxPostEvent(GetParent(), closeEvent); } void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index b2d0a3a2..7fbfecc1 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -41,8 +41,7 @@ private: wxCheckBox* m_save_window_position_size; wxCheckBox* m_save_padwindow_position_size; wxCheckBox* m_discord_presence, *m_fullscreen_menubar; - wxCheckBox* m_auto_update, *m_save_screenshot; - wxCheckBox* m_permanent_storage; + wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot; wxCheckBox* m_disable_screensaver; wxCheckBox* m_play_boot_sound; #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) @@ -97,7 +96,7 @@ private: void OnRemovePathClicked(wxCommandEvent& event); void OnActiveAccountChanged(wxCommandEvent& event); void OnMLCPathSelect(wxCommandEvent& event); - void OnMLCPathChar(wxKeyEvent& event); + void OnMLCPathClear(wxCommandEvent& event); void OnShowOnlineValidator(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp index bfd206b1..22426cf2 100644 --- a/src/gui/GettingStartedDialog.cpp +++ b/src/gui/GettingStartedDialog.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "config/ActiveSettings.h" #include "gui/CemuApp.h" @@ -11,7 +12,6 @@ #include "gui/GraphicPacksWindow2.h" #include "gui/input/InputSettings2.h" #include "config/CemuConfig.h" -#include "config/PermanentConfig.h" #include "Cafe/TitleList/TitleList.h" @@ -21,75 +21,100 @@ #include "wxHelper.h" +wxDEFINE_EVENT(EVT_REFRESH_FIRST_PAGE, wxCommandEvent); // used to refresh the first page after the language change + wxPanel* GettingStartedDialog::CreatePage1() { - auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* mainPanel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* page1_sizer = new wxBoxSizer(wxVERTICAL); { auto* sizer = new wxBoxSizer(wxHORIZONTAL); - - sizer->Add(new wxStaticBitmap(result, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - - auto* m_staticText11 = new wxStaticText(result, wxID_ANY, _("It looks like you're starting Cemu for the first time.\nThis quick setup assistant will help you get the best experience"), wxDefaultPosition, wxDefaultSize, 0); - m_staticText11->Wrap(-1); - sizer->Add(m_staticText11, 0, wxALL, 5); - + sizer->Add(new wxStaticBitmap(mainPanel, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_page1.staticText11 = new wxStaticText(mainPanel, wxID_ANY, _("It looks like you're starting Cemu for the first time.\nThis quick setup assistant will help you get the best experience"), wxDefaultPosition, wxDefaultSize, 0); + m_page1.staticText11->Wrap(-1); + sizer->Add(m_page1.staticText11, 0, wxALL, 5); page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } + if(ActiveSettings::IsPortableMode()) { - m_mlc_box_sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("mlc01 path")); - m_mlc_box_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("The mlc path is the root folder of the emulated Wii U internal flash storage. It contains all your saves, installed updates and DLCs.\nIt is strongly recommend that you create a dedicated folder for it (example: C:\\wiiu\\mlc\\) \nIf left empty, the mlc folder will be created inside the Cemu folder.")), 0, wxALL, 5); + m_page1.portableModeInfoText = new wxStaticText(mainPanel, wxID_ANY, _("Cemu is running in portable mode")); + m_page1.portableModeInfoText->Show(true); + page1_sizer->Add(m_page1.portableModeInfoText, 0, wxALL, 5); - m_prev_mlc_warning = new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("A custom mlc path from a previous Cemu installation has been found and filled in.")); - m_prev_mlc_warning->SetForegroundColour(*wxRED); - m_prev_mlc_warning->Show(false); - m_mlc_box_sizer->Add(m_prev_mlc_warning, 0, wxALL, 5); - - auto* mlc_path_sizer = new wxBoxSizer(wxHORIZONTAL); - mlc_path_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("Custom mlc01 path")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - - // workaround since we can't specify our own browse label? >> _("Browse") - m_mlc_folder = new wxDirPickerCtrl(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, wxDIRP_DEFAULT_STYLE); - auto tTest1 = m_mlc_folder->GetTextCtrl(); - if(m_mlc_folder->HasTextCtrl()) - { - m_mlc_folder->GetTextCtrl()->SetEditable(false); - m_mlc_folder->GetTextCtrl()->Bind(wxEVT_CHAR, &GettingStartedDialog::OnMLCPathChar, this); - } - mlc_path_sizer->Add(m_mlc_folder, 1, wxALL, 5); - - mlc_path_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("(optional)")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - - m_mlc_box_sizer->Add(mlc_path_sizer, 0, wxEXPAND, 5); - - page1_sizer->Add(m_mlc_box_sizer, 0, wxALL | wxEXPAND, 5); } + // language selection +#if 0 { - auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Game paths")); + m_page1.languageBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Language")); + m_page1.languageText = new wxStaticText(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, _("Select the language you want to use in Cemu")); + m_page1.languageBoxSizer->Add(m_page1.languageText, 0, wxALL, 5); - sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to locate your games. We recommend creating a dedicated directory in which\nyou place all your Wii U games. (example: C:\\wiiu\\games\\)\n\nYou can also set additional paths in the general settings of Cemu.")), 0, wxALL, 5); + wxString language_choices[] = { _("Default") }; + wxChoice* m_language = new wxChoice(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); + m_language->SetSelection(0); + + for (const auto& language : wxGetApp().GetLanguages()) + { + m_language->Append(language->DescriptionNative); + } + + m_language->SetSelection(0); + m_page1.languageBoxSizer->Add(m_language, 0, wxALL | wxEXPAND, 5); + + page1_sizer->Add(m_page1.languageBoxSizer, 0, wxALL | wxEXPAND, 5); + + m_language->Bind(wxEVT_CHOICE, [this, m_language](const auto&) + { + const auto language = m_language->GetStringSelection(); + auto selection = m_language->GetSelection(); + if (selection == 0) + GetConfig().language = wxLANGUAGE_DEFAULT; + else + { + auto* app = (CemuApp*)wxTheApp; + const auto language = m_language->GetStringSelection(); + for (const auto& lang : app->GetLanguages()) + { + if (lang->DescriptionNative == language) + { + app->LocalizeUI(static_cast(lang->Language)); + wxCommandEvent event(EVT_REFRESH_FIRST_PAGE); + wxPostEvent(this, event); + break; + } + } + } + }); + } +#endif + + { + m_page1.gamePathBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Game paths")); + m_page1.gamePathText = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to automatically locate your games, game updates and DLCs. We recommend creating a dedicated directory in which\nyou place all your Wii U game files. Additional paths can be set later in Cemu's general settings. All common Wii U game formats are supported by Cemu.")); + m_page1.gamePathBoxSizer->Add(m_page1.gamePathText, 0, wxALL, 5); auto* game_path_sizer = new wxBoxSizer(wxHORIZONTAL); - game_path_sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Game path")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_page1.gamePathText2 = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("Game path")); + game_path_sizer->Add(m_page1.gamePathText2, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - m_game_path = new wxDirPickerCtrl(sizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder")); - game_path_sizer->Add(m_game_path, 1, wxALL, 5); + m_page1.gamePathPicker = new wxDirPickerCtrl(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder")); + game_path_sizer->Add(m_page1.gamePathPicker, 1, wxALL, 5); - sizer->Add(game_path_sizer, 0, wxEXPAND, 5); + m_page1.gamePathBoxSizer->Add(game_path_sizer, 0, wxEXPAND, 5); - page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + page1_sizer->Add(m_page1.gamePathBoxSizer, 0, wxALL | wxEXPAND, 5); } { - auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Graphic packs")); + auto* sizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Graphic packs && mods")); - sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the possibility to change resolution, tweak FPS or add other visual or gameplay modifications.\nDownload the community graphic packs to get started.\n")), 0, wxALL, 5); + sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the ability to change resolution, increase FPS, tweak visuals or add gameplay modifications.\nGet started by opening the graphic packs configuration window.\n")), 0, wxALL, 5); - auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download community graphic packs")); - download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnDownloadGPs, this); + auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download and configure graphic packs")); + download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnConfigureGPs, this); sizer->Add(download_gp, 0, wxALIGN_CENTER | wxALL, 5); page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); @@ -102,16 +127,15 @@ wxPanel* GettingStartedDialog::CreatePage1() sizer->SetFlexibleDirection(wxBOTH); sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - auto* next = new wxButton(result, wxID_ANY, _("Next"), wxDefaultPosition, wxDefaultSize, 0); + auto* next = new wxButton(mainPanel, wxID_ANY, _("Next"), wxDefaultPosition, wxDefaultSize, 0); next->Bind(wxEVT_BUTTON, [this](const auto&){m_notebook->SetSelection(1); }); sizer->Add(next, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); page1_sizer->Add(sizer, 1, wxEXPAND, 5); } - - result->SetSizer(page1_sizer); - return result; + mainPanel->SetSizer(page1_sizer); + return mainPanel; } wxPanel* GettingStartedDialog::CreatePage2() @@ -138,17 +162,17 @@ wxPanel* GettingStartedDialog::CreatePage2() option_sizer->SetFlexibleDirection(wxBOTH); option_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); - m_fullscreen = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen")); - option_sizer->Add(m_fullscreen, 0, wxALL, 5); + m_page2.fullscreenCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen")); + option_sizer->Add(m_page2.fullscreenCheckbox, 0, wxALL, 5); - m_separate = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen")); - option_sizer->Add(m_separate, 0, wxALL, 5); + m_page2.separateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen")); + option_sizer->Add(m_page2.separateCheckbox, 0, wxALL, 5); - m_update = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); - option_sizer->Add(m_update, 0, wxALL, 5); + m_page2.updateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); + option_sizer->Add(m_page2.updateCheckbox, 0, wxALL, 5); #if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { - m_update->Disable(); + m_page2.updateCheckbox->Disable(); } #endif sizer->Add(option_sizer, 1, wxEXPAND, 5); @@ -162,10 +186,6 @@ wxPanel* GettingStartedDialog::CreatePage2() sizer->SetFlexibleDirection(wxBOTH); sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - m_dont_show = new wxCheckBox(result, wxID_ANY, _("Don't show this again")); - m_dont_show->SetValue(true); - sizer->Add(m_dont_show, 0, wxALIGN_BOTTOM | wxALL, 5); - auto* previous = new wxButton(result, wxID_ANY, _("Previous")); previous->Bind(wxEVT_BUTTON, [this](const auto&) {m_notebook->SetSelection(0); }); sizer->Add(previous, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); @@ -184,23 +204,9 @@ wxPanel* GettingStartedDialog::CreatePage2() void GettingStartedDialog::ApplySettings() { auto& config = GetConfig(); - m_fullscreen->SetValue(config.fullscreen.GetValue()); - m_update->SetValue(config.check_update.GetValue()); - m_separate->SetValue(config.pad_open.GetValue()); - m_dont_show->SetValue(true); // we want it always enabled by default - m_mlc_folder->SetPath(config.mlc_path.GetValue()); - - try - { - const auto pconfig = PermanentConfig::Load(); - if(!pconfig.custom_mlc_path.empty()) - { - m_mlc_folder->SetPath(wxString::FromUTF8(pconfig.custom_mlc_path)); - m_prev_mlc_warning->Show(true); - } - } - catch (const PSDisabledException&) {} - catch (...) {} + m_page2.fullscreenCheckbox->SetValue(config.fullscreen.GetValue()); + m_page2.updateCheckbox->SetValue(config.check_update.GetValue()); + m_page2.separateCheckbox->SetValue(config.pad_open.GetValue()); } void GettingStartedDialog::UpdateWindowSize() @@ -219,46 +225,25 @@ void GettingStartedDialog::OnClose(wxCloseEvent& event) event.Skip(); auto& config = GetConfig(); - config.fullscreen = m_fullscreen->GetValue(); - config.check_update = m_update->GetValue(); - config.pad_open = m_separate->GetValue(); - config.did_show_graphic_pack_download = m_dont_show->GetValue(); + config.fullscreen = m_page2.fullscreenCheckbox->GetValue(); + config.check_update = m_page2.updateCheckbox->GetValue(); + config.pad_open = m_page2.separateCheckbox->GetValue(); - const fs::path gamePath = wxHelper::MakeFSPath(m_game_path->GetPath()); - if (!gamePath.empty() && fs::exists(gamePath)) + const fs::path gamePath = wxHelper::MakeFSPath(m_page1.gamePathPicker->GetPath()); + std::error_code ec; + if (!gamePath.empty() && fs::exists(gamePath, ec)) { 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(_pathToUtf8(gamePath)); - m_game_path_changed = true; } } - - const fs::path mlcPath = wxHelper::MakeFSPath(m_mlc_folder->GetPath()); - if(config.mlc_path.GetValue() != mlcPath && (mlcPath.empty() || fs::exists(mlcPath))) - { - config.SetMLCPath(mlcPath, false); - m_mlc_changed = true; - } - - g_config.Save(); - - if(m_mlc_changed) - CemuApp::CreateDefaultFiles(); - - CafeTitleList::ClearScanPaths(); - for (auto& it : GetConfig().game_paths) - CafeTitleList::AddScanPath(_utf8ToPath(it)); - CafeTitleList::Refresh(); } - GettingStartedDialog::GettingStartedDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Getting started"), wxDefaultPosition, { 740,530 }, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { - //this->SetSizeHints(wxDefaultSize, { 740,530 }); - auto* sizer = new wxBoxSizer(wxVERTICAL); m_notebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); @@ -274,24 +259,18 @@ GettingStartedDialog::GettingStartedDialog(wxWindow* parent) this->SetSizer(sizer); this->Centre(wxBOTH); this->Bind(wxEVT_CLOSE_WINDOW, &GettingStartedDialog::OnClose, this); - + ApplySettings(); UpdateWindowSize(); } -void GettingStartedDialog::OnDownloadGPs(wxCommandEvent& event) +void GettingStartedDialog::OnConfigureGPs(wxCommandEvent& event) { DownloadGraphicPacksWindow dialog(this); dialog.ShowModal(); - GraphicPacksWindow2::RefreshGraphicPacks(); - - wxMessageDialog ask_dialog(this, _("Do you want to view the downloaded graphic packs?"), _("Graphic packs"), wxCENTRE | wxYES_NO); - if (ask_dialog.ShowModal() == wxID_YES) - { - GraphicPacksWindow2 window(this, 0); - window.ShowModal(); - } + GraphicPacksWindow2 window(this, 0); + window.ShowModal(); } void GettingStartedDialog::OnInputSettings(wxCommandEvent& event) @@ -299,20 +278,3 @@ void GettingStartedDialog::OnInputSettings(wxCommandEvent& event) InputSettings2 dialog(this); dialog.ShowModal(); } - -void GettingStartedDialog::OnMLCPathChar(wxKeyEvent& event) -{ - //if (LaunchSettings::GetMLCPath().has_value()) - // return; - - if (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK) - { - m_mlc_folder->GetTextCtrl()->SetValue(wxEmptyString); - if(m_prev_mlc_warning->IsShown()) - { - m_prev_mlc_warning->Show(false); - UpdateWindowSize(); - } - } -} - diff --git a/src/gui/GettingStartedDialog.h b/src/gui/GettingStartedDialog.h index ec122eab..9dfd69b4 100644 --- a/src/gui/GettingStartedDialog.h +++ b/src/gui/GettingStartedDialog.h @@ -13,9 +13,6 @@ class GettingStartedDialog : public wxDialog public: GettingStartedDialog(wxWindow* parent = nullptr); - [[nodiscard]] bool HasGamePathChanged() const { return m_game_path_changed; } - [[nodiscard]] bool HasMLCChanged() const { return m_mlc_changed; } - private: wxPanel* CreatePage1(); wxPanel* CreatePage2(); @@ -23,22 +20,29 @@ private: void UpdateWindowSize(); void OnClose(wxCloseEvent& event); - void OnDownloadGPs(wxCommandEvent& event); + void OnConfigureGPs(wxCommandEvent& event); void OnInputSettings(wxCommandEvent& event); - void OnMLCPathChar(wxKeyEvent& event); wxSimplebook* m_notebook; - wxCheckBox* m_fullscreen; - wxCheckBox* m_separate; - wxCheckBox* m_update; - wxCheckBox* m_dont_show; - wxStaticBoxSizer* m_mlc_box_sizer; - wxStaticText* m_prev_mlc_warning; - wxDirPickerCtrl* m_mlc_folder; - wxDirPickerCtrl* m_game_path; + struct + { + // header + wxStaticText* staticText11{}; + wxStaticText* portableModeInfoText{}; - bool m_game_path_changed = false; - bool m_mlc_changed = false; + // game path box + wxStaticBoxSizer* gamePathBoxSizer{}; + wxStaticText* gamePathText{}; + wxStaticText* gamePathText2{}; + wxDirPickerCtrl* gamePathPicker{}; + }m_page1; + + struct + { + wxCheckBox* fullscreenCheckbox; + wxCheckBox* separateCheckbox; + wxCheckBox* updateCheckbox; + }m_page2; }; diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index 29f4b865..c49cbeae 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -458,10 +458,10 @@ void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event) m_shown_graphic_pack = gp; - m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 20); m_graphic_pack_name->GetGrandParent()->Layout(); - m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 20); m_graphic_pack_description->GetGrandParent()->Layout(); m_right_panel->FitInside(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 097d506e..c83ab16b 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -12,7 +12,7 @@ #include "audio/audioDebuggerWindow.h" #include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/VulkanCanvas.h" -#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "gui/debugger/DebuggerWindow2.h" #include "util/helpers/helpers.h" @@ -30,6 +30,7 @@ #include "Cafe/Filesystem/FST/FST.h" #include "gui/TitleManager.h" +#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include "Cafe/CafeSystem.h" @@ -110,6 +111,7 @@ enum MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600, MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, + MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, // cpu // cpu->timer speed MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700, @@ -147,8 +149,6 @@ enum // help MAINFRAME_MENU_ID_HELP_ABOUT = 21700, MAINFRAME_MENU_ID_HELP_UPDATE, - MAINFRAME_MENU_ID_HELP_GETTING_STARTED, - // custom MAINFRAME_ID_TIMER1 = 21800, }; @@ -188,6 +188,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput) +EVT_MENU(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, MainWindow::OnToolsInput) // cpu menu EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting) @@ -222,7 +223,6 @@ EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugView // help menu 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) // misc EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_GAMELIST_REFRESH, MainWindow::OnRequestGameListRefresh) @@ -261,7 +261,7 @@ public: return false; uint32 nfcError; std::string path = filenames[0].utf8_string(); - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError)) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError)) { GetConfig().AddRecentNfcFile(path); m_window->UpdateNFCMenu(); @@ -269,10 +269,10 @@ public: } else { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } @@ -415,25 +415,6 @@ wxString MainWindow::GetInitialWindowTitle() return BUILD_VERSION_WITH_NAME_STRING; } -void MainWindow::ShowGettingStartedDialog() -{ - GettingStartedDialog dia(this); - dia.ShowModal(); - if (dia.HasGamePathChanged() || dia.HasMLCChanged()) - m_game_list->ReloadGameEntries(); - - TogglePadView(); - - auto& config = GetConfig(); - m_padViewMenuItem->Check(config.pad_open.GetValue()); - m_fullscreenMenuItem->Check(config.fullscreen.GetValue()); -} - -namespace coreinit -{ - void OSSchedulerEnd(); -}; - void MainWindow::OnClose(wxCloseEvent& event) { wxTheClipboard->Flush(); @@ -749,12 +730,12 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) return; wxString wxStrFilePath = openFileDialog.GetPath(); uint32 nfcError; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { @@ -772,12 +753,12 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) if (!path.empty()) { uint32 nfcError = 0; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { @@ -1515,6 +1496,29 @@ void MainWindow::OnToolsInput(wxCommandEvent& event) }); m_title_manager->Show(); } + break; + } + case MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES: + { + if (m_usb_devices) + { + m_usb_devices->Show(true); + m_usb_devices->Raise(); + m_usb_devices->SetFocus(); + } + else + { + m_usb_devices = new EmulatedUSBDeviceFrame(this); + m_usb_devices->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) + { + if (event.CanVeto()) { + m_usb_devices->Show(false); + event.Veto(); + } + }); + m_usb_devices->Show(true); + } + break; } break; } @@ -2049,11 +2053,6 @@ void MainWindow::OnHelpUpdate(wxCommandEvent& event) test.ShowModal(); } -void MainWindow::OnHelpGettingStarted(wxCommandEvent& event) -{ - ShowGettingStartedDialog(); -} - void MainWindow::RecreateMenu() { if (m_menuBar) @@ -2166,6 +2165,7 @@ void MainWindow::RecreateMenu() m_memorySearcherMenuItem->Enable(false); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); + toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, _("&Emulated USB Devices")); m_menuBar->Append(toolsMenu, _("&Tools")); @@ -2191,25 +2191,35 @@ void MainWindow::RecreateMenu() m_menuBar->Append(nfcMenu, _("&NFC")); m_nfcMenuSeparator0 = nullptr; // debug->logging submenu - wxMenu* debugLoggingMenu = new wxMenu; + wxMenu* debugLoggingMenu = new wxMenu(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::UnsupportedAPI), _("&Unsupported API calls"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::UnsupportedAPI)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::APIErrors), _("&Invalid API usage"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::APIErrors)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitLogging), _("&Coreinit Logging (OSReport/OSConsole)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitLogging)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("&Coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("&Coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("&Coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("&Coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("&PRUDP (for NN FP)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("&Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("&Socket API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("&Save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("&H264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + debugLoggingMenu->AppendSeparator(); + + wxMenu* logCosModulesMenu = new wxMenu(); + logCosModulesMenu->AppendCheckItem(0, _("&Options below are for experts. Leave off if unsure"), wxEmptyString)->Enable(false); + logCosModulesMenu->AppendSeparator(); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("nn_save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("nn_nfp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("nn_fp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("nn_fp PRUDP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("nn_boss API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("nfc API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("ntag API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("nsysnet API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("h264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("gx2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); + + debugLoggingMenu->AppendSubMenu(logCosModulesMenu, _("&CafeOS modules logging")); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache)); @@ -2266,8 +2276,7 @@ void MainWindow::RecreateMenu() if (!std::getenv("APPIMAGE")) { m_check_update_menu->Enable(false); } -#endif - helpMenu->Append(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, _("&Getting started")); +#endif helpMenu->AppendSeparator(); helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu")); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 7191df12..beb86f98 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -22,6 +22,7 @@ struct GameEntry; class DiscordPresence; class TitleManager; class GraphicPacksWindow2; +class EmulatedUSBDeviceFrame; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); @@ -102,7 +103,6 @@ public: void OnAccountSelect(wxCommandEvent& event); void OnConsoleLanguage(wxCommandEvent& event); void OnHelpAbout(wxCommandEvent& event); - void OnHelpGettingStarted(wxCommandEvent& event); void OnHelpUpdate(wxCommandEvent& event); void OnDebugSetting(wxCommandEvent& event); void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); @@ -149,7 +149,6 @@ private: void RecreateMenu(); void UpdateChildWindowTitleRunningState(); static wxString GetInitialWindowTitle(); - void ShowGettingStartedDialog(); bool InstallUpdate(const fs::path& metaFilePath); @@ -164,6 +163,7 @@ private: MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; + EmulatedUSBDeviceFrame* m_usb_devices = nullptr; PadViewFrame* m_padView = nullptr; GraphicPacksWindow2* m_graphic_pack_window = nullptr; diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index 744df323..e7cc5c18 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -20,18 +20,24 @@ extern WindowInfo g_window_info; +#define PAD_MIN_WIDTH 320 +#define PAD_MIN_HEIGHT 180 + PadViewFrame::PadViewFrame(wxFrame* parent) - : wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxSize(854, 480), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS) + : wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxDefaultSize, wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS) { gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_pad, this); - + SetIcon(wxICON(M_WND_ICON128)); wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); - SetMinClientSize({ 320, 180 }); + SetMinClientSize({ PAD_MIN_WIDTH, PAD_MIN_HEIGHT }); SetPosition({ g_window_info.restored_pad_x, g_window_info.restored_pad_y }); - SetSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); + if (g_window_info.restored_pad_width >= PAD_MIN_WIDTH && g_window_info.restored_pad_height >= PAD_MIN_HEIGHT) + SetClientSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); + else + SetClientSize(wxSize(854, 480)); if (g_window_info.pad_maximized) Maximize(); diff --git a/src/gui/debugger/SymbolCtrl.cpp b/src/gui/debugger/SymbolCtrl.cpp index cb1f3b1a..aa862987 100644 --- a/src/gui/debugger/SymbolCtrl.cpp +++ b/src/gui/debugger/SymbolCtrl.cpp @@ -46,25 +46,25 @@ SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxP void SymbolListCtrl::OnGameLoaded() { m_data.clear(); - long itemId = 0; const auto symbol_map = rplSymbolStorage_lockSymbolMap(); for (auto const& [address, symbol_info] : symbol_map) { if (symbol_info == nullptr || symbol_info->symbolName == nullptr) continue; + wxString libNameWX = wxString::FromAscii((const char*)symbol_info->libName); + wxString symbolNameWX = wxString::FromAscii((const char*)symbol_info->symbolName); + wxString searchNameWX = libNameWX + symbolNameWX; + searchNameWX.MakeLower(); + auto new_entry = m_data.try_emplace( symbol_info->address, - (char*)(symbol_info->symbolName), - (char*)(symbol_info->libName), - "", + symbolNameWX, + libNameWX, + searchNameWX, false ); - new_entry.first->second.searchName += new_entry.first->second.name; - new_entry.first->second.searchName += new_entry.first->second.libName; - new_entry.first->second.searchName.MakeLower(); - if (m_list_filter.IsEmpty()) new_entry.first->second.visible = true; else if (new_entry.first->second.searchName.Contains(m_list_filter)) diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index dfbaf76e..f4e5b7af 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -277,12 +277,10 @@ void DebugPPCThreadsWindow::RefreshThreadList() m_thread_list->SetScrollPos(0, scrollPos, true); } -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread) { 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])); + DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]), true); } void DebugPPCThreadsWindow::PresentProfileResults(OSThread_t* thread, const std::unordered_map& samples) diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index d928e46c..64b238fc 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -472,15 +472,12 @@ bool InputManager::save(size_t player_index, std::string_view filename) emulated_controller->type_string() }.c_str()); + if(!is_default_file) + emulated_controller->m_profile_name = std::string{filename}; + if (emulated_controller->has_profile_name()) emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( emulated_controller->get_profile_name().c_str()); - else if (!is_default_file) - { - emulated_controller->m_profile_name = std::string{filename}; - emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( - emulated_controller->get_profile_name().c_str()); - } // custom settings emulated_controller->save(emulated_controller_node); diff --git a/src/main.cpp b/src/main.cpp index 1ccc2805..ea1df684 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,6 @@ #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" -#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/CemuConfig.h" @@ -160,7 +159,7 @@ void ExpressionParser_test(); void FSTVolumeTest(); void CRCTest(); -void unitTests() +void UnitTests() { ExpressionParser_test(); gx2CopySurfaceTest(); @@ -169,17 +168,6 @@ void unitTests() CRCTest(); } -int mainEmulatorHLE() -{ - LatteOverlay_init(); - // run a couple of tests if in non-release mode -#ifdef CEMU_DEBUG_ASSERT - unitTests(); -#endif - CemuCommonInit(); - return 0; -} - bool isConsoleConnected = false; void requireConsole() { diff --git a/src/resource/cemu.rc b/src/resource/cemu.rc index 860ca8fb..6f78bfc3 100644 --- a/src/resource/cemu.rc +++ b/src/resource/cemu.rc @@ -73,8 +73,8 @@ END #define str(s) #s VS_VERSION_INFO VERSIONINFO -FILEVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0 -PRODUCTVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0 +FILEVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 +PRODUCTVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -94,7 +94,7 @@ BEGIN VALUE "LegalCopyright", "Team Cemu" VALUE "OriginalFilename", "Cemu.exe" VALUE "ProductName", "Cemu" - VALUE "ProductVersion", xstr(EMULATOR_VERSION_LEAD) "." xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX "\0" + VALUE "ProductVersion", xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) "." xstr(EMULATOR_VERSION_PATCH) EMULATOR_VERSION_SUFFIX "\0" END END BLOCK "VarFileInfo" diff --git a/src/resource/embedded/fontawesome.S b/src/resource/embedded/fontawesome.S index 04da3b24..db23e7ae 100644 --- a/src/resource/embedded/fontawesome.S +++ b/src/resource/embedded/fontawesome.S @@ -1,4 +1,4 @@ -.section .text +.section .rodata,"",%progbits .global g_fontawesome_data, g_fontawesome_size g_fontawesome_data: @@ -6,3 +6,4 @@ g_fontawesome_data: g_fontawesome_size: .int g_fontawesome_size - g_fontawesome_data +.section .note.GNU-stack,"",%progbits \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index b27a7095..0a46e32e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "cemu", "version-string": "1.0", - "builtin-baseline": "cbf4a6641528cee6f172328984576f51698de726", + "builtin-baseline": "a4275b7eee79fb24ec2e135481ef5fce8b41c339", "dependencies": [ "pugixml", "zlib", @@ -44,6 +44,22 @@ "default-features": false, "features": [ "openssl" ] }, + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "tiff", + "default-features": false, + "features": ["jpeg", "zip"] + }, "libusb" + ], + "overrides": [ + { + "name": "sdl2", + "version": "2.30.3" + } ] }