Merge remote-tracking branch 'base/main' into macos-arm64

temporarily reverted CI
This commit is contained in:
Exverge 2024-11-11 14:19:18 -05:00
commit 7120ec92c8
92 changed files with 3025 additions and 759 deletions

View file

@ -15,6 +15,7 @@ BinPackArguments: true
BinPackParameters: true BinPackParameters: true
BraceWrapping: BraceWrapping:
AfterCaseLabel: true AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always AfterControlStatement: Always
AfterEnum: true AfterEnum: true
AfterExternBlock: true AfterExternBlock: true

View file

@ -1,9 +0,0 @@
#include <stdio.h>
#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;
}

View file

@ -3,10 +3,10 @@ name: Build Cemu
on: on:
workflow_call: workflow_call:
inputs: inputs:
deploymode: next_version_major:
required: false required: false
type: string type: string
experimentalversion: next_version_minor:
required: false required: false
type: string type: string
@ -24,30 +24,17 @@ jobs:
submodules: "recursive" submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
- name: "Fetch full history for vcpkg submodule" - name: Setup release mode parameters
run: |
cd dependencies/vcpkg
git fetch --unshallow
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
run: | run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release" echo "Build mode is release"
- name: Setup debug mode parameters (for continous build) - name: Setup build flags for version
if: ${{ inputs.deploymode != 'release' }} if: ${{ inputs.next_version_major != '' }}
run: | run: |
echo "BUILD_MODE=debug" >> $GITHUB_ENV echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $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
- name: "Install system dependencies" - name: "Install system dependencies"
run: | run: |
@ -86,12 +73,10 @@ jobs:
cmake --build build cmake --build build
- name: Prepare artifact - name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: mv bin/Cemu_release bin/Cemu run: mv bin/Cemu_release bin/Cemu
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with: with:
name: cemu-bin-linux-x64 name: cemu-bin-linux-x64
path: ./bin/Cemu path: ./bin/Cemu
@ -133,29 +118,17 @@ jobs:
with: with:
submodules: "recursive" submodules: "recursive"
- name: "Fetch full history for vcpkg submodule" - name: Setup release mode parameters
run: |
cd dependencies/vcpkg
git fetch --unshallow
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
run: | run: |
echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 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_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is release" echo "Build mode is release"
- name: Setup debug mode parameters (for continous build) - name: Setup build flags for version
if: ${{ inputs.deploymode != 'release' }} if: ${{ inputs.next_version_major != '' }}
run: | run: |
echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 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
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
- name: "Setup cmake" - name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2 uses: jwlawson/actions-setup-cmake@v2
@ -194,61 +167,50 @@ jobs:
cmake --build . --config ${{ env.BUILD_MODE }} cmake --build . --config ${{ env.BUILD_MODE }}
- name: Prepare artifact - name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: Rename-Item bin/Cemu_release.exe Cemu.exe run: Rename-Item bin/Cemu_release.exe Cemu.exe
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with: with:
name: cemu-bin-windows-x64 name: cemu-bin-windows-x64
path: ./bin/Cemu.exe path: ./bin/Cemu.exe
build-macos-intel: build-macos:
runs-on: macos-12 runs-on: macos-14
steps: steps:
- name: "Checkout repo" - name: "Checkout repo"
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: "recursive" submodules: "recursive"
- name: "Fetch full history for vcpkg submodule" - name: Setup release mode parameters
run: |
cd dependencies/vcpkg
git fetch --unshallow
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
run: | run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release" 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: | run: |
echo "BUILD_MODE=debug" >> $GITHUB_ENV echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $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
- name: "Install system dependencies" - name: "Install system dependencies"
run: | run: |
brew update brew update
brew install llvm@15 ninja nasm automake libtool cmake python3 ninja brew install ninja nasm automake libtool
- name: "Build and install molten-vk" - name: "Install molten-vk"
run: | run: |
git clone https://github.com/KhronosGroup/MoltenVK.git curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar
cd MoltenVK tar xf MoltenVK-macos.tar
git checkout bf097edc74ec3b6dfafdcd5a38d3ce14b11952d6 sudo mkdir -p /usr/local/lib
./fetchDependencies --macos sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib
make macos
make install - name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
- name: "Bootstrap vcpkg" - name: "Bootstrap vcpkg"
run: | run: |
@ -274,9 +236,8 @@ jobs:
cd build cd build
cmake .. ${{ env.BUILD_FLAGS }} \ cmake .. ${{ env.BUILD_FLAGS }} \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DMACOS_BUNDLE=ON \ -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 -G Ninja
- name: "Build Cemu" - name: "Build Cemu"
@ -284,7 +245,6 @@ jobs:
cmake --build build cmake --build build
- name: Prepare artifact - name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: | run: |
mkdir bin/Cemu_app mkdir bin/Cemu_app
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
@ -298,105 +258,6 @@ jobs:
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with: with:
name: cemu-bin-macos-x64 name: cemu-bin-macos-x64
path: ./bin/Cemu.dmg path: ./bin/Cemu.dmg
build-macos-arm64:
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' }}
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' }}
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
- name: "Install system dependencies"
run: |
brew update
brew install ninja automake libtool cmake ninja python3
- name: "Build and install molten-vk"
run: |
git clone https://github.com/KhronosGroup/MoltenVK.git
cd MoltenVK
git checkout bf097edc74ec3b6dfafdcd5a38d3ce14b11952d6
./fetchDependencies --macos
make macos
sudo make install
cp /usr/local/lib/libMoltenVK.dylib /opt/homebrew/lib
- name: "Bootstrap vcpkg"
run: |
bash ./dependencies/vcpkg/bootstrap-vcpkg.sh
- name: 'Setup NuGet Credentials for vcpkg'
shell: 'bash'
run: |
mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \
sources add \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
-storepasswordincleartext \
-name "GitHub" \
-username "${{ github.repository_owner }}" \
-password "${{ secrets.GITHUB_TOKEN }}"
mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \
setapikey "${{ secrets.GITHUB_TOKEN }}" \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
- name: "cmake"
run: |
mkdir build
cd build
cmake .. ${{ env.BUILD_FLAGS }} \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
-DMACOS_BUNDLE=ON \
-G Ninja
- name: "Build Cemu"
run: |
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
mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh}
ln -s /Applications bin/Cemu_app/Applications
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg
rm bin/tmp.dmg
- name: Upload artifact
uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-macos-arm64
path: ./bin/Cemu.dmg

View file

@ -16,6 +16,3 @@ on:
jobs: jobs:
build: build:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with:
deploymode: release
experimentalversion: 999999

View file

@ -1,20 +1,83 @@
name: Deploy experimental release name: Deploy experimental release
on: on:
workflow_dispatch: 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: jobs:
calculate-version:
name: Calculate Version
uses: ./.github/workflows/determine_release_version.yml
call-release-build: call-release-build:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
needs: calculate-version
with: with:
deploymode: release next_version_major: ${{ needs.calculate-version.outputs.next_version_major }}
experimentalversion: ${{ github.run_number }} next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }}
deploy: deploy:
name: Deploy experimental release name: Deploy experimental release
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: call-release-build needs: [call-release-build, calculate-version]
steps: 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 - uses: actions/download-artifact@v4
with: with:
name: cemu-bin-linux-x64 name: cemu-bin-linux-x64
@ -40,15 +103,13 @@ jobs:
mkdir upload mkdir upload
sudo apt install zip sudo apt install zip
- name: Get version - name: Set version dependent vars
run: | run: |
echo "Experimental version: ${{ github.run_number }}" echo "Version: ${{ needs.calculate-version.outputs.next_version }}"
ls echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}"
gcc -o getversion .github/getversion.cpp echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}"
./getversion echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
echo "Cemu CI version: $(./getversion)" echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
echo "CEMU_VERSION=$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
- name: Create release from windows-bin - name: Create release from windows-bin
run: | 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 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 tar xvzf ghr.tar.gz; rm ghr.tar.gz
echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}" 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

View file

@ -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"

4
.gitmodules vendored
View file

@ -9,10 +9,12 @@
[submodule "dependencies/vcpkg"] [submodule "dependencies/vcpkg"]
path = dependencies/vcpkg path = dependencies/vcpkg
url = https://github.com/microsoft/vcpkg url = https://github.com/microsoft/vcpkg
shallow = true shallow = false
[submodule "dependencies/Vulkan-Headers"] [submodule "dependencies/Vulkan-Headers"]
path = dependencies/Vulkan-Headers path = dependencies/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers
shallow = true
[submodule "dependencies/imgui"] [submodule "dependencies/imgui"]
path = dependencies/imgui path = dependencies/imgui
url = https://github.com/ocornut/imgui url = https://github.com/ocornut/imgui
shallow = true

View file

@ -16,11 +16,11 @@
- [Compiling Errors](#compiling-errors) - [Compiling Errors](#compiling-errors)
- [Building Errors](#building-errors) - [Building Errors](#building-errors)
- [macOS](#macos) - [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 brew](#installing-brew)
- [Installing Dependencies](#installing-dependencies) - [Installing Tool Dependencies](#installing-tool-dependencies)
- [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang) - [Installing Library Dependencies](#installing-library-dependencies)
- [Updating Cemu and source code](#updating-cemu-and-source-code) - [Build Cemu using CMake](#build-cemu-using-cmake)
- [Updating Cemu and source code](#updating-cemu-and-source-code)
## Windows ## Windows
@ -141,31 +141,41 @@ If you are getting a different error than any of the errors listed above, you ma
## macOS ## macOS
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below
below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that
API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't
Molten-VK compatibility layer supported on macOS, so 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
### Installing brew ### Installing brew
1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 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` 1. `git clone --recursive https://github.com/cemu-project/Cemu`
2. `cd 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` 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`. 5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.

View file

@ -2,20 +2,39 @@ cmake_minimum_required(VERSION 3.21.1)
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) 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) 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) # used by CI script to set version:
add_definitions(-DEMULATOR_VERSION_MINOR=${EXPERIMENTAL_VERSION}) set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
execute_process( set(EMULATOR_VERSION_MINOR "0" CACHE STRING "")
COMMAND git log --format=%h -1 set(EMULATOR_VERSION_PATCH "0" CACHE STRING "")
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH execute_process(
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND git log --format=%h -1
) WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
add_definitions(-DEMULATOR_HASH=${GIT_HASH}) OUTPUT_VARIABLE GIT_HASH
endif() OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
if (ENABLE_VCPKG) 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) if(UNIX AND NOT APPLE)
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux") set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux")
elseif(APPLE) elseif(APPLE)
@ -44,6 +63,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_definitions($<$<CONFIG:Debug>:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT add_compile_definitions($<$<CONFIG:Debug>: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) set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# enable link time optimization for release builds # enable link time optimization for release builds
@ -69,6 +92,7 @@ endif()
if (APPLE) if (APPLE)
enable_language(OBJC OBJCXX) enable_language(OBJC OBJCXX)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
endif() endif()
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
@ -198,7 +222,7 @@ if (ENABLE_CUBEB)
option(BUILD_TOOLS "" OFF) option(BUILD_TOOLS "" OFF)
option(BUNDLE_SPEEX "" OFF) option(BUNDLE_SPEEX "" OFF)
set(USE_WINMM OFF CACHE BOOL "") set(USE_WINMM OFF CACHE BOOL "")
add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL) add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL SYSTEM)
set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_library(cubeb::cubeb ALIAS cubeb) add_library(cubeb::cubeb ALIAS cubeb)
endif() endif()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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.

View file

@ -117,7 +117,13 @@ add_library (ih264d
"decoder/ivd.h" "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") set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86")
include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES}) include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES})
target_sources(ih264d PRIVATE target_sources(ih264d PRIVATE
@ -140,7 +146,7 @@ target_sources(ih264d PRIVATE
"decoder/x86/ih264d_function_selector_sse42.c" "decoder/x86/ih264d_function_selector_sse42.c"
"decoder/x86/ih264d_function_selector_ssse3.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 ) enable_language( C CXX ASM )
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm") set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES}) include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
@ -178,7 +184,7 @@ target_sources(ih264d PRIVATE
) )
target_compile_options(ih264d PRIVATE -DARMV8) target_compile_options(ih264d PRIVATE -DARMV8)
else() else()
message(FATAL_ERROR "ih264d unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}") message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}")
endif() endif()
if(MSVC) if(MSVC)

View file

@ -10,6 +10,8 @@ curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/re
chmod a+x mkappimage.AppImage chmod a+x mkappimage.AppImage
curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh" curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
chmod a+x 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 if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then
sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh 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 \ -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
-i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \ -i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \
-e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \ -e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \
--plugin gtk --plugin gtk \
--plugin checkrt
if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then
GITVERSION=experimental GITVERSION=experimental
@ -47,7 +50,6 @@ fi
echo "Cemu Version Cemu-${GITVERSION}" echo "Cemu Version Cemu-${GITVERSION}"
rm AppDir/usr/lib/libwayland-client.so.0 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 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 VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir

View file

@ -101,12 +101,18 @@ if (MACOS_BUNDLE)
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}") COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
endforeach(folder) endforeach(folder)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/debug/lib/libusb-1.0.0.dylib")
else()
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib")
endif()
add_custom_command (TARGET CemuBin POST_BUILD add_custom_command (TARGET CemuBin POST_BUILD
COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib"
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${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 ${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 -add_rpath @executable_path/../Frameworks ${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}") COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
endif() endif()
set_target_properties(CemuBin PROPERTIES set_target_properties(CemuBin PROPERTIES

View file

@ -465,6 +465,8 @@ add_library(CemuCafe
OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendLibusb.h
OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.cpp
OS/libs/nsyshid/BackendWindowsHID.h OS/libs/nsyshid/BackendWindowsHID.h
OS/libs/nsyshid/Dimensions.cpp
OS/libs/nsyshid/Dimensions.h
OS/libs/nsyshid/Infinity.cpp OS/libs/nsyshid/Infinity.cpp
OS/libs/nsyshid/Infinity.h OS/libs/nsyshid/Infinity.h
OS/libs/nsyshid/Skylander.cpp OS/libs/nsyshid/Skylander.cpp

View file

@ -396,7 +396,7 @@ void cemu_initForGame()
// replace any known function signatures with our HLE implementations and patch bugs in the games // replace any known function signatures with our HLE implementations and patch bugs in the games
GamePatch_scan(); GamePatch_scan();
} }
LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled(); LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled();
InfoLog_PrintActiveSettings(); InfoLog_PrintActiveSettings();
Latte_Start(); Latte_Start();
// check for debugger entrypoint bp // check for debugger entrypoint bp

View file

@ -3,6 +3,8 @@
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include "../fsc.h"
// path parser and utility class for Wii U paths // path parser and utility class for Wii U paths
// optimized to be allocation-free for common path lengths // optimized to be allocation-free for common path lengths
class FSCPath class FSCPath
@ -119,9 +121,7 @@ public:
template<typename F> template<typename F>
class FSAFileTree class FSAFileTree
{ {
public: private:
private:
enum NODETYPE : uint8 enum NODETYPE : uint8
{ {
@ -133,6 +133,7 @@ private:
{ {
std::string name; std::string name;
std::vector<node_t*> subnodes; std::vector<node_t*> subnodes;
size_t fileSize;
F* custom; F* custom;
NODETYPE type; NODETYPE type;
}; };
@ -179,13 +180,54 @@ private:
return newNode; 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: public:
FSAFileTree() FSAFileTree()
{ {
rootNode.type = NODETYPE_DIRECTORY; 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); FSCPath p(path);
if (p.GetNodeCount() == 0) if (p.GetNodeCount() == 0)
@ -196,6 +238,7 @@ public:
return false; // node already exists return false; // node already exists
// add file node // add file node
node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1)); node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1));
fileNode->fileSize = fileSize;
fileNode->custom = custom; fileNode->custom = custom;
return true; return true;
} }
@ -214,6 +257,20 @@ public:
return true; 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) bool removeFile(std::string_view path)
{ {
FSCPath p(path); FSCPath p(path);

View file

@ -212,4 +212,4 @@ bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTarg
// redirect device // redirect device
void fscDeviceRedirect_map(); 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);

View file

@ -11,7 +11,7 @@ struct RedirectEntry
FSAFileTree<RedirectEntry> redirectTree; FSAFileTree<RedirectEntry> 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 // check if source already has a redirection
RedirectEntry* existingEntry; RedirectEntry* existingEntry;
@ -24,7 +24,7 @@ void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& t
delete existingEntry; delete existingEntry;
} }
RedirectEntry* entry = new RedirectEntry(targetFilePath, priority); RedirectEntry* entry = new RedirectEntry(targetFilePath, priority);
redirectTree.addFile(virtualSourcePath, entry); redirectTree.addFile(virtualSourcePath, fileSize, entry);
} }
class fscDeviceTypeRedirect : public fscDeviceC 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 FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
{ {
RedirectEntry* redirectionEntry; 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); 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; return nullptr;
} }

View file

@ -830,7 +830,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC
{ {
virtualMountPath = fs::path("vol/content/") / virtualMountPath; 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);
} }
} }
} }

View file

@ -52,7 +52,7 @@ struct LatteGPUState_t
uint32 gx2InitCalled; // incremented every time GX2Init() is called uint32 gx2InitCalled; // incremented every time GX2Init() is called
// OpenGL control // OpenGL control
uint32 glVendor; // GLVENDOR_* uint32 glVendor; // GLVENDOR_*
bool alwaysDisplayDRC = false; bool isDRCPrimary = false;
// temporary (replace with proper solution later) // temporary (replace with proper solution later)
bool tvBufferUsesSRGB; bool tvBufferUsesSRGB;
bool drcBufferUsesSRGB; bool drcBufferUsesSRGB;

View file

@ -989,8 +989,6 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
g_renderer->ImguiEnd(); g_renderer->ImguiEnd();
} }
bool ctrlTabHotkeyPressed = false;
void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget) void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget)
{ {
cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice
@ -1000,38 +998,31 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
return; return;
} }
auto getVPADScreenActive = [](size_t n) -> std::pair<bool, bool> {
auto controller = InputManager::instance().get_vpad_controller(n);
if (!controller)
return {false,false};
auto pressed = controller->is_screen_active();
auto toggle = controller->is_screen_active_toggle();
return {pressed && !toggle, pressed && toggle};
};
const bool tabPressed = gui_isKeyDown(PlatformKeyCodes::TAB); const bool tabPressed = gui_isKeyDown(PlatformKeyCodes::TAB);
const bool ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL); const bool ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL);
const auto [vpad0Active, vpad0Toggle] = getVPADScreenActive(0);
const auto [vpad1Active, vpad1Toggle] = getVPADScreenActive(1);
bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed; const bool altScreenRequested = (!ctrlPressed && tabPressed) || vpad0Active || vpad1Active;
bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC; const bool togglePressed = (ctrlPressed && tabPressed) || vpad0Toggle || vpad1Toggle;
static bool togglePressedLast = false;
if (ctrlPressed && tabPressed) bool& isDRCPrimary = LatteGPUState.isDRCPrimary;
{
if (ctrlTabHotkeyPressed == false)
{
alwaysDisplayDRC = !alwaysDisplayDRC;
ctrlTabHotkeyPressed = true;
}
}
else
ctrlTabHotkeyPressed = false;
if (alwaysDisplayDRC) if(togglePressed && !togglePressedLast)
showDRC = !tabPressed; isDRCPrimary = !isDRCPrimary;
togglePressedLast = togglePressed;
if (!showDRC) bool showDRC = swkbd_hasKeyboardInputHook() == false && (isDRCPrimary ^ altScreenRequested);
{
auto controller = InputManager::instance().get_vpad_controller(0);
if (controller && controller->is_screen_active())
showDRC = true;
if (!showDRC)
{
controller = InputManager::instance().get_vpad_controller(1);
if (controller && controller->is_screen_active())
showDRC = true;
}
}
if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive()) if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive())
LatteRenderTarget_copyToBackbuffer(texView, true); LatteRenderTarget_copyToBackbuffer(texView, true);

View file

@ -524,7 +524,7 @@ void LatteSHRC_UpdateGSBaseHash(uint8* geometryShaderPtr, uint32 geometryShaderS
// update hash from geometry shader data // update hash from geometry shader data
uint64 gsHash1 = 0; uint64 gsHash1 = 0;
uint64 gsHash2 = 0; uint64 gsHash2 = 0;
_calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheVS, &gsHash1, &gsHash2); _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheGS, &gsHash1, &gsHash2);
// get geometry shader // get geometry shader
uint64 gsHash = gsHash1 + gsHash2; uint64 gsHash = gsHash1 + gsHash2;
gsHash += (uint64)_activeVertexShader->ringParameterCount; gsHash += (uint64)_activeVertexShader->ringParameterCount;

View file

@ -12,9 +12,9 @@ uint32 RendererShader::GeneratePrecompiledCacheId()
v += (uint32)(*s); v += (uint32)(*s);
s++; s++;
} }
v += (EMULATOR_VERSION_LEAD * 1000000u); v += (EMULATOR_VERSION_MAJOR * 1000000u);
v += (EMULATOR_VERSION_MAJOR * 10000u); v += (EMULATOR_VERSION_MINOR * 10000u);
v += (EMULATOR_VERSION_MINOR * 100u); v += (EMULATOR_VERSION_PATCH * 100u);
// settings that can influence shaders // settings that can influence shaders
v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133; v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133;

View file

@ -146,8 +146,17 @@ void SwapchainInfoVk::Create()
UnrecoverableError("Failed to create semaphore for swapchain acquire"); 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; m_acquireIndex = 0;
hasDefinedSwapchainImage = false; hasDefinedSwapchainImage = false;
m_queueDepth = 0;
} }
void SwapchainInfoVk::Cleanup() void SwapchainInfoVk::Cleanup()
@ -177,6 +186,12 @@ void SwapchainInfoVk::Cleanup()
m_swapchainFramebuffers.clear(); m_swapchainFramebuffers.clear();
if (m_imageAvailableFence)
{
WaitAvailableFence();
vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr);
m_imageAvailableFence = nullptr;
}
if (m_swapchain) if (m_swapchain)
{ {
vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr);
@ -189,6 +204,18 @@ bool SwapchainInfoVk::IsValid() const
return m_swapchain && !m_acquireSemaphores.empty(); 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 SwapchainInfoVk::ConsumeAcquireSemaphore()
{ {
VkSemaphore ret = m_currentSemaphore; VkSemaphore ret = m_currentSemaphore;
@ -198,8 +225,10 @@ VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore()
bool SwapchainInfoVk::AcquireImage() bool SwapchainInfoVk::AcquireImage()
{ {
ResetAvailableFence();
VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; 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) if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
m_shouldRecreate = true; m_shouldRecreate = true;
if (result == VK_TIMEOUT) if (result == VK_TIMEOUT)
@ -216,6 +245,7 @@ bool SwapchainInfoVk::AcquireImage()
return false; return false;
} }
m_currentSemaphore = acquireSemaphore; m_currentSemaphore = acquireSemaphore;
m_awaitableFence = m_imageAvailableFence;
m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size();
return true; return true;
@ -319,6 +349,7 @@ VkExtent2D SwapchainInfoVk::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& cap
VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes) VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes)
{ {
m_maxQueued = 0;
const auto vsyncState = (VSync)GetConfig().vsync.GetValue(); const auto vsyncState = (VSync)GetConfig().vsync.GetValue();
if (vsyncState == VSync::MAILBOX) if (vsyncState == VSync::MAILBOX)
{ {
@ -345,6 +376,7 @@ VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector<VkPresentM
return VK_PRESENT_MODE_FIFO_KHR; return VK_PRESENT_MODE_FIFO_KHR;
} }
m_maxQueued = 1;
return VK_PRESENT_MODE_FIFO_KHR; return VK_PRESENT_MODE_FIFO_KHR;
} }

View file

@ -26,6 +26,9 @@ struct SwapchainInfoVk
bool IsValid() const; bool IsValid() const;
void WaitAvailableFence();
void ResetAvailableFence() const;
bool AcquireImage(); bool AcquireImage();
// retrieve semaphore of last acquire for submitting a wait operation // retrieve semaphore of last acquire for submitting a wait operation
// only one wait operation must be submitted per acquire (which submits a single signal operation) // only one wait operation must be submitted per acquire (which submits a single signal operation)
@ -68,6 +71,9 @@ struct SwapchainInfoVk
VkSwapchainKHR m_swapchain{}; VkSwapchainKHR m_swapchain{};
Vector2i m_desiredExtent{}; Vector2i m_desiredExtent{};
uint32 swapchainImageIndex = (uint32)-1; uint32 swapchainImageIndex = (uint32)-1;
uint64 m_presentId = 1;
uint64 m_queueDepth = 0; // number of frames with pending presentation requests
uint64 m_maxQueued = 0; // the maximum number of frames with presentation requests.
// swapchain image ringbuffer (indexed by swapchainImageIndex) // swapchain image ringbuffer (indexed by swapchainImageIndex)
@ -81,6 +87,8 @@ struct SwapchainInfoVk
private: private:
uint32 m_acquireIndex = 0; uint32 m_acquireIndex = 0;
std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex
VkFence m_imageAvailableFence{};
VkFence m_awaitableFence = VK_NULL_HANDLE;
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
std::array<uint32, 2> m_swapchainQueueFamilyIndices; std::array<uint32, 2> m_swapchainQueueFamilyIndices;

View file

@ -188,6 +188,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR);
VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdBeginRenderingKHR);
VKFUNC_DEVICE(vkCmdEndRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR);
// khr_present_wait
VKFUNC_DEVICE(vkWaitForPresentKHR);
// transform feedback extension // transform feedback extension
VKFUNC_DEVICE(vkCmdBindTransformFeedbackBuffersEXT); VKFUNC_DEVICE(vkCmdBindTransformFeedbackBuffersEXT);
VKFUNC_DEVICE(vkCmdBeginTransformFeedbackEXT); VKFUNC_DEVICE(vkCmdBeginTransformFeedbackEXT);

View file

@ -826,7 +826,7 @@ void PipelineCompiler::InitDepthStencilState()
depthStencilState.front.reference = stencilRefFront; depthStencilState.front.reference = stencilRefFront;
depthStencilState.front.compareMask = stencilCompareMaskFront; depthStencilState.front.compareMask = stencilCompareMaskFront;
depthStencilState.front.writeMask = stencilWriteMaskBack; depthStencilState.front.writeMask = stencilWriteMaskFront;
depthStencilState.front.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; depthStencilState.front.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc];
depthStencilState.front.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; depthStencilState.front.depthFailOp = stencilOpTable[(size_t)frontStencilZFail];
depthStencilState.front.failOp = stencilOpTable[(size_t)frontStencilFail]; depthStencilState.front.failOp = stencilOpTable[(size_t)frontStencilFail];

View file

@ -7,6 +7,7 @@
#include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h"
@ -29,6 +30,7 @@
#include <glslang/Public/ShaderLang.h> #include <glslang/Public/ShaderLang.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/intl.h> // for localization
#ifndef VK_API_VERSION_MAJOR #ifndef VK_API_VERSION_MAJOR
#define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU) #define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU)
@ -45,7 +47,9 @@ const std::vector<const char*> kOptionalDeviceExtensions =
VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet
VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME,
VK_KHR_SYNCHRONIZATION_2_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<const char*> kRequiredDeviceExtensions = const std::vector<const char*> kRequiredDeviceExtensions =
@ -123,7 +127,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
VkApplicationInfo app_info{}; VkApplicationInfo app_info{};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = EMULATOR_NAME; 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.pEngineName = EMULATOR_NAME;
app_info.engineVersion = app_info.applicationVersion; app_info.engineVersion = app_info.applicationVersion;
app_info.apiVersion = apiVersion; app_info.apiVersion = apiVersion;
@ -250,12 +254,24 @@ void VulkanRenderer::GetDeviceFeatures()
pcc.pNext = prevStruct; pcc.pNext = prevStruct;
prevStruct = &pcc; 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{}; VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
physicalDeviceFeatures2.pNext = prevStruct; physicalDeviceFeatures2.pNext = prevStruct;
vkGetPhysicalDeviceFeatures2(m_physicalDevice, &physicalDeviceFeatures2); vkGetPhysicalDeviceFeatures2(m_physicalDevice, &physicalDeviceFeatures2);
cemuLog_log(LogType::Force, "Vulkan: present_wait extension: {}", (pwf.presentWait && pidf.presentId) ? "supported" : "unsupported");
/* Get Vulkan device properties and limits */ /* Get Vulkan device properties and limits */
VkPhysicalDeviceFloatControlsPropertiesKHR pfcp{}; VkPhysicalDeviceFloatControlsPropertiesKHR pfcp{};
prevStruct = nullptr; 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"); 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 async shader compilation is enabled show warning message
if (GetConfig().async_compile) 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) if (!m_featureControl.deviceExtensions.custom_border_color_without_format)
{ {
@ -337,7 +353,7 @@ VulkanRenderer::VulkanRenderer()
VkApplicationInfo app_info{}; VkApplicationInfo app_info{};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = EMULATOR_NAME; 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.pEngineName = EMULATOR_NAME;
app_info.engineVersion = app_info.applicationVersion; app_info.engineVersion = app_info.applicationVersion;
app_info.apiVersion = apiVersion; app_info.apiVersion = apiVersion;
@ -488,6 +504,24 @@ VulkanRenderer::VulkanRenderer()
customBorderColorFeature.customBorderColors = VK_TRUE; customBorderColorFeature.customBorderColors = VK_TRUE;
customBorderColorFeature.customBorderColorWithoutFormat = 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<const char*> used_extensions; std::vector<const char*> used_extensions;
VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions);
@ -1045,6 +1079,10 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe
used_extensions.emplace_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); used_extensions.emplace_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
if (m_featureControl.deviceExtensions.shader_float_controls) if (m_featureControl.deviceExtensions.shader_float_controls)
used_extensions.emplace_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); used_extensions.emplace_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
if (m_featureControl.deviceExtensions.present_wait)
used_extensions.emplace_back(VK_KHR_PRESENT_ID_EXTENSION_NAME);
if (m_featureControl.deviceExtensions.present_wait)
used_extensions.emplace_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME);
VkDeviceCreateInfo createInfo{}; VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
@ -1142,6 +1180,7 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device,
info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
// dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022
info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME);
// check for framedebuggers // check for framedebuggers
info.debugMarkersSupported = false; info.debugMarkersSupported = false;
@ -1853,6 +1892,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers()
if (fenceStatus == VK_SUCCESS) if (fenceStatus == VK_SUCCESS)
{ {
ProcessDestructionQueue(); ProcessDestructionQueue();
m_uniformVarBufferReadIndex = m_cmdBufferUniformRingbufIndices[m_commandBufferSyncIndex];
m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size(); m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size();
memoryManager->cleanupBuffers(m_countCommandBufferFinished); memoryManager->cleanupBuffers(m_countCommandBufferFinished);
m_countCommandBufferFinished++; m_countCommandBufferFinished++;
@ -1946,6 +1986,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer..."); cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer...");
WaitForNextFinishedCommandBuffer(); WaitForNextFinishedCommandBuffer();
} }
m_cmdBufferUniformRingbufIndices[nextCmdBufferIndex] = m_cmdBufferUniformRingbufIndices[m_commandBufferIndex];
m_commandBufferIndex = nextCmdBufferIndex; m_commandBufferIndex = nextCmdBufferIndex;
@ -2198,6 +2239,8 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
else else
{ {
formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_COLOR_BIT; formatInfoOut->vkImageAspect = 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) switch (format)
{ {
// RGBA formats // RGBA formats
@ -2439,6 +2482,11 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
// used by Color Splash and Resident Evil // used by Color Splash and Resident Evil
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format? formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format?
formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify 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: default:
cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format); cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format);
cemu_assert_debug(false); cemu_assert_debug(false);
@ -2686,11 +2734,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); 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]; VkSemaphore presentSemaphore = chainInfo.m_presentSemaphores[chainInfo.swapchainImageIndex];
SubmitCommandBuffer(presentSemaphore); // submit all command and signal semaphore SubmitCommandBuffer(presentSemaphore); // submit all command and signal semaphore
cemu_assert_debug(m_numSubmittedCmdBuffers > 0); 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 = {}; VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.swapchainCount = 1; presentInfo.swapchainCount = 1;
@ -2700,6 +2758,24 @@ void VulkanRenderer::SwapBuffer(bool mainWindow)
presentInfo.waitSemaphoreCount = 1; presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &presentSemaphore; 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); VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo);
if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR) if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR)
{ {
@ -2708,6 +2784,12 @@ void VulkanRenderer::SwapBuffer(bool mainWindow)
if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
chainInfo.m_shouldRecreate = true; chainInfo.m_shouldRecreate = true;
if(result >= 0)
{
chainInfo.m_queueDepth++;
chainInfo.m_presentId++;
}
chainInfo.hasDefinedSwapchainImage = false; chainInfo.hasDefinedSwapchainImage = false;
chainInfo.swapchainImageIndex = -1; chainInfo.swapchainImageIndex = -1;
@ -3482,13 +3564,13 @@ void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType,
switch (shaderType) switch (shaderType)
{ {
case LatteConst::ShaderType::Vertex: case LatteConst::ShaderType::Vertex:
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = offset; dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = offset;
break; break;
case LatteConst::ShaderType::Geometry: case LatteConst::ShaderType::Geometry:
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = offset; dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = offset;
break; break;
case LatteConst::ShaderType::Pixel: case LatteConst::ShaderType::Pixel:
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = offset; dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = offset;
break; break;
default: default:
cemu_assert_debug(false); cemu_assert_debug(false);

View file

@ -450,6 +450,7 @@ private:
bool synchronization2 = false; // VK_KHR_synchronization2 bool synchronization2 = false; // VK_KHR_synchronization2
bool dynamic_rendering = false; // VK_KHR_dynamic_rendering bool dynamic_rendering = false; // VK_KHR_dynamic_rendering
bool shader_float_controls = false; // VK_KHR_shader_float_controls bool shader_float_controls = false; // VK_KHR_shader_float_controls
bool present_wait = false; // VK_KHR_present_wait
}deviceExtensions; }deviceExtensions;
struct struct
@ -457,7 +458,7 @@ private:
bool shaderRoundingModeRTEFloat32{ false }; bool shaderRoundingModeRTEFloat32{ false };
}shaderFloatControls; // from VK_KHR_shader_float_controls }shaderFloatControls; // from VK_KHR_shader_float_controls
struct struct
{ {
bool debug_utils = false; // VK_EXT_DEBUG_UTILS bool debug_utils = false; // VK_EXT_DEBUG_UTILS
}instanceExtensions; }instanceExtensions;
@ -590,6 +591,7 @@ private:
bool m_uniformVarBufferMemoryIsCoherent{false}; bool m_uniformVarBufferMemoryIsCoherent{false};
uint8* m_uniformVarBufferPtr = nullptr; uint8* m_uniformVarBufferPtr = nullptr;
uint32 m_uniformVarBufferWriteIndex = 0; uint32 m_uniformVarBufferWriteIndex = 0;
uint32 m_uniformVarBufferReadIndex = 0;
// transform feedback ringbuffer // transform feedback ringbuffer
VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE; VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE;
@ -635,6 +637,8 @@ private:
size_t m_commandBufferIndex = 0; // current buffer being filled 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_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit)
size_t m_commandBufferIDOfPrevFrame = 0;
std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer
std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences; std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences;
std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers; std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers;
std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores; std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores;
@ -657,7 +661,7 @@ private:
uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
struct struct
{ {
uint32 unformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS]; uint32 uniformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS];
}shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; }shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
}dynamicOffsetInfo{}; }dynamicOffsetInfo{};

View file

@ -375,24 +375,20 @@ float s_vkUniformData[512 * 4];
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader) void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader)
{ {
auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return s_vkUniformData + (index / 4); }; auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); };
sint32 shaderAluConst; sint32 shaderAluConst;
sint32 shaderUniformRegisterOffset;
switch (shader->shaderType) switch (shader->shaderType)
{ {
case LatteConst::ShaderType::Vertex: case LatteConst::ShaderType::Vertex:
shaderAluConst = 0x400; shaderAluConst = 0x400;
shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START;
break; break;
case LatteConst::ShaderType::Pixel: case LatteConst::ShaderType::Pixel:
shaderAluConst = 0; shaderAluConst = 0;
shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START;
break; break;
case LatteConst::ShaderType::Geometry: case LatteConst::ShaderType::Geometry:
shaderAluConst = 0; // geometry shader has no ALU const shaderAluConst = 0; // geometry shader has no ALU const
shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START;
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -445,7 +441,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
} }
if (shader->uniform.loc_verticesPerInstance >= 0) if (shader->uniform.loc_verticesPerInstance >= 0)
{ {
*(int*)(s_vkUniformData + ((size_t)shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance; *(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance;
for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++)
{ {
if (shader->uniform.loc_streamoutBufferBase[b] >= 0) if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
@ -455,26 +451,63 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
} }
} }
// upload // upload
if ((m_uniformVarBufferWriteIndex + shader->uniform.uniformRangeSize + 1024) > UNIFORMVAR_RINGBUFFER_SIZE) const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
const uint32 uniformSize = (shader->uniform.uniformRangeSize + bufferAlignmentM1) & ~bufferAlignmentM1;
auto waitWhileCondition = [&](std::function<bool()> condition) {
while (condition())
{
if (m_commandBufferSyncIndex == m_commandBufferIndex)
{
if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex)
{
draw_endRenderPass();
SubmitCommandBuffer();
}
else
{
// submitting work would not change readIndex, so there's no way for conditions based on it to change
cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption");
cemu_assert_suspicious();
break;
}
}
WaitForNextFinishedCommandBuffer();
}
};
// wrap around if it doesnt fit consecutively
if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE)
{ {
waitWhileCondition([&]() {
return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0;
});
m_uniformVarBufferWriteIndex = 0; m_uniformVarBufferWriteIndex = 0;
} }
uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
auto ringBufRemaining = [&]() {
ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex;
if (ringBufferUsedBytes < 0)
ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE;
return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes;
};
waitWhileCondition([&]() {
return ringBufRemaining() < uniformSize;
});
const uint32 uniformOffset = m_uniformVarBufferWriteIndex; const uint32 uniformOffset = m_uniformVarBufferWriteIndex;
memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize); memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize);
m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize; m_uniformVarBufferWriteIndex += uniformSize;
m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1;
// update dynamic offset // update dynamic offset
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset; dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset;
// flush if not coherent // flush if not coherent
if (!m_uniformVarBufferMemoryIsCoherent) if (!m_uniformVarBufferMemoryIsCoherent)
{ {
uint32 nonCoherentAtomSizeM1 = m_featureControl.limits.nonCoherentAtomSize - 1;
VkMappedMemoryRange flushedRange{}; VkMappedMemoryRange flushedRange{};
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
flushedRange.memory = m_uniformVarBufferMemory; flushedRange.memory = m_uniformVarBufferMemory;
flushedRange.offset = uniformOffset; flushedRange.offset = uniformOffset;
flushedRange.size = (shader->uniform.uniformRangeSize + nonCoherentAtomSizeM1) & ~nonCoherentAtomSizeM1; flushedRange.size = uniformSize;
vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange); vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange);
} }
} }
@ -494,7 +527,7 @@ void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderSta
{ {
for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex]) for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex])
{ {
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].unformBufferOffset[itr]; dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].uniformBufferOffset[itr];
numDynOffsets++; numDynOffsets++;
} }
} }
@ -1357,6 +1390,24 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
return; return;
} }
// prepare streamout
m_streamoutState.verticesPerInstance = count;
LatteStreamout_PrepareDrawcall(count, instanceCount);
// update uniform vars
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
if (vertexShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
if (pixelShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
if (geometryShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
// store where the read pointer should go after command buffer execution
m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex;
// process index data // process index data
const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]); const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]);
@ -1410,22 +1461,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
} }
// prepare streamout
m_streamoutState.verticesPerInstance = count;
LatteStreamout_PrepareDrawcall(count, instanceCount);
// update uniform vars
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
if (vertexShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
if (pixelShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
if (geometryShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
PipelineInfo* pipeline_info; PipelineInfo* pipeline_info;
if (!isFirst) if (!isFirst)
@ -1613,13 +1648,13 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader
switch (shaderType) switch (shaderType)
{ {
case LatteConst::ShaderType::Vertex: case LatteConst::ShaderType::Vertex:
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
break; break;
case LatteConst::ShaderType::Geometry: case LatteConst::ShaderType::Geometry:
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
break; break;
case LatteConst::ShaderType::Pixel: case LatteConst::ShaderType::Pixel:
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
break; break;
default: default:
UNREACHABLE; UNREACHABLE;

View file

@ -137,6 +137,10 @@ namespace iosu
this->task_settings.taskType = settings->taskType; this->task_settings.taskType = settings->taskType;
curl = std::shared_ptr<CURL>(curl_easy_init(), curl_easy_cleanup); curl = std::shared_ptr<CURL>(curl_easy_init(), curl_easy_cleanup);
if(GetConfig().proxy_server.GetValue() != "")
{
curl_easy_setopt(curl.get(), CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str());
}
} }
}; };

View file

@ -511,6 +511,8 @@ namespace iosu
return CallHandler_GetBlackList(fpdClient, vecIn, numVecIn, vecOut, numVecOut); return CallHandler_GetBlackList(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
case FPD_REQUEST_ID::GetFriendListEx: case FPD_REQUEST_ID::GetFriendListEx:
return CallHandler_GetFriendListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut); 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: case FPD_REQUEST_ID::UpdatePreferenceAsync:
return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync: case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync:
@ -719,18 +721,23 @@ namespace iosu
nnResult CallHandler_GetMyComment(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) 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) if(numVecIn != 0 || numVecOut != 1)
return FPResult_InvalidIPCParam; 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<uint16be> myComment; std::basic_string<uint16be> myComment;
myComment.resize(MY_COMMENT_LENGTH); if(g_fpd.nexFriendSession)
memcpy(vecOut->basePhys.GetPtr(), myComment.data(), MY_COMMENT_LENGTH*sizeof(uint16be)); {
return 0; 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) nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
@ -1143,6 +1150,36 @@ namespace iosu
return FPResult_Ok; 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) nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
{ {
std::unique_lock _l(g_fpd.mtxFriendSession); std::unique_lock _l(g_fpd.mtxFriendSession);

View file

@ -212,6 +212,7 @@ namespace iosu
static const int RELATIONSHIP_FRIEND = 3; 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 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 enum class FPD_REQUEST_ID
{ {
@ -245,6 +246,7 @@ namespace iosu
CheckSettingStatusAsync = 0x7596, CheckSettingStatusAsync = 0x7596,
GetFriendListEx = 0x75F9, GetFriendListEx = 0x75F9,
GetFriendRequestListEx = 0x76C1, GetFriendRequestListEx = 0x76C1,
UpdateCommentAsync = 0x7726,
UpdatePreferenceAsync = 0x7727, UpdatePreferenceAsync = 0x7727,
RemoveFriendAsync = 0x7789, RemoveFriendAsync = 0x7789,
DeleteFriendFlagsAsync = 0x778A, DeleteFriendFlagsAsync = 0x778A,

View file

@ -23,3 +23,86 @@ void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64);
// utility functions // utility functions
#include "Cafe/OS/common/OSUtil.h" #include "Cafe/OS/common/OSUtil.h"
// va_list
struct ppc_va_list
{
uint8be gprIndex;
uint8be fprIndex;
uint8be _padding2[2];
MEMPTR<uint8be> overflow_arg_area;
MEMPTR<uint8be> 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<ppc_va_list_reg_storage> 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;
}
}

View file

@ -7,14 +7,9 @@
namespace coreinit namespace coreinit
{ {
sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs)
/* coreinit logging and string format */
sint32 ppcSprintf(const char* formatStr, char* strOut, sint32 maxLength, PPCInterpreter_t* hCPU, sint32 initialParamIndex)
{ {
char tempStr[4096]; char tempStr[4096];
sint32 integerParamIndex = initialParamIndex;
sint32 floatParamIndex = 0;
sint32 writeIndex = 0; sint32 writeIndex = 0;
while (*formatStr) while (*formatStr)
{ {
@ -101,8 +96,7 @@ namespace coreinit
tempFormat[(formatStr - formatStart)] = '\0'; tempFormat[(formatStr - formatStart)] = '\0';
else else
tempFormat[sizeof(tempFormat) - 1] = '\0'; tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32));
integerParamIndex++;
for (sint32 i = 0; i < tempLen; i++) for (sint32 i = 0; i < tempLen; i++)
{ {
if (writeIndex >= maxLength) if (writeIndex >= maxLength)
@ -120,13 +114,12 @@ namespace coreinit
tempFormat[(formatStr - formatStart)] = '\0'; tempFormat[(formatStr - formatStart)] = '\0';
else else
tempFormat[sizeof(tempFormat) - 1] = '\0'; 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; sint32 tempLen = 0;
if (strOffset == MPTR_NULL) if (strOffset == MPTR_NULL)
tempLen = sprintf(tempStr, "NULL"); tempLen = sprintf(tempStr, "NULL");
else else
tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset)); tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset));
integerParamIndex++;
for (sint32 i = 0; i < tempLen; i++) for (sint32 i = 0; i < tempLen; i++)
{ {
if (writeIndex >= maxLength) if (writeIndex >= maxLength)
@ -136,25 +129,6 @@ namespace coreinit
} }
strOut[std::min(maxLength - 1, writeIndex)] = '\0'; 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') else if (*formatStr == 'c')
{ {
// character // character
@ -164,8 +138,24 @@ namespace coreinit
tempFormat[(formatStr - formatStart)] = '\0'; tempFormat[(formatStr - formatStart)] = '\0';
else else
tempFormat[sizeof(tempFormat) - 1] = '\0'; tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32));
integerParamIndex++; 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<double>*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE));
for (sint32 i = 0; i < tempLen; i++) for (sint32 i = 0; i < tempLen; i++)
{ {
if (writeIndex >= maxLength) if (writeIndex >= maxLength)
@ -183,8 +173,7 @@ namespace coreinit
tempFormat[(formatStr - formatStart)] = '\0'; tempFormat[(formatStr - formatStart)] = '\0';
else else
tempFormat[sizeof(tempFormat) - 1] = '\0'; tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, (double)hCPU->fpr[1 + floatParamIndex].fp0); sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype<double>*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE));
floatParamIndex++;
for (sint32 i = 0; i < tempLen; i++) for (sint32 i = 0; i < tempLen; i++)
{ {
if (writeIndex >= maxLength) if (writeIndex >= maxLength)
@ -196,16 +185,13 @@ namespace coreinit
else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X'))) else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X')))
{ {
formatStr += 3; formatStr += 3;
// double (64bit) // 64bit int
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat)) if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0'; tempFormat[(formatStr - formatStart)] = '\0';
else else
tempFormat[sizeof(tempFormat) - 1] = '\0'; tempFormat[sizeof(tempFormat) - 1] = '\0';
if (integerParamIndex & 1) sint32 tempLen = sprintf(tempStr, tempFormat, (uint64)*(uint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64));
integerParamIndex++;
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex));
integerParamIndex += 2;
for (sint32 i = 0; i < tempLen; i++) for (sint32 i = 0; i < tempLen; i++)
{ {
if (writeIndex >= maxLength) if (writeIndex >= maxLength)
@ -223,10 +209,7 @@ namespace coreinit
tempFormat[(formatStr - formatStart)] = '\0'; tempFormat[(formatStr - formatStart)] = '\0';
else else
tempFormat[sizeof(tempFormat) - 1] = '\0'; tempFormat[sizeof(tempFormat) - 1] = '\0';
if (integerParamIndex & 1) sint32 tempLen = sprintf(tempStr, tempFormat, (sint64)*(sint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64));
integerParamIndex++;
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex));
integerParamIndex += 2;
for (sint32 i = 0; i < tempLen; i++) for (sint32 i = 0; i < tempLen; i++)
{ {
if (writeIndex >= maxLength) if (writeIndex >= maxLength)
@ -255,9 +238,12 @@ namespace coreinit
return std::min(writeIndex, maxLength - 1); return std::min(writeIndex, maxLength - 1);
} }
/* coreinit logging and string format */
sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr) 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; 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]; char tmpBuffer[1024];
sint32 len = ppcSprintf(format, buffer, sizeof(buffer), PPCInterpreter_getCurrentInstance(), 1); sint32 len = ppc_vprintf(format, tmpBuffer, sizeof(tmpBuffer), vargs);
WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len); 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) void COSWarn(int moduleId, const char* format)
{ {
char buffer[1024 * 2]; ppc_define_va_list(2, 0);
int prefixLen = sprintf(buffer, "[COSWarn-%d] ", moduleId); char tmpBuffer[1024];
sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 2); int prefixLen = sprintf(tmpBuffer, "[COSWarn-%d] ", moduleId);
WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); 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) void OSLogPrintf(int ukn1, int ukn2, int ukn3, const char* format)
{ {
char buffer[1024 * 2]; ppc_define_va_list(4, 0);
int prefixLen = sprintf(buffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); char tmpBuffer[1024];
sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 4); int prefixLen = sprintf(tmpBuffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3);
WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs);
WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen);
} }
void OSConsoleWrite(const char* strPtr, sint32 length) void OSConsoleWrite(const char* strPtr, sint32 length)
@ -562,9 +556,11 @@ namespace coreinit
s_transitionToForeground = false; s_transitionToForeground = false;
cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder);
cafeExportRegister("coreinit", COSVReport, LogType::Placeholder);
cafeExportRegister("coreinit", COSWarn, LogType::Placeholder);
cafeExportRegister("coreinit", OSReport, LogType::Placeholder); cafeExportRegister("coreinit", OSReport, LogType::Placeholder);
cafeExportRegister("coreinit", OSVReport, LogType::Placeholder); cafeExportRegister("coreinit", OSVReport, LogType::Placeholder);
cafeExportRegister("coreinit", COSWarn, LogType::Placeholder);
cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder);

View file

@ -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_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3);
uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId); 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(); void miscInit();
}; };

View file

@ -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 // 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 // to avoid an infinite loop we have no choice but to yield the thread even it is in an uninterruptible state
if( !OSIsInterruptEnabled() ) 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)) while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{ {
OSYieldThread(); OSYieldThread();

View file

@ -464,6 +464,14 @@ namespace nn
return ipcCtx->Submit(std::move(ipcCtx)); return ipcCtx->Submit(std::move(ipcCtx));
} }
nnResult GetMyPlayingGame(iosu::fpd::GameKey* myPlayingGame)
{
FP_API_BASE();
auto ipcCtx = std::make_unique<FPIpcContext>(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) nnResult GetMyPreference(iosu::fpd::FPDPreference* myPreference)
{ {
FP_API_BASE(); FP_API_BASE();
@ -472,6 +480,14 @@ namespace nn
return ipcCtx->Submit(std::move(ipcCtx)); return ipcCtx->Submit(std::move(ipcCtx));
} }
nnResult GetMyComment(uint16be* myComment)
{
FP_API_BASE();
auto ipcCtx = std::make_unique<FPIpcContext>(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) nnResult GetMyMii(FFLData_t* fflData)
{ {
FP_API_BASE(); FP_API_BASE();
@ -607,6 +623,20 @@ namespace nn
return resultBuf != 0 ? 1 : 0; return resultBuf != 0 ? 1 : 0;
} }
nnResult UpdateCommentAsync(uint16be* newComment, void* funcPtr, void* customParam)
{
FP_API_BASE();
auto ipcCtx = std::make_unique<FPIpcContext>(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) nnResult UpdatePreferenceAsync(iosu::fpd::FPDPreference* newPreference, void* funcPtr, void* customParam)
{ {
FP_API_BASE(); FP_API_BASE();
@ -763,7 +793,9 @@ namespace nn
cafeExportRegisterFunc(GetMyAccountId, "nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", LogType::NN_FP); cafeExportRegisterFunc(GetMyAccountId, "nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", LogType::NN_FP);
cafeExportRegisterFunc(GetMyScreenName, "nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", LogType::NN_FP); cafeExportRegisterFunc(GetMyScreenName, "nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", LogType::NN_FP);
cafeExportRegisterFunc(GetMyMii, "nn_fp", "GetMyMii__Q2_2nn2fpFP12FFLStoreData", 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(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(GetFriendAccountId, "nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", LogType::NN_FP);
cafeExportRegisterFunc(GetFriendScreenName, "nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", 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(CheckSettingStatusAsync, "nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", LogType::NN_FP);
cafeExportRegisterFunc(IsPreferenceValid, "nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", 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(UpdatePreferenceAsync, "nn_fp", "UpdatePreferenceAsync__Q2_2nn2fpFPCQ3_2nn2fp10PreferencePFQ2_2nn6ResultPv_vPv", LogType::NN_FP);
cafeExportRegisterFunc(GetRequestBlockSettingAsync, "nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(GetRequestBlockSettingAsync, "nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP);

View file

@ -334,45 +334,63 @@ void nnNfpExport_MountRom(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0));
} }
typedef struct namespace nn::nfp
{ {
/* +0x00 */ uint8 characterId[3]; struct RomInfo
/* +0x03 */ uint8 amiiboSeries;
/* +0x04 */ uint16be number;
/* +0x06 */ uint8 nfpType;
/* +0x07 */ uint8 unused[0x2F];
}nfpRomInfo_t;
static_assert(offsetof(nfpRomInfo_t, amiiboSeries) == 0x3, "nfpRomInfo.seriesId has invalid offset");
static_assert(offsetof(nfpRomInfo_t, number) == 0x4, "nfpRomInfo.number has invalid offset");
static_assert(offsetof(nfpRomInfo_t, nfpType) == 0x6, "nfpRomInfo.nfpType has invalid offset");
static_assert(sizeof(nfpRomInfo_t) == 0x36, "nfpRomInfo_t has invalid size");
void nnNfpExport_GetNfpRomInfo(PPCInterpreter_t* hCPU)
{
cemuLog_log(LogType::NN_NFP, "GetNfpRomInfo(0x{:08x})", hCPU->gpr[3]);
ppcDefineParamStructPtr(romInfo, nfpRomInfo_t, 0);
nnNfpLock();
if (nfp_data.hasActiveAmiibo == false)
{ {
nnNfpUnlock(); /* +0x00 */ uint8 characterId[3];
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code /* +0x03 */ uint8 amiiboSeries;
return; /* +0x04 */ uint16be number;
/* +0x06 */ uint8 nfpType;
/* +0x07 */ uint8 unused[0x2F];
};
static_assert(offsetof(RomInfo, amiiboSeries) == 0x3);
static_assert(offsetof(RomInfo, number) == 0x4);
static_assert(offsetof(RomInfo, nfpType) == 0x6);
static_assert(sizeof(RomInfo) == 0x36);
using ReadOnlyInfo = RomInfo; // same layout
void GetRomInfo(RomInfo* romInfo)
{
cemu_assert_debug(nfp_data.hasActiveAmiibo);
memset(romInfo, 0x00, sizeof(RomInfo));
romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0];
romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1];
romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed
romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed
romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed
romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed
memset(romInfo->unused, 0x00, sizeof(romInfo->unused));
} }
memset(romInfo, 0x00, sizeof(nfpRomInfo_t));
romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; nnResult GetNfpRomInfo(RomInfo* romInfo)
romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; {
romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed nnNfpLock();
if (nfp_data.hasActiveAmiibo == false)
{
nnNfpUnlock();
return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code
}
GetRomInfo(romInfo);
nnNfpUnlock();
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0);
}
romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed nnResult GetNfpReadOnlyInfo(ReadOnlyInfo* readOnlyInfo)
romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed {
romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed nnNfpLock();
if (nfp_data.hasActiveAmiibo == false)
nnNfpUnlock(); {
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); nnNfpUnlock();
} return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code
}
GetRomInfo(readOnlyInfo);
nnNfpUnlock();
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0);
}
};
typedef struct typedef struct
{ {
@ -880,13 +898,13 @@ void nnNfp_update()
if (amiiboElapsedTouchTime >= 1500) if (amiiboElapsedTouchTime >= 1500)
{ {
nnNfp_unloadAmiibo(); nnNfp_unloadAmiibo();
if (nfp_data.deactivateEvent)
{
coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent);
coreinit::OSSignalEvent(osEvent);
}
} }
nnNfpUnlock(); nnNfpUnlock();
if (nfp_data.deactivateEvent)
{
coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent);
coreinit::OSSignalEvent(osEvent);
}
} }
void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU)
@ -1001,8 +1019,6 @@ namespace nn::nfp
osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount); osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount);
osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom); osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom);
osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount); osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount);
osLib_addFunction("nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", nnNfpExport_GetNfpRomInfo);
osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo); osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo);
osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo); osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo);
@ -1028,7 +1044,9 @@ namespace nn::nfp
{ {
nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc
cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::NN_NFP);
cafeExportRegisterFunc(nn::nfp::GetNfpRomInfo, "nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", LogType::NN_NFP);
cafeExportRegisterFunc(nn::nfp::GetNfpReadOnlyInfo, "nn_nfp", "GetNfpReadOnlyInfo__Q2_2nn3nfpFPQ3_2nn3nfp12ReadOnlyInfo", LogType::NN_NFP);
} }
} }

View file

@ -112,7 +112,7 @@ namespace nn
nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param)
{ {
scope_exit _se([&](){coreinit::OSSignalEvent(event);}); stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);});
uint64 titleId = CafeSystem::GetForegroundTitleId(); uint64 titleId = CafeSystem::GetForegroundTitleId();
@ -184,7 +184,7 @@ namespace nn
nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize)
{ {
scope_exit _se([&](){coreinit::OSSignalEvent(event);}); stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);});
if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE)) if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE))
return OLV_RESULT_MISSING_DATA; return OLV_RESULT_MISSING_DATA;

View file

@ -1,4 +1,6 @@
#include "BackendEmulated.h" #include "BackendEmulated.h"
#include "Dimensions.h"
#include "Infinity.h" #include "Infinity.h"
#include "Skylander.h" #include "Skylander.h"
#include "config/CemuConfig.h" #include "config/CemuConfig.h"
@ -33,5 +35,12 @@ namespace nsyshid::backend::emulated
auto device = std::make_shared<InfinityBaseDevice>(); auto device = std::make_shared<InfinityBaseDevice>();
AttachDevice(device); AttachDevice(device);
} }
if (GetConfig().emulated_usb_devices.emulate_dimensions_toypad && !FindDeviceById(0x0E6F, 0x0241))
{
cemuLog_logDebug(LogType::Force, "Attaching Emulated Toypad");
// Add Dimensions Toypad
auto device = std::make_shared<DimensionsToypadDevice>();
AttachDevice(device);
}
} }
} // namespace nsyshid::backend::emulated } // namespace nsyshid::backend::emulated

View file

@ -15,7 +15,7 @@ namespace nsyshid::backend::libusb
if (m_initReturnCode < 0) if (m_initReturnCode < 0)
{ {
m_ctx = nullptr; m_ctx = nullptr;
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb with return code %i", cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}",
m_initReturnCode); m_initReturnCode);
return; return;
} }
@ -35,7 +35,7 @@ namespace nsyshid::backend::libusb
if (ret != LIBUSB_SUCCESS) if (ret != LIBUSB_SUCCESS)
{ {
cemuLog_logDebug(LogType::Force, cemuLog_logDebug(LogType::Force,
"nsyshid::BackendLibusb: failed to register hotplug callback with return code %i", "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}",
ret); ret);
} }
else else
@ -415,7 +415,7 @@ namespace nsyshid::backend::libusb
if (ret < 0) if (ret < 0)
{ {
cemuLog_log(LogType::Force, cemuLog_log(LogType::Force,
"nsyshid::DeviceLibusb::open(): failed to get device descriptor; return code: %i", "nsyshid::DeviceLibusb::open(): failed to get device descriptor, return code: {}",
ret); ret);
libusb_free_device_list(devices, 1); libusb_free_device_list(devices, 1);
return false; return false;
@ -439,8 +439,8 @@ namespace nsyshid::backend::libusb
{ {
this->m_libusbHandle = nullptr; this->m_libusbHandle = nullptr;
cemuLog_log(LogType::Force, cemuLog_log(LogType::Force,
"nsyshid::DeviceLibusb::open(): failed to open device; return code: %i", "nsyshid::DeviceLibusb::open(): failed to open device: {}",
ret); libusb_strerror(ret));
libusb_free_device_list(devices, 1); libusb_free_device_list(devices, 1);
return false; return false;
} }

View file

@ -67,13 +67,6 @@ namespace nsyshid::backend::windows
device->m_productId); device->m_productId);
} }
} }
else
{
cemuLog_log(LogType::Force,
"nsyshid::BackendWindowsHID: device not on whitelist: {:04x}:{:04x}",
device->m_vendorId,
device->m_productId);
}
} }
CloseHandle(hHIDDevice); CloseHandle(hHIDDevice);
} }
@ -125,14 +118,12 @@ namespace nsyshid::backend::windows
} }
if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000)
{ {
cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", cemuLog_logDebug(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength);
maxPacketInputLength);
maxPacketInputLength = 0x20; maxPacketInputLength = 0x20;
} }
if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000)
{ {
cemuLog_log(LogType::Force, "HID: Output packet length not available or out of range (length = {})", cemuLog_logDebug(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength);
maxPacketOutputLength);
maxPacketOutputLength = 0x20; maxPacketOutputLength = 0x20;
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
#include <mutex>
#include "nsyshid.h"
#include "Backend.h"
#include "Common/FileStream.h"
namespace nsyshid
{
class DimensionsToypadDevice final : public Device
{
public:
DimensionsToypadDevice();
~DimensionsToypadDevice() = 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;
};
class DimensionsUSB
{
public:
struct DimensionsMini final
{
std::unique_ptr<FileStream> dimFile;
std::array<uint8, 0x2D * 0x04> data{};
uint8 index = 255;
uint8 pad = 255;
uint32 id = 0;
void Save();
};
void SendCommand(std::span<const uint8, 32> buf);
std::array<uint8, 32> GetStatus();
void GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence,
std::array<uint8, 32>& replyBuf);
void InitializeRNG(uint32 seed);
void GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence,
std::array<uint8, 32>& replyBuf);
void QueryBlock(uint8 index, uint8 page, std::array<uint8, 32>& replyBuf,
uint8 sequence);
void WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf, std::array<uint8, 32>& replyBuf,
uint8 sequence);
void GetModel(std::span<const uint8, 8> buf, uint8 sequence,
std::array<uint8, 32>& replyBuf);
bool RemoveFigure(uint8 pad, uint8 index, bool fullRemove);
bool TempRemove(uint8 index);
bool CancelRemove(uint8 index);
uint32 LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index);
bool CreateFigure(fs::path pathName, uint32 id);
bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex);
static std::map<const uint32, const char*> GetListMinifigs();
static std::map<const uint32, const char*> GetListTokens();
std::string FindFigure(uint32 figNum);
protected:
std::mutex m_dimensionsMutex;
std::array<DimensionsMini, 7> m_figures{};
private:
void RandomUID(std::array<uint8, 0x2D * 0x04>& uidBuffer);
uint8 GenerateChecksum(const std::array<uint8, 32>& data,
int numOfBytes) const;
std::array<uint8, 8> Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key);
std::array<uint8, 8> Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key);
std::array<uint8, 16> GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& uid);
std::array<uint8, 4> PWDGenerate(const std::array<uint8, 0x2D * 0x04>& uid);
std::array<uint8, 4> DimensionsRandomize(const std::vector<uint8> key, uint8 count);
uint32 GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf);
uint32 Scramble(const std::array<uint8, 7>& uid, uint8 count);
uint32 GetNext();
DimensionsMini& GetFigureByIndex(uint8 index);
uint32 m_randomA;
uint32 m_randomB;
uint32 m_randomC;
uint32 m_randomD;
bool m_isAwake = false;
std::queue<std::array<uint8, 32>> m_figureAddedRemovedResponses;
std::queue<std::array<uint8, 32>> m_queries;
};
extern DimensionsUSB g_dimensionstoypad;
} // namespace nsyshid

View file

@ -1017,11 +1017,7 @@ namespace nsyshid
std::array<uint8, 16> InfinityUSB::GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data) std::array<uint8, 16> InfinityUSB::GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data)
{ {
std::array<uint8, 20> digest = {}; std::array<uint8, 20> digest = {};
SHA_CTX ctx; SHA1(sha1Data.data(), sha1Data.size(), digest.data());
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 // Infinity AES keys are the first 16 bytes of the SHA1 Digest, every set of 4 bytes need to be
// reversed due to endianness // reversed due to endianness
std::array<uint8, 16> key = {}; std::array<uint8, 16> key = {};

View file

@ -1210,6 +1210,14 @@ void nsysnetExport_select(PPCInterpreter_t* hCPU)
timeval tv = { 0 }; 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); uint64 msTimeout = (_swapEndianU32(timeOut->tv_usec) / 1000) + (_swapEndianU32(timeOut->tv_sec) * 1000);
uint32 startTime = GetTickCount(); uint32 startTime = GetTickCount();
while (true) while (true)

View file

@ -509,7 +509,7 @@ namespace ntag
noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1);
} }
memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(noftHeader)); memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(NTAGNoftHeader));
memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize);
// Encrypt // Encrypt

View file

@ -12,6 +12,7 @@
enum class KPAD_ERROR : sint32 enum class KPAD_ERROR : sint32
{ {
NONE = 0, NONE = 0,
NO_SAMPLE_DATA = -1,
NO_CONTROLLER = -2, NO_CONTROLLER = -2,
NOT_INITIALIZED = -5, NOT_INITIALIZED = -5,
}; };
@ -106,6 +107,9 @@ void padscoreExport_WPADProbe(PPCInterpreter_t* hCPU)
} }
else else
{ {
if(type)
*type = 253;
osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER);
} }
} }
@ -420,9 +424,12 @@ void padscoreExport_KPADSetConnectCallback(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); osLib_returnFromFunction(hCPU, old_callback.GetMPTR());
} }
uint64 g_kpadLastRead[InputManager::kMaxWPADControllers] = {0};
bool g_kpadIsInited = true; bool g_kpadIsInited = true;
sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype<KPAD_ERROR>* errResult) sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype<KPAD_ERROR>* errResult)
{ {
if (channel >= InputManager::kMaxWPADControllers) if (channel >= InputManager::kMaxWPADControllers)
{ {
debugBreakpoint(); debugBreakpoint();
@ -446,6 +453,19 @@ sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, bety
return 0; 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)); memset(samplingBufs, 0x00, sizeof(KPADStatus_t));
samplingBufs->wpadErr = WPAD_ERR_NONE; samplingBufs->wpadErr = WPAD_ERR_NONE;
samplingBufs->data_format = controller->get_data_format(); samplingBufs->data_format = controller->get_data_format();
@ -474,7 +494,6 @@ void padscoreExport_KPADReadEx(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, samplesRead); osLib_returnFromFunction(hCPU, samplesRead);
} }
bool debugUseDRC1 = true;
void padscoreExport_KPADRead(PPCInterpreter_t* hCPU) void padscoreExport_KPADRead(PPCInterpreter_t* hCPU)
{ {
ppcDefineParamU32(channel, 0); ppcDefineParamU32(channel, 0);
@ -726,7 +745,8 @@ namespace padscore
// call sampling callback // call sampling callback
for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) 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)) if (const auto controller = instance.get_wpad_controller(i))
{ {
cemuLog_log(LogType::InputAPI, "Calling WPADsamplingCallback({})", i); cemuLog_log(LogType::InputAPI, "Calling WPADsamplingCallback({})", i);
@ -741,7 +761,7 @@ namespace padscore
{ {
OSCreateAlarm(&g_padscore.alarm); OSCreateAlarm(&g_padscore.alarm);
const uint64 start_tick = coreinit::coreinit_getOSTime(); 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); MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler); OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);
} }

View file

@ -427,7 +427,7 @@ namespace proc_ui
} }
if(callbackType != ProcUICallbackId::AcquireForeground) if(callbackType != ProcUICallbackId::AcquireForeground)
priority = -priority; priority = -priority;
AddCallbackInternal(funcPtr, userParam, priority, 0, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]); AddCallbackInternal(funcPtr, userParam, 0, priority, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]);
} }
void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority) void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority)
@ -437,7 +437,7 @@ namespace proc_ui
void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay) void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay)
{ {
AddCallbackInternal(funcPtr, userParam, 0, tickDelay, s_backgroundCallbackList); AddCallbackInternal(funcPtr, userParam, tickDelay, 0, s_backgroundCallbackList);
} }
void FreeCallbackChain(ProcUICallbackList& callbackList) void FreeCallbackChain(ProcUICallbackList& callbackList)

View file

@ -522,10 +522,10 @@ namespace snd_core
// called periodically to check for AX updates // called periodically to check for AX updates
void AXOut_update() void AXOut_update()
{ {
constexpr auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3))); constexpr static auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3)));
constexpr auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3)); constexpr static auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3));
constexpr auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900)); constexpr static auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900));
constexpr auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700)); constexpr static auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700));
// if we haven't buffered any blocks, we will wait less time than usual // if we haven't buffered any blocks, we will wait less time than usual
bool additional_blocks_required = false; bool additional_blocks_required = false;

View file

@ -50,7 +50,6 @@
extern bool isLaunchTypeELF; extern bool isLaunchTypeELF;
bool debugUseDRC = true;
VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] = VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] =
{ {
{{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}}, {{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; status->tpProcessed2.validity = VPAD_TP_VALIDITY_INVALID_XY;
const auto controller = InputManager::instance().get_vpad_controller(channel); 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) if (error)
*error = VPAD_READ_ERR_NONE; // VPAD_READ_ERR_NO_DATA; // VPAD_READ_ERR_NO_CONTROLLER; *error = VPAD_READ_ERR_NONE;
return 1; return 1;
//osLib_returnFromFunction(hCPU, 1); return;
}
if (channel != 0)
{
debugBreakpoint();
} }
const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled(); const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled();
@ -274,9 +274,7 @@ namespace vpad
// not ready yet // not ready yet
if (error) if (error)
*error = VPAD_READ_ERR_NONE; *error = VPAD_READ_ERR_NONE;
return 0; return 0;
//osLib_returnFromFunction(hCPU, 0); return;
} }
else if (dif <= ESPRESSO_TIMER_CLOCK) else if (dif <= ESPRESSO_TIMER_CLOCK)
{ {

View file

@ -91,7 +91,11 @@ bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args)
else else
{ {
const auto format_view = fmt::basic_string_view<T>(formatStr); const auto format_view = fmt::basic_string_view<T>(formatStr);
#if FMT_VERSION >= 110000
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffered_context<T>>(args...));
#else
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...)); const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...));
#endif
cemuLog_log(type, std::basic_string_view(text.data(), text.size())); cemuLog_log(type, std::basic_string_view(text.data(), text.size()));
} }
return true; return true;

View file

@ -277,7 +277,8 @@ void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response
} }
NexFriends* session = (NexFriends*)nexFriends; NexFriends* session = (NexFriends*)nexFriends;
session->myPreference = nexPrincipalPreference(&response->data); session->myPreference = nexPrincipalPreference(&response->data);
nexComment comment(&response->data); auto comment = nexComment(&response->data);
session->myComment = comment;
if (response->data.hasReadOutOfBounds()) if (response->data.hasReadOutOfBounds())
return; return;
// acquire lock on lists // acquire lock on lists
@ -391,6 +392,28 @@ void NexFriends::getMyPreference(nexPrincipalPreference& preference)
preference = myPreference; preference = myPreference;
} }
bool NexFriends::updateCommentAsync(nexComment newComment, std::function<void(RpcErrorCode)> 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) bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId)
{ {
uint8 tempNexBufferArray[512]; uint8 tempNexBufferArray[512];

View file

@ -297,7 +297,9 @@ public:
void writeData(nexPacketBuffer* pb) const override 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 void readData(nexPacketBuffer* pb) override
@ -554,6 +556,7 @@ public:
bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId); bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId);
bool isOnline(); bool isOnline();
void getMyPreference(nexPrincipalPreference& preference); void getMyPreference(nexPrincipalPreference& preference);
void getMyComment(nexComment& comment);
// asynchronous API (data has to be requested) // asynchronous API (data has to be requested)
bool addProvisionalFriend(char* name, std::function<void(RpcErrorCode)> cb); bool addProvisionalFriend(char* name, std::function<void(RpcErrorCode)> cb);
@ -565,6 +568,7 @@ public:
void acceptFriendRequest(uint64 messageId, std::function<void(RpcErrorCode)> cb); void acceptFriendRequest(uint64 messageId, std::function<void(RpcErrorCode)> cb);
void deleteFriendRequest(uint64 messageId, std::function<void(RpcErrorCode)> cb); // rejecting incoming friend request (differs from blocking friend requests) void deleteFriendRequest(uint64 messageId, std::function<void(RpcErrorCode)> cb); // rejecting incoming friend request (differs from blocking friend requests)
bool updatePreferencesAsync(const nexPrincipalPreference newPreferences, std::function<void(RpcErrorCode)> cb); bool updatePreferencesAsync(const nexPrincipalPreference newPreferences, std::function<void(RpcErrorCode)> cb);
bool updateCommentAsync(const nexComment newComment, std::function<void(RpcErrorCode)> cb);
void updateMyPresence(nexPresenceV2& myPresence); void updateMyPresence(nexPresenceV2& myPresence);
void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)); void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid));
@ -619,6 +623,7 @@ private:
// local friend state // local friend state
nexPresenceV2 myPresence; nexPresenceV2 myPresence;
nexPrincipalPreference myPreference; nexPrincipalPreference myPreference;
nexComment myComment;
std::recursive_mutex mtx_lists; std::recursive_mutex mtx_lists;
std::vector<nexFriend> list_friends; std::vector<nexFriend> list_friends;

View file

@ -4,7 +4,7 @@
using MPTR = uint32; // generic address in PowerPC memory space using MPTR = uint32; // generic address in PowerPC memory space
#define MPTR_NULL (0) #define MPTR_NULL (0)
using VAddr = uint32; // virtual address using VAddr = uint32; // virtual address
using PAddr = uint32; // physical address using PAddr = uint32; // physical address
@ -14,144 +14,177 @@ extern uint8* PPCInterpreterGetStackPointer();
extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset);
extern void PPCInterpreterModifyStackPointer(sint32 offset); extern void PPCInterpreterModifyStackPointer(sint32 offset);
class MEMPTRBase {}; class MEMPTRBase
{
};
template <typename T> template<typename T>
class MEMPTR : MEMPTRBase class MEMPTR : MEMPTRBase
{ {
public: public:
constexpr MEMPTR() constexpr MEMPTR() noexcept
: m_value(0) { } : m_value(0) {}
explicit constexpr MEMPTR(uint32 offset) explicit constexpr MEMPTR(uint32 offset) noexcept
: m_value(offset) { } : m_value(offset) {}
explicit constexpr MEMPTR(const uint32be& offset) explicit constexpr MEMPTR(const uint32be& offset) noexcept
: m_value(offset) { } : m_value(offset) {}
constexpr MEMPTR(std::nullptr_t) constexpr MEMPTR(std::nullptr_t) noexcept
: m_value(0) { } : m_value(0) {}
MEMPTR(T* ptr) MEMPTR(T* ptr) noexcept
{ {
if (ptr == nullptr) if (ptr == nullptr)
m_value = 0; m_value = 0;
else else
{ {
cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000);
m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base);
} }
} }
constexpr MEMPTR(const MEMPTR& memptr) constexpr MEMPTR(const MEMPTR&) noexcept = default;
: m_value(memptr.m_value) { }
constexpr MEMPTR& operator=(const MEMPTR& memptr) constexpr MEMPTR& operator=(const MEMPTR&) noexcept = default;
{
m_value = memptr.m_value;
return *this;
}
constexpr MEMPTR& operator=(const uint32& offset) constexpr MEMPTR& operator=(const uint32& offset) noexcept
{ {
m_value = offset; m_value = offset;
return *this; return *this;
} }
constexpr MEMPTR& operator=(const std::nullptr_t rhs) constexpr MEMPTR& operator=(std::nullptr_t) noexcept
{ {
m_value = 0; m_value = 0;
return *this; return *this;
} }
MEMPTR& operator=(T* ptr) MEMPTR& operator=(T* ptr) noexcept
{ {
if (ptr == nullptr) if (ptr == nullptr)
m_value = 0; m_value = 0;
else else
{ {
cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000);
m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base);
} }
return *this; return *this;
} }
bool atomic_compare_exchange(T* comparePtr, T* newPtr) bool atomic_compare_exchange(T* comparePtr, T* newPtr) noexcept
{ {
MEMPTR<T> mp_compare = comparePtr; MEMPTR<T> mp_compare = comparePtr;
MEMPTR<T> mp_new = newPtr; MEMPTR<T> mp_new = newPtr;
std::atomic<uint32be>* thisValueAtomic = (std::atomic<uint32be>*)&m_value; auto* thisValueAtomic = reinterpret_cast<std::atomic<uint32be>*>(&m_value);
return thisValueAtomic->compare_exchange_strong(mp_compare.m_value, mp_new.m_value); return thisValueAtomic->compare_exchange_strong(mp_compare.m_value, mp_new.m_value);
} }
explicit constexpr operator bool() const noexcept { return m_value != 0; } explicit constexpr operator bool() const noexcept
constexpr operator T*() const noexcept { return GetPtr(); } // allow implicit cast to wrapped pointer type
template <typename X>
explicit operator MEMPTR<X>() const { return MEMPTR<X>(this->m_value); }
//bool operator==(const MEMPTR<T>& 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<T>& 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()); }
MEMPTR operator+(sint32 v)
{ {
// pointer arithmetic return m_value != 0;
return MEMPTR(this->GetMPTR() + v * 4);
} }
MEMPTR operator-(sint32 v) // allow implicit cast to wrapped pointer type
constexpr operator T*() const noexcept
{ {
// pointer arithmetic return GetPtr();
return MEMPTR(this->GetMPTR() - v * 4);
} }
template <class Q = T> template<typename X>
typename std::enable_if<!std::is_same<Q, void>::value, Q>::type& explicit operator MEMPTR<X>() const noexcept
operator*() const { return *GetPtr(); } {
return MEMPTR<X>(this->m_value);
}
T* operator->() const { return GetPtr(); } sint32 operator-(const MEMPTR& ptr) noexcept
requires(!std::is_void_v<T>)
{
return static_cast<sint32>(this->GetMPTR() - ptr.GetMPTR());
}
template <class Q = T> MEMPTR operator+(sint32 v) noexcept
typename std::enable_if<!std::is_same<Q, void>::value, Q>::type& requires(!std::is_void_v<T>)
operator[](int index) { return GetPtr()[index]; } {
// pointer arithmetic
return MEMPTR(this->GetMPTR() + v * sizeof(T));
}
T* GetPtr() const { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value); } MEMPTR operator-(sint32 v) noexcept
requires(!std::is_void_v<T>)
{
// pointer arithmetic
return MEMPTR(this->GetMPTR() - v * sizeof(T));
}
template <typename C> MEMPTR& operator+=(sint32 v) noexcept
C* GetPtr() const { return (C*)(GetPtr()); } requires(!std::is_void_v<T>)
{
m_value += v * sizeof(T);
return *this;
}
constexpr uint32 GetMPTR() const { return m_value.value(); } template<typename Q = T>
constexpr const uint32be& GetBEValue() const { return m_value; } requires(!std::is_void_v<Q>)
Q& operator*() const noexcept
{
return *GetPtr();
}
constexpr bool IsNull() const { return m_value == 0; } constexpr T* operator->() const noexcept
{
return GetPtr();
}
private: template<typename Q = T>
requires(!std::is_void_v<Q>)
Q& operator[](int index) noexcept
{
return GetPtr()[index];
}
T* GetPtr() const noexcept
{
return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value);
}
template<typename C>
C* GetPtr() const noexcept
{
return static_cast<C*>(GetPtr());
}
[[nodiscard]] constexpr uint32 GetMPTR() const noexcept
{
return m_value.value();
}
[[nodiscard]] constexpr const uint32be& GetBEValue() const noexcept
{
return m_value;
}
[[nodiscard]] constexpr bool IsNull() const noexcept
{
return m_value == 0;
}
private:
uint32be m_value; uint32be m_value;
}; };
static_assert(sizeof(MEMPTR<void*>) == sizeof(uint32be)); static_assert(sizeof(MEMPTR<void*>) == sizeof(uint32be));
static_assert(std::is_trivially_copyable_v<MEMPTR<void*>>);
#include "StackAllocator.h" #include "StackAllocator.h"
#include "SysAllocator.h" #include "SysAllocator.h"
template <typename T> template<typename T>
struct fmt::formatter<MEMPTR<T>> : formatter<string_view> struct fmt::formatter<MEMPTR<T>> : formatter<string_view>
{ {
template <typename FormatContext> template<typename FormatContext>
auto format(const MEMPTR<T>& v, FormatContext& ctx) const -> format_context::iterator { return fmt::format_to(ctx.out(), "{:#x}", v.GetMPTR()); } auto format(const MEMPTR<T>& v, FormatContext& ctx) const -> format_context::iterator
{
return fmt::format_to(ctx.out(), "{:#x}", v.GetMPTR());
}
}; };

View file

@ -121,6 +121,12 @@ public:
return *this; return *this;
} }
betype<T>& operator+=(const T& v) requires std::integral<T>
{
m_value = SwapEndian(T(value() + v));
return *this;
}
betype<T>& operator-=(const betype<T>& v) betype<T>& operator-=(const betype<T>& v)
{ {
m_value = SwapEndian(T(value() - v.value())); m_value = SwapEndian(T(value() - v.value()));
@ -188,17 +194,36 @@ public:
return from_bevalue(T(~m_value)); return from_bevalue(T(~m_value));
} }
// pre-increment
betype<T>& operator++() requires std::integral<T> betype<T>& operator++() requires std::integral<T>
{ {
m_value = SwapEndian(T(value() + 1)); m_value = SwapEndian(T(value() + 1));
return *this; return *this;
} }
// post-increment
betype<T> operator++(int) requires std::integral<T>
{
betype<T> tmp(*this);
m_value = SwapEndian(T(value() + 1));
return tmp;
}
// pre-decrement
betype<T>& operator--() requires std::integral<T> betype<T>& operator--() requires std::integral<T>
{ {
m_value = SwapEndian(T(value() - 1)); m_value = SwapEndian(T(value() - 1));
return *this; return *this;
} }
// post-decrement
betype<T> operator--(int) requires std::integral<T>
{
betype<T> tmp(*this);
m_value = SwapEndian(T(value() - 1));
return tmp;
}
private: private:
//T m_value{}; // before 1.26.2 //T m_value{}; // before 1.26.2
T m_value; T m_value;

View file

@ -395,16 +395,10 @@ void vectorRemoveByIndex(std::vector<T>& vec, const size_t index)
vec.erase(vec.begin() + index); vec.erase(vec.begin() + index);
} }
template<typename T1, typename T2> template<typename T1, typename... Types>
int match_any_of(T1 value, T2 compareTo) bool match_any_of(T1&& value, Types&&... others)
{ {
return value == compareTo; return ((value == others) || ...);
}
template<typename T1, typename T2, typename... Types>
bool match_any_of(T1 value, T2 compareTo, Types&&... others)
{
return value == compareTo || match_any_of(value, others...);
} }
// we cache the frequency in a static variable // we cache the frequency in a static variable
@ -502,13 +496,6 @@ bool future_is_ready(std::future<T>& f)
#endif #endif
} }
// replace with std::scope_exit once available
struct scope_exit
{
std::function<void()> f_;
explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
~scope_exit() { if (f_) f_(); }
};
// helper function to cast raw pointers to std::atomic // helper function to cast raw pointers to std::atomic
// this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members // this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members
@ -516,6 +503,8 @@ struct scope_exit
template<typename T> template<typename T>
std::atomic<T>* _rawPtrToAtomic(T* ptr) std::atomic<T>* _rawPtrToAtomic(T* ptr)
{ {
static_assert(sizeof(T) == sizeof(std::atomic<T>));
cemu_assert_debug((reinterpret_cast<std::uintptr_t>(ptr) % alignof(std::atomic<T>)) == 0);
return reinterpret_cast<std::atomic<T>*>(ptr); return reinterpret_cast<std::atomic<T>*>(ptr);
} }
@ -579,13 +568,34 @@ struct fmt::formatter<betype<T>> : fmt::formatter<T>
} }
}; };
// useful C++23 stuff that isn't yet widely supported // useful future C++ stuff
// std::to_underlying
namespace stdx namespace stdx
{ {
// std::to_underlying
template <typename EnumT, typename = std::enable_if_t < std::is_enum<EnumT>{} >> template <typename EnumT, typename = std::enable_if_t < std::is_enum<EnumT>{} >>
constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept { constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept {
return static_cast<std::underlying_type_t<EnumT>>(e); return static_cast<std::underlying_type_t<EnumT>>(e);
}; };
// std::scope_exit
template <typename Fn>
class scope_exit
{
Fn m_func;
bool m_released = false;
public:
explicit scope_exit(Fn&& f) noexcept
: m_func(std::forward<Fn>(f))
{}
~scope_exit()
{
if (!m_released) m_func();
}
scope_exit(scope_exit&& other) noexcept
: m_func(std::move(other.m_func)), m_released(std::exchange(other.m_released, true))
{}
scope_exit(const scope_exit&) = delete;
scope_exit& operator=(scope_exit) = delete;
void release() { m_released = true;}
};
} }

View file

@ -1,36 +1,19 @@
#ifndef EMULATOR_NAME #ifndef EMULATOR_NAME
#define EMULATOR_NAME "Cemu" #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 "" #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 _XSTRINGFY(s) _STRINGFY(s)
#define _STRINGFY(s) #s #define _STRINGFY(s) #s
#if EMULATOR_VERSION_MINOR != 0 #if EMULATOR_VERSION_MAJOR != 0
#if defined(EMULATOR_HASH) && EMULATOR_VERSION_MINOR == 999999 #define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX)
#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_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX)
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#else #else
#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) // no version provided. Only show commit hash
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) #define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#endif #define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#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)
#endif #endif
#endif #endif

View file

@ -1,6 +1,12 @@
project(CemuAsm C) 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) if (WIN32)
@ -40,8 +46,8 @@ if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
endif() endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") elseif(CEMU_ASM_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)")
add_library(CemuAsm stub.cpp) add_library(CemuAsm stub.cpp)
else() else()
message(STATUS "CemuAsm - Unsupported arch: ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS "CemuAsm - Unsupported arch: ${CEMU_ASM_ARCHITECTURE}")
endif() endif()

View file

@ -38,6 +38,7 @@ void CemuConfig::Load(XMLConfigParser& parser)
fullscreen_menubar = parser.get("fullscreen_menubar", false); fullscreen_menubar = parser.get("fullscreen_menubar", false);
feral_gamemode = parser.get("feral_gamemode", false); feral_gamemode = parser.get("feral_gamemode", false);
check_update = parser.get("check_update", check_update); 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); save_screenshot = parser.get("save_screenshot", save_screenshot);
did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); 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); did_show_graphic_pack_download = parser.get("gp_download", did_show_graphic_pack_download);
@ -345,6 +346,7 @@ void CemuConfig::Load(XMLConfigParser& parser)
auto usbdevices = parser.get("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_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); emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base);
emulated_usb_devices.emulate_dimensions_toypad = usbdevices.get("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad);
} }
void CemuConfig::Save(XMLConfigParser& parser) void CemuConfig::Save(XMLConfigParser& parser)
@ -360,6 +362,7 @@ void CemuConfig::Save(XMLConfigParser& parser)
config.set<bool>("fullscreen_menubar", fullscreen_menubar); config.set<bool>("fullscreen_menubar", fullscreen_menubar);
config.set<bool>("feral_gamemode", feral_gamemode); config.set<bool>("feral_gamemode", feral_gamemode);
config.set<bool>("check_update", check_update); config.set<bool>("check_update", check_update);
config.set<bool>("receive_untested_updates", receive_untested_updates);
config.set<bool>("save_screenshot", save_screenshot); config.set<bool>("save_screenshot", save_screenshot);
config.set<bool>("vk_warning", did_show_vulkan_warning); config.set<bool>("vk_warning", did_show_vulkan_warning);
config.set<bool>("gp_download", did_show_graphic_pack_download); config.set<bool>("gp_download", did_show_graphic_pack_download);
@ -543,6 +546,7 @@ void CemuConfig::Save(XMLConfigParser& parser)
auto usbdevices = config.set("EmulatedUsbDevices"); auto usbdevices = config.set("EmulatedUsbDevices");
usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue());
usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue());
usbdevices.set("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad.GetValue());
} }
GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId)

View file

@ -194,7 +194,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled);
template <> template <>
struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> { struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const PrecompiledShaderOption c, FormatContext &ctx) { auto format(const PrecompiledShaderOption c, FormatContext &ctx) const {
string_view name; string_view name;
switch (c) switch (c)
{ {
@ -209,7 +209,7 @@ struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> {
template <> template <>
struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> { struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const AccurateShaderMulOption c, FormatContext &ctx) { auto format(const AccurateShaderMulOption c, FormatContext &ctx) const {
string_view name; string_view name;
switch (c) switch (c)
{ {
@ -223,7 +223,7 @@ struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> {
template <> template <>
struct fmt::formatter<CPUMode> : formatter<string_view> { struct fmt::formatter<CPUMode> : formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const CPUMode c, FormatContext &ctx) { auto format(const CPUMode c, FormatContext &ctx) const {
string_view name; string_view name;
switch (c) switch (c)
{ {
@ -240,7 +240,7 @@ struct fmt::formatter<CPUMode> : formatter<string_view> {
template <> template <>
struct fmt::formatter<CPUModeLegacy> : formatter<string_view> { struct fmt::formatter<CPUModeLegacy> : formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const CPUModeLegacy c, FormatContext &ctx) { auto format(const CPUModeLegacy c, FormatContext &ctx) const {
string_view name; string_view name;
switch (c) switch (c)
{ {
@ -257,7 +257,7 @@ struct fmt::formatter<CPUModeLegacy> : formatter<string_view> {
template <> template <>
struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> { struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const CafeConsoleRegion v, FormatContext &ctx) { auto format(const CafeConsoleRegion v, FormatContext &ctx) const {
string_view name; string_view name;
switch (v) switch (v)
{ {
@ -413,7 +413,8 @@ struct CemuConfig
Vector2i pad_size{ -1,-1 }; Vector2i pad_size{ -1,-1 };
ConfigValue<bool> pad_maximized; ConfigValue<bool> pad_maximized;
ConfigValue<bool> check_update{false}; ConfigValue<bool> check_update{true};
ConfigValue<bool> receive_untested_updates{false};
ConfigValue<bool> save_screenshot{true}; ConfigValue<bool> save_screenshot{true};
ConfigValue<bool> did_show_vulkan_warning{false}; ConfigValue<bool> did_show_vulkan_warning{false};
@ -441,7 +442,7 @@ struct CemuConfig
ConfigValue<int> vsync{ 0 }; // 0 = off, 1+ = on depending on render backend ConfigValue<int> vsync{ 0 }; // 0 = off, 1+ = on depending on render backend
ConfigValue<bool> gx2drawdone_sync {true}; ConfigValue<bool> gx2drawdone_sync {true};
ConfigValue<bool> render_upside_down{ false }; ConfigValue<bool> render_upside_down{ false };
ConfigValue<bool> async_compile{ false }; ConfigValue<bool> async_compile{ true };
ConfigValue<bool> vk_accurate_barriers{ true }; ConfigValue<bool> vk_accurate_barriers{ true };
@ -520,6 +521,7 @@ struct CemuConfig
{ {
ConfigValue<bool> emulate_skylander_portal{false}; ConfigValue<bool> emulate_skylander_portal{false};
ConfigValue<bool> emulate_infinity_base{false}; ConfigValue<bool> emulate_infinity_base{false};
ConfigValue<bool> emulate_dimensions_toypad{false};
}emulated_usb_devices{}; }emulated_usb_devices{};
private: private:

View file

@ -112,10 +112,10 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args)
{ {
requireConsole(); requireConsole();
std::string versionStr; std::string versionStr;
#if EMULATOR_VERSION_MINOR == 0 #if EMULATOR_VERSION_PATCH == 0
versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX);
#else #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 #endif
std::cout << versionStr << std::endl; std::cout << versionStr << std::endl;
return false; // exit in main return false; // exit in main
@ -199,7 +199,11 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args)
std::string errorMsg; std::string errorMsg;
errorMsg.append("Error while trying to parse command line parameter:\n"); errorMsg.append("Error while trying to parse command line parameter:\n");
errorMsg.append(ex.what()); errorMsg.append(ex.what());
#if BOOST_OS_WINDOWS
wxMessageBox(errorMsg, "Parameter error", wxICON_ERROR); wxMessageBox(errorMsg, "Parameter error", wxICON_ERROR);
#else
std::cout << errorMsg << std::endl;
#endif
return false; return false;
} }

View file

@ -15,6 +15,9 @@
#if BOOST_OS_LINUX && HAS_WAYLAND #if BOOST_OS_LINUX && HAS_WAYLAND
#include "gui/helpers/wxWayland.h" #include "gui/helpers/wxWayland.h"
#endif #endif
#if __WXGTK__
#include <glib.h>
#endif
#include <wx/image.h> #include <wx/image.h>
#include <wx/filename.h> #include <wx/filename.h>

View file

@ -116,9 +116,11 @@ bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string&
#elif BOOST_OS_MACOS #elif BOOST_OS_MACOS
urlStr.append("&platform=macos_bundle_x86"); urlStr.append("&platform=macos_bundle_x86");
#elif #elif
#error Name for current platform is missing #error Name for current platform is missing
#endif #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_URL, urlStr.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);

View file

@ -115,7 +115,7 @@ void DownloadGraphicPacksWindow::UpdateThread()
curlDownloadFileState_t tempDownloadState; curlDownloadFileState_t tempDownloadState;
std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?"); std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?");
char temp[64]; 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(temp);
queryUrl.append("&"); 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) 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)

View file

@ -1,4 +1,4 @@
#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include "EmulatedUSBDeviceFrame.h"
#include <algorithm> #include <algorithm>
@ -8,14 +8,17 @@
#include "util/helpers/helpers.h" #include "util/helpers/helpers.h"
#include "Cafe/OS/libs/nsyshid/nsyshid.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h"
#include "Cafe/OS/libs/nsyshid/Dimensions.h"
#include "Common/FileStream.h" #include "Common/FileStream.h"
#include <wx/arrstr.h> #include <wx/arrstr.h>
#include <wx/button.h> #include <wx/button.h>
#include <wx/combobox.h>
#include <wx/checkbox.h> #include <wx/checkbox.h>
#include <wx/combobox.h> #include <wx/combobox.h>
#include <wx/filedlg.h> #include <wx/filedlg.h>
#include <wx/log.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/notebook.h> #include <wx/notebook.h>
#include <wx/panel.h> #include <wx/panel.h>
@ -29,7 +32,6 @@
#include <wx/wfstream.h> #include <wx/wfstream.h>
#include "resource/embedded/resources.h" #include "resource/embedded/resources.h"
#include "EmulatedUSBDeviceFrame.h"
EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent)
: wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition,
@ -44,6 +46,7 @@ EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent)
notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal"));
notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base")); notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base"));
notebook->AddPage(AddDimensionsPage(notebook), _("Dimensions Toypad"));
sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); sizer->Add(notebook, 1, wxEXPAND | wxALL, 2);
@ -120,8 +123,52 @@ wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook)
return panel; return panel;
} }
wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook)
wxStaticBox* box) {
auto* panel = new wxPanel(notebook);
auto* panel_sizer = new wxBoxSizer(wxVERTICAL);
auto* box = new wxStaticBox(panel, wxID_ANY, _("Dimensions Manager"));
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_emulateToypad =
new wxCheckBox(box, wxID_ANY, _("Emulate Dimensions Toypad"));
m_emulateToypad->SetValue(
GetConfig().emulated_usb_devices.emulate_dimensions_toypad);
m_emulateToypad->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
GetConfig().emulated_usb_devices.emulate_dimensions_toypad =
m_emulateToypad->IsChecked();
g_config.Save();
});
row->Add(m_emulateToypad, 1, wxEXPAND | wxALL, 2);
box_sizer->Add(row, 1, wxEXPAND | wxALL, 2);
auto* top_row = new wxBoxSizer(wxHORIZONTAL);
auto* bottom_row = new wxBoxSizer(wxHORIZONTAL);
auto* dummy = new wxStaticText(box, wxID_ANY, "");
top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2);
top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2);
top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2);
top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2);
top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 0);
bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2);
box_sizer->Add(top_row, 1, wxEXPAND | wxALL, 2);
box_sizer->Add(bottom_row, 1, wxEXPAND | wxALL, 2);
panel_sizer->Add(box_sizer, 1, wxEXPAND | wxALL, 2);
panel->SetSizerAndFit(panel_sizer);
return panel;
}
wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box)
{ {
auto* row = new wxBoxSizer(wxHORIZONTAL); auto* row = new wxBoxSizer(wxHORIZONTAL);
@ -184,6 +231,44 @@ wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumbe
return row; return row;
} }
wxBoxSizer* EmulatedUSBDeviceFrame::AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box)
{
auto* panel = new wxBoxSizer(wxVERTICAL);
auto* combo_row = new wxBoxSizer(wxHORIZONTAL);
m_dimensionSlots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
combo_row->Add(m_dimensionSlots[index], 1, wxEXPAND | wxALL, 2);
auto* move_button = new wxButton(box, wxID_ANY, _("Move"));
move_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
MoveMinifig(pad, index);
});
combo_row->Add(move_button, 1, wxEXPAND | wxALL, 2);
auto* button_row = new wxBoxSizer(wxHORIZONTAL);
auto* load_button = new wxButton(box, wxID_ANY, _("Load"));
load_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
LoadMinifig(pad, index);
});
auto* clear_button = new wxButton(box, wxID_ANY, _("Clear"));
clear_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
ClearMinifig(pad, index);
});
auto* create_button = new wxButton(box, wxID_ANY, _("Create"));
create_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
CreateMinifig(pad, index);
});
button_row->Add(clear_button, 1, wxEXPAND | wxALL, 2);
button_row->Add(create_button, 1, wxEXPAND | wxALL, 2);
button_row->Add(load_button, 1, wxEXPAND | wxALL, 2);
panel->Add(combo_row, 1, wxEXPAND | wxALL, 2);
panel->Add(button_row, 1, wxEXPAND | wxALL, 2);
return panel;
}
void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot)
{ {
wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "",
@ -307,8 +392,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot)
return; return;
m_filePath = saveFileDialog.GetPath(); m_filePath = saveFileDialog.GetPath();
if(!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) if (!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar))
{ {
wxMessageDialog errorMessage(this, "Failed to create file"); wxMessageDialog errorMessage(this, "Failed to create file");
errorMessage.ShowModal(); errorMessage.ShowModal();
@ -351,6 +436,80 @@ wxString CreateSkylanderDialog::GetFilePath() const
return m_filePath; return m_filePath;
} }
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);
}
}
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<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true));
if (!infFile)
{
wxMessageDialog errorMessage(this, "File Open Error");
errorMessage.ShowModal();
return;
}
std::array<uint8, nsyshid::INF_FIGURE_SIZE> 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);
}
CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot) CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot)
: wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150)) : wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150))
{ {
@ -447,76 +606,231 @@ wxString CreateInfinityFigureDialog::GetFilePath() const
return m_filePath; return m_filePath;
} }
void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index)
{ {
wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "",
"BIN files (*.bin)|*.bin", "Dimensions files (*.bin)|*.bin",
wxFD_OPEN | wxFD_FILE_MUST_EXIST); wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty())
return;
LoadMinifigPath(openFileDialog.GetPath(), pad, index);
}
void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index)
{
std::unique_ptr<FileStream> dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true));
if (!dim_file)
{ {
wxMessageDialog errorMessage(this, "File Okay Error"); wxMessageDialog errorMessage(this, "Failed to open minifig file");
errorMessage.ShowModal(); errorMessage.ShowModal();
return; return;
} }
LoadFigurePath(slot, openFileDialog.GetPath()); std::array<uint8, 0x2D * 0x04> file_data;
}
void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size())
{
std::unique_ptr<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true));
if (!infFile)
{ {
wxMessageDialog errorMessage(this, "File Open Error"); wxMessageDialog errorMessage(this, "Failed to read minifig file data");
errorMessage.ShowModal(); errorMessage.ShowModal();
return; return;
} }
std::array<uint8, nsyshid::INF_FIGURE_SIZE> fileData; ClearMinifig(pad, index);
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); uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index);
m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); m_dimensionSlots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id));
m_dimSlots[index] = id;
} }
void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index)
{ {
cemuLog_log(LogType::Force, "Create Figure: {}", slot); nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true);
CreateInfinityFigureDialog create_dlg(this, slot); m_dimensionSlots[index]->ChangeValue("None");
m_dimSlots[index] = std::nullopt;
}
void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index)
{
CreateDimensionFigureDialog create_dlg(this);
create_dlg.ShowModal(); create_dlg.ShowModal();
if (create_dlg.GetReturnCode() == 1) if (create_dlg.GetReturnCode() == 1)
{ {
LoadFigurePath(slot, create_dlg.GetFilePath()); LoadMinifigPath(create_dlg.GetFilePath(), pad, index);
} }
} }
void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index)
{ {
m_infinitySlots[slot]->ChangeValue("None"); if (!m_dimSlots[index])
nsyshid::g_infinitybase.RemoveFigure(slot); return;
}
void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() MoveDimensionFigureDialog move_dlg(this, index);
{ nsyshid::g_dimensionstoypad.TempRemove(index);
for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) move_dlg.ShowModal();
if (move_dlg.GetReturnCode() == 1)
{ {
std::string displayString; nsyshid::g_dimensionstoypad.MoveFigure(move_dlg.GetNewPad(), move_dlg.GetNewIndex(), pad, index);
if (auto sd = m_skySlots[i]) if (index != move_dlg.GetNewIndex())
{ {
auto [portalSlot, skyId, skyVar] = sd.value(); m_dimSlots[move_dlg.GetNewIndex()] = m_dimSlots[index];
displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); m_dimensionSlots[move_dlg.GetNewIndex()]->ChangeValue(m_dimensionSlots[index]->GetValue());
m_dimSlots[index] = std::nullopt;
m_dimensionSlots[index]->ChangeValue("None");
} }
else
{
displayString = "None";
}
m_skylanderSlots[i]->ChangeValue(displayString);
} }
else
{
nsyshid::g_dimensionstoypad.CancelRemove(index);
}
}
CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Dimensions Figure Creator"), wxDefaultPosition, wxSize(500, 200))
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* comboRow = new wxBoxSizer(wxHORIZONTAL);
auto* comboBox = new wxComboBox(this, wxID_ANY);
comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF));
wxArrayString filterlist;
for (const auto& it : nsyshid::g_dimensionstoypad.GetListMinifigs())
{
const uint32 figure = it.first;
comboBox->Append(it.second, reinterpret_cast<void*>(figure));
filterlist.Add(it.second);
}
comboBox->SetSelection(0);
bool enabled = comboBox->AutoComplete(filterlist);
comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2);
auto* figNumRow = new wxBoxSizer(wxHORIZONTAL);
wxIntegerValidator<uint32> 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) || longFigNum > 0xFFFF)
{
wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid");
idError.ShowModal();
this->EndModal(0);
}
uint16 figNum = longFigNum & 0xFFFF;
auto figure = nsyshid::g_dimensionstoypad.FindFigure(figNum);
wxString predefName = figure + ".bin";
wxFileDialog
saveFileDialog(this, _("Create Dimensions 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_dimensionstoypad.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum);
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<uint64>(comboBox->GetClientData(comboBox->GetSelection()));
if (fig_info != 0xFFFF)
{
const uint16 figNum = fig_info & 0xFFFF;
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 CreateDimensionFigureDialog::GetFilePath() const
{
return m_filePath;
}
MoveDimensionFigureDialog::MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex)
: wxDialog(parent, wxID_ANY, _("Dimensions Figure Mover"), wxDefaultPosition, wxSize(700, 300))
{
auto* sizer = new wxGridSizer(2, 5, 10, 10);
std::array<std::optional<uint32>, 7> ids = parent->GetCurrentMinifigs();
sizer->Add(AddMinifigSlot(2, 0, currentIndex, ids[0]), 1, wxALL, 5);
sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(1, 1, currentIndex, ids[1]), 1, wxALL, 5);
sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(3, 2, currentIndex, ids[2]), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(2, 3, currentIndex, ids[3]), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(2, 4, currentIndex, ids[4]), 1, wxALL, 5);
sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(3, 5, currentIndex, ids[5]), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(3, 6, currentIndex, ids[6]), 1, wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
}
wxBoxSizer* MoveDimensionFigureDialog::AddMinifigSlot(uint8 pad, uint8 index, uint8 currentIndex, std::optional<uint32> currentId)
{
auto* panel = new wxBoxSizer(wxVERTICAL);
auto* label = new wxStaticText(this, wxID_ANY, "None");
if (currentId)
label->SetLabel(nsyshid::g_dimensionstoypad.FindFigure(currentId.value()));
auto* moveButton = new wxButton(this, wxID_ANY, _("Move Here"));
if (index == currentIndex)
moveButton->SetLabelText("Pick up and Place");
moveButton->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
m_newPad = pad;
m_newIndex = index;
this->EndModal(1);
});
panel->Add(label, 1, wxALL, 5);
panel->Add(moveButton, 1, wxALL, 5);
return panel;
}
uint8 MoveDimensionFigureDialog::GetNewPad() const
{
return m_newPad;
}
uint8 MoveDimensionFigureDialog::GetNewIndex() const
{
return m_newIndex;
}
std::array<std::optional<uint32>, 7> EmulatedUSBDeviceFrame::GetCurrentMinifigs()
{
return m_dimSlots;
} }

View file

@ -17,33 +17,47 @@ class wxStaticBox;
class wxString; class wxString;
class wxTextCtrl; class wxTextCtrl;
class EmulatedUSBDeviceFrame : public wxFrame { class EmulatedUSBDeviceFrame : public wxFrame
{
public: public:
EmulatedUSBDeviceFrame(wxWindow* parent); EmulatedUSBDeviceFrame(wxWindow* parent);
~EmulatedUSBDeviceFrame(); ~EmulatedUSBDeviceFrame();
std::array<std::optional<uint32>, 7> GetCurrentMinifigs();
private: private:
wxCheckBox* m_emulatePortal; wxCheckBox* m_emulatePortal;
wxCheckBox* m_emulateBase; wxCheckBox* m_emulateBase;
wxCheckBox* m_emulateToypad;
std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots; std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots;
std::array<wxTextCtrl*, nsyshid::MAX_FIGURES> m_infinitySlots; std::array<wxTextCtrl*, nsyshid::MAX_FIGURES> m_infinitySlots;
std::array<wxTextCtrl*, 7> m_dimensionSlots;
std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots; std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots;
std::array<std::optional<uint32>, 7> m_dimSlots;
wxPanel* AddSkylanderPage(wxNotebook* notebook); wxPanel* AddSkylanderPage(wxNotebook* notebook);
wxPanel* AddInfinityPage(wxNotebook* notebook); wxPanel* AddInfinityPage(wxNotebook* notebook);
wxPanel* AddDimensionsPage(wxNotebook* notebook);
wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box);
wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box); wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box);
wxBoxSizer* AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box);
void LoadSkylander(uint8 slot); void LoadSkylander(uint8 slot);
void LoadSkylanderPath(uint8 slot, wxString path); void LoadSkylanderPath(uint8 slot, wxString path);
void CreateSkylander(uint8 slot); void CreateSkylander(uint8 slot);
void ClearSkylander(uint8 slot); void ClearSkylander(uint8 slot);
void UpdateSkylanderEdits();
void LoadFigure(uint8 slot); void LoadFigure(uint8 slot);
void LoadFigurePath(uint8 slot, wxString path); void LoadFigurePath(uint8 slot, wxString path);
void CreateFigure(uint8 slot); void CreateFigure(uint8 slot);
void ClearFigure(uint8 slot); void ClearFigure(uint8 slot);
void UpdateSkylanderEdits(); void LoadMinifig(uint8 pad, uint8 index);
void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index);
void CreateMinifig(uint8 pad, uint8 index);
void ClearMinifig(uint8 pad, uint8 index);
void MoveMinifig(uint8 pad, uint8 index);
}; };
class CreateSkylanderDialog : public wxDialog {
class CreateSkylanderDialog : public wxDialog
{
public: public:
explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const; wxString GetFilePath() const;
@ -52,11 +66,37 @@ class CreateSkylanderDialog : public wxDialog {
wxString m_filePath; wxString m_filePath;
}; };
class CreateInfinityFigureDialog : public wxDialog { class CreateInfinityFigureDialog : public wxDialog
{
public: public:
explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const; wxString GetFilePath() const;
protected: protected:
wxString m_filePath; wxString m_filePath;
};
class CreateDimensionFigureDialog : public wxDialog
{
public:
explicit CreateDimensionFigureDialog(wxWindow* parent);
wxString GetFilePath() const;
protected:
wxString m_filePath;
};
class MoveDimensionFigureDialog : public wxDialog
{
public:
explicit MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex);
uint8 GetNewPad() const;
uint8 GetNewIndex() const;
protected:
uint8 m_newIndex = 0;
uint8 m_newPad = 0;
private:
wxBoxSizer* AddMinifigSlot(uint8 pad, uint8 index, uint8 oldIndex, std::optional<uint32> currentId);
}; };

View file

@ -141,49 +141,66 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
second_row->SetFlexibleDirection(wxBOTH); second_row->SetFlexibleDirection(wxBOTH);
second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); 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; 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 = 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")); 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->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 = 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")); 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); second_row->Add(m_save_padwindow_position_size, 0, topflag, 5);
CountRowElement();
const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM; const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM;
m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence")); 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!")); 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); second_row->Add(m_discord_presence, 0, botflag, 5);
CountRowElement();
#ifndef ENABLE_DISCORD_RPC #ifndef ENABLE_DISCORD_RPC
m_discord_presence->Disable(); m_discord_presence->Disable();
#endif #endif
second_row->AddSpacer(10); //second_row->AddSpacer(10);
m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar")); 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")); 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); 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 = 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")); 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); second_row->Add(m_save_screenshot, 0, botflag, 5);
CountRowElement();
m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver")); 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.")); 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); second_row->Add(m_disable_screensaver, 0, botflag, 5);
CountRowElement();
// Enable/disable feral interactive gamemode // Enable/disable feral interactive gamemode
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode"));
m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed."));
second_row->Add(m_feral_gamemode, 0, botflag, 5); second_row->Add(m_feral_gamemode, 0, botflag, 5);
CountRowElement();
#endif #endif
// temporary workaround because feature crashes on macOS // temporary workaround because feature crashes on macOS
@ -191,6 +208,22 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
m_disable_screensaver->Enable(false); m_disable_screensaver->Enable(false);
#endif #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); box_sizer->Add(second_row, 0, wxEXPAND, 5);
} }
@ -899,6 +932,7 @@ void GeneralSettings2::StoreConfig()
config.fullscreen_menubar = m_fullscreen_menubar->IsChecked(); config.fullscreen_menubar = m_fullscreen_menubar->IsChecked();
config.check_update = m_auto_update->IsChecked(); config.check_update = m_auto_update->IsChecked();
config.save_screenshot = m_save_screenshot->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) #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
config.feral_gamemode = m_feral_gamemode->IsChecked(); config.feral_gamemode = m_feral_gamemode->IsChecked();
#endif #endif
@ -1536,6 +1570,7 @@ void GeneralSettings2::ApplyConfig()
m_fullscreen_menubar->SetValue(config.fullscreen_menubar); m_fullscreen_menubar->SetValue(config.fullscreen_menubar);
m_auto_update->SetValue(config.check_update); m_auto_update->SetValue(config.check_update);
m_receive_untested_releases->SetValue(config.receive_untested_updates);
m_save_screenshot->SetValue(config.save_screenshot); m_save_screenshot->SetValue(config.save_screenshot);
m_disable_screensaver->SetValue(config.disable_screensaver); m_disable_screensaver->SetValue(config.disable_screensaver);

View file

@ -41,7 +41,7 @@ private:
wxCheckBox* m_save_window_position_size; wxCheckBox* m_save_window_position_size;
wxCheckBox* m_save_padwindow_position_size; wxCheckBox* m_save_padwindow_position_size;
wxCheckBox* m_discord_presence, *m_fullscreen_menubar; wxCheckBox* m_discord_presence, *m_fullscreen_menubar;
wxCheckBox* m_auto_update, *m_save_screenshot; wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot;
wxCheckBox* m_disable_screensaver; wxCheckBox* m_disable_screensaver;
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
wxCheckBox* m_feral_gamemode; wxCheckBox* m_feral_gamemode;

View file

@ -458,10 +458,10 @@ void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event)
m_shown_graphic_pack = gp; 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_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_graphic_pack_description->GetGrandParent()->Layout();
m_right_panel->FitInside(); m_right_panel->FitInside();

View file

@ -1392,7 +1392,6 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
const auto outputPath = shortcutDialog.GetPath(); const auto outputPath = shortcutDialog.GetPath();
std::optional<fs::path> icon_path = std::nullopt; std::optional<fs::path> icon_path = std::nullopt;
[&]()
{ {
int iconIdx; int iconIdx;
int smallIconIdx; int smallIconIdx;
@ -1402,15 +1401,13 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
return; return;
} }
const auto icon = m_image_list->GetIcon(iconIdx); const auto icon = m_image_list->GetIcon(iconIdx);
PWSTR localAppData; const auto folder = ActiveSettings::GetUserDataPath("icons");
const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData); if (!fs::exists(folder) && !fs::create_directories(folder))
wxBitmap bitmap{};
auto folder = fs::path(localAppData) / "Cemu" / "icons";
if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder)))
{ {
cemuLog_log(LogType::Force, "Failed to create icon directory"); cemuLog_log(LogType::Force, "Failed to create icon directory");
return; return;
} }
wxBitmap bitmap{};
if (!bitmap.CopyFromIcon(icon)) if (!bitmap.CopyFromIcon(icon))
{ {
cemuLog_log(LogType::Force, "Failed to copy icon"); cemuLog_log(LogType::Force, "Failed to copy icon");
@ -1426,7 +1423,7 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
icon_path = std::nullopt; icon_path = std::nullopt;
cemuLog_log(LogType::Force, "Icon failed to save"); cemuLog_log(LogType::Force, "Icon failed to save");
} }
}(); }
IShellLinkW* shellLink; IShellLinkW* shellLink;
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink)); HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink));

View file

@ -8,7 +8,7 @@ template <>
struct fmt::formatter<wxString> : formatter<string_view> struct fmt::formatter<wxString> : formatter<string_view>
{ {
template <typename FormatContext> template <typename FormatContext>
auto format(const wxString& str, FormatContext& ctx) auto format(const wxString& str, FormatContext& ctx) const
{ {
return formatter<string_view>::format(str.c_str().AsChar(), ctx); return formatter<string_view>::format(str.c_str().AsChar(), ctx);
} }

View file

@ -114,6 +114,11 @@ InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position,
this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this); this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this);
} }
InputAPIAddWindow::~InputAPIAddWindow()
{
discard_thread_result();
}
void InputAPIAddWindow::on_add_button(wxCommandEvent& event) void InputAPIAddWindow::on_add_button(wxCommandEvent& event)
{ {
const auto selection = m_input_api->GetSelection(); const auto selection = m_input_api->GetSelection();
@ -159,6 +164,8 @@ std::unique_ptr<ControllerProviderSettings> InputAPIAddWindow::get_settings() co
void InputAPIAddWindow::on_api_selected(wxCommandEvent& event) void InputAPIAddWindow::on_api_selected(wxCommandEvent& event)
{ {
discard_thread_result();
if (m_input_api->GetSelection() == wxNOT_FOUND) if (m_input_api->GetSelection() == wxNOT_FOUND)
return; return;
@ -239,19 +246,25 @@ void InputAPIAddWindow::on_controller_dropdown(wxCommandEvent& event)
m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr); m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr);
m_controller_list->SetSelection(wxNOT_FOUND); m_controller_list->SetSelection(wxNOT_FOUND);
std::thread([this, provider, selected_uuid]() m_search_thread_data = std::make_unique<AsyncThreadData>();
std::thread([this, provider, selected_uuid](std::shared_ptr<AsyncThreadData> data)
{ {
auto available_controllers = provider->get_controllers(); auto available_controllers = provider->get_controllers();
wxCommandEvent event(wxControllersRefreshed); {
event.SetEventObject(m_controller_list); std::lock_guard lock{data->mutex};
event.SetClientObject(new wxCustomData(std::move(available_controllers))); if(!data->discardResult)
event.SetInt(provider->api()); {
event.SetString(selected_uuid); wxCommandEvent event(wxControllersRefreshed);
wxPostEvent(this, event); event.SetEventObject(m_controller_list);
event.SetClientObject(new wxCustomData(std::move(available_controllers)));
m_search_running = false; event.SetInt(provider->api());
}).detach(); event.SetString(selected_uuid);
wxPostEvent(this, event);
m_search_running = false;
}
}
}, m_search_thread_data).detach();
} }
void InputAPIAddWindow::on_controller_selected(wxCommandEvent& event) void InputAPIAddWindow::on_controller_selected(wxCommandEvent& event)
@ -301,3 +314,13 @@ void InputAPIAddWindow::on_controllers_refreshed(wxCommandEvent& event)
} }
} }
} }
void InputAPIAddWindow::discard_thread_result()
{
m_search_running = false;
if(m_search_thread_data)
{
std::lock_guard lock{m_search_thread_data->mutex};
m_search_thread_data->discardResult = true;
}
}

View file

@ -19,6 +19,7 @@ class InputAPIAddWindow : public wxDialog
{ {
public: public:
InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector<ControllerPtr>& controllers); InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector<ControllerPtr>& controllers);
~InputAPIAddWindow();
bool is_valid() const { return m_type.has_value() && m_controller != nullptr; } bool is_valid() const { return m_type.has_value() && m_controller != nullptr; }
InputAPI::Type get_type() const { return m_type.value(); } InputAPI::Type get_type() const { return m_type.value(); }
@ -38,6 +39,8 @@ private:
void on_controller_selected(wxCommandEvent& event); void on_controller_selected(wxCommandEvent& event);
void on_controllers_refreshed(wxCommandEvent& event); void on_controllers_refreshed(wxCommandEvent& event);
void discard_thread_result();
wxChoice* m_input_api; wxChoice* m_input_api;
wxComboBox* m_controller_list; wxComboBox* m_controller_list;
wxButton* m_ok_button; wxButton* m_ok_button;
@ -50,4 +53,10 @@ private:
std::vector<ControllerPtr> m_controllers; std::vector<ControllerPtr> m_controllers;
std::atomic_bool m_search_running = false; std::atomic_bool m_search_running = false;
struct AsyncThreadData
{
std::atomic_bool discardResult = false;
std::mutex mutex;
};
std::shared_ptr<AsyncThreadData> m_search_thread_data;
}; };

View file

@ -5,6 +5,7 @@
#include <wx/statline.h> #include <wx/statline.h>
#include <wx/textctrl.h> #include <wx/textctrl.h>
#include <wx/slider.h> #include <wx/slider.h>
#include <wx/checkbox.h>
#include "gui/helpers/wxControlObject.h" #include "gui/helpers/wxControlObject.h"
@ -131,11 +132,23 @@ VPADInputPanel::VPADInputPanel(wxWindow* parent)
} }
// Blow Mic // Blow Mic
row = 9; row = 8;
add_button_row(main_sizer, row, column, VPADController::kButtonId_Mic, _("blow mic")); add_button_row(main_sizer, row, column, VPADController::kButtonId_Mic, _("blow mic"));
row++; row++;
add_button_row(main_sizer, row, column, VPADController::kButtonId_Screen, _("show screen")); add_button_row(main_sizer, row, column, VPADController::kButtonId_Screen, _("show screen"));
row++;
auto toggleScreenText = new wxStaticText(this, wxID_ANY, _("toggle screen"));
main_sizer->Add(toggleScreenText,
wxGBPosition(row, column),
wxDefaultSpan,
wxALL | wxALIGN_CENTER_VERTICAL, 5);
m_togglePadViewCheckBox = new wxCheckBox(this, wxID_ANY, {}, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxString toggleScreenTT = _("Makes the \"show screen\" button toggle between the TV and gamepad screens");
m_togglePadViewCheckBox->SetToolTip(toggleScreenTT);
toggleScreenText->SetToolTip(toggleScreenTT);
main_sizer->Add(m_togglePadViewCheckBox, wxGBPosition(row,column+1), wxDefaultSpan, wxALL | wxEXPAND, 5);
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
@ -168,6 +181,8 @@ void VPADInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller,
{ {
InputPanel::on_timer(emulated_controller, controller_base); InputPanel::on_timer(emulated_controller, controller_base);
static_cast<VPADController*>(emulated_controller.get())->set_screen_toggle(m_togglePadViewCheckBox->GetValue());
if(emulated_controller) if(emulated_controller)
{ {
const auto axis = emulated_controller->get_axis(); const auto axis = emulated_controller->get_axis();
@ -182,3 +197,10 @@ void VPADInputPanel::OnVolumeChange(wxCommandEvent& event)
{ {
} }
void VPADInputPanel::load_controller(const EmulatedControllerPtr& controller)
{
InputPanel::load_controller(controller);
const bool isToggle = static_cast<VPADController*>(controller.get())->is_screen_active_toggle();
m_togglePadViewCheckBox->SetValue(isToggle);
}

View file

@ -4,6 +4,7 @@
#include "gui/input/panels/InputPanel.h" #include "gui/input/panels/InputPanel.h"
class wxInputDraw; class wxInputDraw;
class wxCheckBox;
class VPADInputPanel : public InputPanel class VPADInputPanel : public InputPanel
{ {
@ -11,11 +12,13 @@ public:
VPADInputPanel(wxWindow* parent); VPADInputPanel(wxWindow* parent);
void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override;
virtual void load_controller(const EmulatedControllerPtr& controller) override;
private: private:
void OnVolumeChange(wxCommandEvent& event); void OnVolumeChange(wxCommandEvent& event);
wxInputDraw* m_left_draw, * m_right_draw; wxInputDraw* m_left_draw, * m_right_draw;
wxCheckBox* m_togglePadViewCheckBox;
void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id); void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id);
void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id, const wxString &label); void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id, const wxString &label);

View file

@ -127,7 +127,7 @@ using EmulatedControllerPtr = std::shared_ptr<EmulatedController>;
template <> template <>
struct fmt::formatter<EmulatedController::Type> : formatter<string_view> { struct fmt::formatter<EmulatedController::Type> : formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(EmulatedController::Type v, FormatContext& ctx) { auto format(EmulatedController::Type v, FormatContext& ctx) const {
switch (v) switch (v)
{ {
case EmulatedController::Type::VPAD: return formatter<string_view>::format("Wii U Gamepad", ctx); case EmulatedController::Type::VPAD: return formatter<string_view>::format("Wii U Gamepad", ctx);

View file

@ -686,3 +686,14 @@ bool VPADController::set_default_mapping(const std::shared_ptr<ControllerBase>&
return mapping_updated; return mapping_updated;
} }
void VPADController::load(const pugi::xml_node& node)
{
if (const auto value = node.child("toggle_display"))
m_screen_active_toggle = ConvertString<bool>(value.child_value());
}
void VPADController::save(pugi::xml_node& node)
{
node.append_child("toggle_display").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (int)m_screen_active_toggle).c_str());
}

View file

@ -66,6 +66,8 @@ public:
bool is_mic_active() { return m_mic_active; } bool is_mic_active() { return m_mic_active; }
bool is_screen_active() { return m_screen_active; } bool is_screen_active() { return m_screen_active; }
bool is_screen_active_toggle() { return m_screen_active_toggle; }
void set_screen_toggle(bool toggle) {m_screen_active_toggle = toggle;}
static std::string_view get_button_name(ButtonId id); static std::string_view get_button_name(ButtonId id);
@ -86,9 +88,13 @@ public:
bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override; bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override;
void load(const pugi::xml_node& node) override;
void save(pugi::xml_node& node) override;
private: private:
bool m_mic_active = false; bool m_mic_active = false;
bool m_screen_active = false; bool m_screen_active = false;
bool m_screen_active_toggle = false;
uint32be m_last_holdvalue = 0; uint32be m_last_holdvalue = 0;
std::chrono::high_resolution_clock::time_point m_last_hold_change{}, m_last_pulse{}; std::chrono::high_resolution_clock::time_point m_last_hold_change{}, m_last_pulse{};

View file

@ -73,8 +73,8 @@ END
#define str(s) #s #define str(s) #s
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0 FILEVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0
PRODUCTVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0 PRODUCTVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -94,7 +94,7 @@ BEGIN
VALUE "LegalCopyright", "Team Cemu" VALUE "LegalCopyright", "Team Cemu"
VALUE "OriginalFilename", "Cemu.exe" VALUE "OriginalFilename", "Cemu.exe"
VALUE "ProductName", "Cemu" 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
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -50,7 +50,6 @@ add_library(CemuUtil
MemMapper/MemMapper.h MemMapper/MemMapper.h
SystemInfo/SystemInfo.cpp SystemInfo/SystemInfo.cpp
SystemInfo/SystemInfo.h SystemInfo/SystemInfo.h
ThreadPool/ThreadPool.cpp
ThreadPool/ThreadPool.h ThreadPool/ThreadPool.h
tinyxml2/tinyxml2.cpp tinyxml2/tinyxml2.cpp
tinyxml2/tinyxml2.h tinyxml2/tinyxml2.h

View file

@ -194,7 +194,7 @@ namespace robin_hood {
// workaround missing "is_trivially_copyable" in g++ < 5.0 // workaround missing "is_trivially_copyable" in g++ < 5.0
// See https://stackoverflow.com/a/31798726/48181 // See https://stackoverflow.com/a/31798726/48181
#if defined(__GNUC__) && __GNUC__ < 5 #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__)
# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
#else #else
# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value

View file

@ -1,29 +1,6 @@
#include "crc32.h" #include "crc32.h"
#if defined(_MSC_VER) || defined(__MINGW32__) constexpr uint32 Crc32Lookup[8][256] =
#define __LITTLE_ENDIAN 1234
#define __BIG_ENDIAN 4321
#define __BYTE_ORDER __LITTLE_ENDIAN
#include <xmmintrin.h>
#ifdef __MINGW32__
#define PREFETCH(location) __builtin_prefetch(location)
#else
#define PREFETCH(location) _mm_prefetch(location, _MM_HINT_T0)
#endif
#else
// defines __BYTE_ORDER as __LITTLE_ENDIAN or __BIG_ENDIAN
#include <sys/param.h>
#ifdef __GNUC__
#define PREFETCH(location) __builtin_prefetch(location)
#else
// no prefetching
#define PREFETCH(location) ;
#endif
#endif
unsigned int Crc32Lookup[8][256] =
{ {
{ {
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3, 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
@ -301,20 +278,7 @@ unsigned int Crc32Lookup[8][256] =
} }
}; };
/// swap endianess uint32 crc32_calc_slice_by_8(uint32 previousCrc32, const void* data, size_t length)
static inline uint32_t swap(uint32_t x)
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#else
return (x >> 24) |
((x >> 8) & 0x0000FF00) |
((x << 8) & 0x00FF0000) |
(x << 24);
#endif
}
unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data, int length)
{ {
uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF
const uint32_t* current = (const uint32_t*)data; const uint32_t* current = (const uint32_t*)data;
@ -323,7 +287,7 @@ unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data,
while (length >= 8) while (length >= 8)
{ {
if constexpr (std::endian::native == std::endian::big){ if constexpr (std::endian::native == std::endian::big){
uint32_t one = *current++ ^ swap(crc); uint32_t one = *current++ ^ _swapEndianU32(crc);
uint32_t two = *current++; uint32_t two = *current++;
crc = Crc32Lookup[0][two & 0xFF] ^ crc = Crc32Lookup[0][two & 0xFF] ^
Crc32Lookup[1][(two >> 8) & 0xFF] ^ Crc32Lookup[1][(two >> 8) & 0xFF] ^
@ -348,13 +312,14 @@ unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data,
Crc32Lookup[7][one & 0xFF]; Crc32Lookup[7][one & 0xFF];
} }
else { else {
cemu_assert(false); static_assert(std::endian::native == std::endian::big || std::endian::native == std::endian::little,
"Platform byte-order is unsupported");
} }
length -= 8; length -= 8;
} }
const uint8_t* currentChar = (const uint8_t*)current; const uint8* currentChar = (const uint8*)current;
// remaining 1 to 7 bytes (standard algorithm) // remaining 1 to 7 bytes (standard algorithm)
while (length-- != 0) while (length-- != 0)
crc = (crc >> 8) ^ Crc32Lookup[0][(crc & 0xFF) ^ *currentChar++]; crc = (crc >> 8) ^ Crc32Lookup[0][(crc & 0xFF) ^ *currentChar++];
@ -362,20 +327,20 @@ unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data,
return ~crc; // same as crc ^ 0xFFFFFFFF return ~crc; // same as crc ^ 0xFFFFFFFF
} }
unsigned int crc32_calc(unsigned int c, const void* data, int length) uint32 crc32_calc(uint32 c, const void* data, size_t length)
{ {
if (length >= 16) if (length >= 16)
{ {
return crc32_calc_slice_by_8(c, data, length); return crc32_calc_slice_by_8(c, data, length);
} }
unsigned char* p = (unsigned char*)data; const uint8* p = (const uint8*)data;
if (length == 0) if (length == 0)
return c; return c;
c ^= 0xFFFFFFFF; c ^= 0xFFFFFFFF;
while (length) while (length)
{ {
unsigned char temp = *p; uint8 temp = *p;
temp ^= (unsigned char)c; temp ^= (uint8)c;
c = (c >> 8) ^ Crc32Lookup[0][temp]; c = (c >> 8) ^ Crc32Lookup[0][temp];
// next // next
length--; length--;

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
unsigned int crc32_calc(unsigned int c, const void* data, int length); uint32 crc32_calc(uint32 c, const void* data, size_t length);
inline unsigned int crc32_calc(const void* data, int length) inline uint32 crc32_calc(const void* data, size_t length)
{ {
return crc32_calc(0, data, length); return crc32_calc(0, data, length);
} }