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
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: 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:
workflow_call:
inputs:
deploymode:
next_version_major:
required: false
type: string
experimentalversion:
next_version_minor:
required: false
type: string
@ -24,30 +24,17 @@ jobs:
submodules: "recursive"
fetch-depth: 0
- name: "Fetch full history for vcpkg submodule"
run: |
cd dependencies/vcpkg
git fetch --unshallow
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release"
- name: Setup debug mode parameters (for continous build)
if: ${{ inputs.deploymode != 'release' }}
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "BUILD_MODE=debug" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is debug"
- name: Setup version for experimental
if: ${{ inputs.experimentalversion != '' }}
run: |
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
- name: "Install system dependencies"
run: |
@ -86,12 +73,10 @@ jobs:
cmake --build build
- name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: mv bin/Cemu_release bin/Cemu
- name: Upload artifact
uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-linux-x64
path: ./bin/Cemu
@ -133,29 +118,17 @@ jobs:
with:
submodules: "recursive"
- name: "Fetch full history for vcpkg submodule"
run: |
cd dependencies/vcpkg
git fetch --unshallow
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is release"
- name: Setup debug mode parameters (for continous build)
if: ${{ inputs.deploymode != 'release' }}
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is debug"
- name: Setup version for experimental
if: ${{ inputs.experimentalversion != '' }}
run: |
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
@ -194,61 +167,50 @@ jobs:
cmake --build . --config ${{ env.BUILD_MODE }}
- name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: Rename-Item bin/Cemu_release.exe Cemu.exe
- name: Upload artifact
uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-windows-x64
path: ./bin/Cemu.exe
build-macos-intel:
runs-on: macos-12
build-macos:
runs-on: macos-14
steps:
- name: "Checkout repo"
uses: actions/checkout@v4
with:
submodules: "recursive"
- name: "Fetch full history for vcpkg submodule"
run: |
cd dependencies/vcpkg
git fetch --unshallow
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release"
- name: Setup debug mode parameters (for continous build)
if: ${{ inputs.deploymode != 'release' }}
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "BUILD_MODE=debug" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is debug"
- name: Setup version for experimental
if: ${{ inputs.experimentalversion != '' }}
run: |
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
- name: "Install system dependencies"
run: |
brew update
brew install llvm@15 ninja nasm automake libtool cmake python3 ninja
brew install ninja nasm automake libtool
- name: "Build and install molten-vk"
- name: "Install molten-vk"
run: |
git clone https://github.com/KhronosGroup/MoltenVK.git
cd MoltenVK
git checkout bf097edc74ec3b6dfafdcd5a38d3ce14b11952d6
./fetchDependencies --macos
make macos
make install
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar
tar xf MoltenVK-macos.tar
sudo mkdir -p /usr/local/lib
sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
- name: "Bootstrap vcpkg"
run: |
@ -274,9 +236,8 @@ jobs:
cd build
cmake .. ${{ env.BUILD_FLAGS }} \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DMACOS_BUNDLE=ON \
-DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \
-G Ninja
- name: "Build Cemu"
@ -284,7 +245,6 @@ jobs:
cmake --build build
- name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: |
mkdir bin/Cemu_app
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
@ -298,105 +258,6 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-macos-x64
path: ./bin/Cemu.dmg
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:
build:
uses: ./.github/workflows/build.yml
with:
deploymode: release
experimentalversion: 999999

View file

@ -1,20 +1,83 @@
name: Deploy experimental release
on:
workflow_dispatch:
inputs:
changelog0:
description: 'Enter the changelog lines for this release. Each line is a feature / bullet point. Do not use dash.'
required: true
type: string
changelog1:
description: 'Feature 2'
required: false
type: string
changelog2:
description: 'Feature 3'
required: false
type: string
changelog3:
description: 'Feature 4'
required: false
type: string
changelog4:
description: 'Feature 5'
required: false
type: string
changelog5:
description: 'Feature 6'
required: false
type: string
changelog6:
description: 'Feature 7'
required: false
type: string
changelog7:
description: 'Feature 8'
required: false
type: string
changelog8:
description: 'Feature 9'
required: false
type: string
changelog9:
description: 'Feature 10'
required: false
type: string
jobs:
calculate-version:
name: Calculate Version
uses: ./.github/workflows/determine_release_version.yml
call-release-build:
uses: ./.github/workflows/build.yml
needs: calculate-version
with:
deploymode: release
experimentalversion: ${{ github.run_number }}
next_version_major: ${{ needs.calculate-version.outputs.next_version_major }}
next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }}
deploy:
name: Deploy experimental release
runs-on: ubuntu-22.04
needs: call-release-build
needs: [call-release-build, calculate-version]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: generate_changelog
run: |
CHANGELOG=""
if [ -n "${{ github.event.inputs.changelog0 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog0 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog1 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog1 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog2 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog2 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog3 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog3 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog4 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog4 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog5 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog5 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog6 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog6 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog7 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog7 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog8 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog8 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog9 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog9 }}\n"; fi
echo -e "$CHANGELOG"
echo "RELEASE_BODY=$CHANGELOG" >> $GITHUB_ENV
- uses: actions/download-artifact@v4
with:
name: cemu-bin-linux-x64
@ -40,15 +103,13 @@ jobs:
mkdir upload
sudo apt install zip
- name: Get version
- name: Set version dependent vars
run: |
echo "Experimental version: ${{ github.run_number }}"
ls
gcc -o getversion .github/getversion.cpp
./getversion
echo "Cemu CI version: $(./getversion)"
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
echo "CEMU_VERSION=$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
echo "Version: ${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
- name: Create release from windows-bin
run: |
@ -83,4 +144,8 @@ jobs:
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz
tar xvzf ghr.tar.gz; rm ghr.tar.gz
echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}"
ghr_v0.15.0_linux_amd64/ghr -prerelease -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }} (Experimental)" -b "Cemu experimental release" "v${{ env.CEMU_VERSION }}" ./upload
CHANGELOG_UNESCAPED=$(printf "%s\n" "${{ env.RELEASE_BODY }}" | sed 's/\\n/\n/g')
RELEASE_BODY=$(printf "%s\n%s" \
"**Changelog:**" \
"$CHANGELOG_UNESCAPED")
ghr_v0.15.0_linux_amd64/ghr -draft -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "$RELEASE_BODY" "v${{ env.CEMU_VERSION }}" ./upload

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"]
path = dependencies/vcpkg
url = https://github.com/microsoft/vcpkg
shallow = true
shallow = false
[submodule "dependencies/Vulkan-Headers"]
path = dependencies/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers
shallow = true
[submodule "dependencies/imgui"]
path = dependencies/imgui
url = https://github.com/ocornut/imgui
shallow = true

View file

@ -16,11 +16,11 @@
- [Compiling Errors](#compiling-errors)
- [Building Errors](#building-errors)
- [macOS](#macos)
- [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used)
- [Installing brew](#installing-brew)
- [Installing Dependencies](#installing-dependencies)
- [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang)
- [Updating Cemu and source code](#updating-cemu-and-source-code)
- [Installing Tool Dependencies](#installing-tool-dependencies)
- [Installing Library Dependencies](#installing-library-dependencies)
- [Build Cemu using CMake](#build-cemu-using-cmake)
- [Updating Cemu and source code](#updating-cemu-and-source-code)
## Windows
@ -141,31 +141,41 @@ If you are getting a different error than any of the errors listed above, you ma
## macOS
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and
below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics
API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the
Molten-VK compatibility layer
### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used
You can skip this section if you have an Intel Mac. Every time you compile, you need to perform steps 2.
1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once
2. `arch -x86_64 zsh` # run an x64 shell
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below
don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that
you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't
supported on macOS, so Vulkan must be used through the Molten-VK compatibility layer.
### Installing brew
1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env
2. Set up the Homebrew shell environment:
1. **On an Intel Mac:** `eval "$(/usr/local/Homebrew/bin/brew shellenv)"`
2. **On an Apple Silicon Mac:** eval `"$(/opt/homebrew/bin/brew shellenv)"`
### Installing Dependencies
### Installing Tool Dependencies
`brew install boost git cmake llvm ninja nasm molten-vk automake libtool`
The native versions of these can be used regardless of what type of Mac you have.
`brew install git cmake ninja nasm automake libtool`
### Installing Library Dependencies
**On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used to install these dependencies:**
1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once
2. `arch -x86_64 zsh` # run an x64 shell
3. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
4. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"`
Then install the dependencies:
`brew install boost molten-vk`
### Build Cemu using CMake
### Build Cemu using CMake and Clang
1. `git clone --recursive https://github.com/cemu-project/Cemu`
2. `cd Cemu`
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja`
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Ninja`
4. `cmake --build build`
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.

View file

@ -2,20 +2,39 @@ cmake_minimum_required(VERSION 3.21.1)
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version
if (EXPERIMENTAL_VERSION)
add_definitions(-DEMULATOR_VERSION_MINOR=${EXPERIMENTAL_VERSION})
execute_process(
COMMAND git log --format=%h -1
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
endif()
# used by CI script to set version:
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
set(EMULATOR_VERSION_MINOR "0" CACHE STRING "")
set(EMULATOR_VERSION_PATCH "0" CACHE STRING "")
execute_process(
COMMAND git log --format=%h -1
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
if (ENABLE_VCPKG)
# check if vcpkg is shallow and unshallow it if necessary
execute_process(
COMMAND git rev-parse --is-shallow-repository
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg
OUTPUT_VARIABLE is_vcpkg_shallow
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(is_vcpkg_shallow STREQUAL "true")
message(STATUS "vcpkg is shallow. Unshallowing it now...")
execute_process(
COMMAND git fetch --unshallow
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
)
endif()
if(UNIX AND NOT APPLE)
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux")
elseif(APPLE)
@ -44,6 +63,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_definitions($<$<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)
# enable link time optimization for release builds
@ -69,6 +92,7 @@ endif()
if (APPLE)
enable_language(OBJC OBJCXX)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
endif()
if (UNIX AND NOT APPLE)
@ -198,7 +222,7 @@ if (ENABLE_CUBEB)
option(BUILD_TOOLS "" OFF)
option(BUNDLE_SPEEX "" OFF)
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>")
add_library(cubeb::cubeb ALIAS cubeb)
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"
)
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
if (CMAKE_OSX_ARCHITECTURES)
set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES})
else()
set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
endif()
if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64")
set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86")
include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES})
target_sources(ih264d PRIVATE
@ -140,7 +146,7 @@ target_sources(ih264d PRIVATE
"decoder/x86/ih264d_function_selector_sse42.c"
"decoder/x86/ih264d_function_selector_ssse3.c"
)
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64")
enable_language( C CXX ASM )
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
@ -178,7 +184,7 @@ target_sources(ih264d PRIVATE
)
target_compile_options(ih264d PRIVATE -DARMV8)
else()
message(FATAL_ERROR "ih264d unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}")
endif()
if(MSVC)

View file

@ -10,6 +10,8 @@ curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/re
chmod a+x mkappimage.AppImage
curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
chmod a+x linuxdeploy-plugin-gtk.sh
curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh"
chmod a+x linuxdeploy-plugin-checkrt.sh
if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then
sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh
@ -39,7 +41,8 @@ export NO_STRIP=1
-d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
-i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \
-e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \
--plugin gtk
--plugin gtk \
--plugin checkrt
if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then
GITVERSION=experimental
@ -47,7 +50,6 @@ fi
echo "Cemu Version Cemu-${GITVERSION}"
rm AppDir/usr/lib/libwayland-client.so.0
cp /lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/lib/
echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-hooks/linuxdeploy-plugin-gtk.sh
VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir

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}")
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
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 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()
set_target_properties(CemuBin PROPERTIES

View file

@ -465,6 +465,8 @@ add_library(CemuCafe
OS/libs/nsyshid/BackendLibusb.h
OS/libs/nsyshid/BackendWindowsHID.cpp
OS/libs/nsyshid/BackendWindowsHID.h
OS/libs/nsyshid/Dimensions.cpp
OS/libs/nsyshid/Dimensions.h
OS/libs/nsyshid/Infinity.cpp
OS/libs/nsyshid/Infinity.h
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
GamePatch_scan();
}
LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled();
LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled();
InfoLog_PrintActiveSettings();
Latte_Start();
// check for debugger entrypoint bp

View file

@ -3,6 +3,8 @@
#include <boost/container/small_vector.hpp>
#include "../fsc.h"
// path parser and utility class for Wii U paths
// optimized to be allocation-free for common path lengths
class FSCPath
@ -119,9 +121,7 @@ public:
template<typename F>
class FSAFileTree
{
public:
private:
private:
enum NODETYPE : uint8
{
@ -133,6 +133,7 @@ private:
{
std::string name;
std::vector<node_t*> subnodes;
size_t fileSize;
F* custom;
NODETYPE type;
};
@ -179,13 +180,54 @@ private:
return newNode;
}
class DirectoryIterator : public FSCVirtualFile
{
public:
DirectoryIterator(node_t* node)
: m_node(node), m_subnodeIndex(0)
{
}
sint32 fscGetType() override
{
return FSC_TYPE_DIRECTORY;
}
bool fscDirNext(FSCDirEntry* dirEntry) override
{
if (m_subnodeIndex >= m_node->subnodes.size())
return false;
const node_t* subnode = m_node->subnodes[m_subnodeIndex];
strncpy(dirEntry->path, subnode->name.c_str(), sizeof(dirEntry->path) - 1);
dirEntry->path[sizeof(dirEntry->path) - 1] = '\0';
dirEntry->isDirectory = subnode->type == FSAFileTree::NODETYPE_DIRECTORY;
dirEntry->isFile = subnode->type == FSAFileTree::NODETYPE_FILE;
dirEntry->fileSize = subnode->type == FSAFileTree::NODETYPE_FILE ? subnode->fileSize : 0;
++m_subnodeIndex;
return true;
}
bool fscRewindDir() override
{
m_subnodeIndex = 0;
return true;
}
private:
node_t* m_node;
size_t m_subnodeIndex;
};
public:
FSAFileTree()
{
rootNode.type = NODETYPE_DIRECTORY;
}
bool addFile(std::string_view path, F* custom)
bool addFile(std::string_view path, size_t fileSize, F* custom)
{
FSCPath p(path);
if (p.GetNodeCount() == 0)
@ -196,6 +238,7 @@ public:
return false; // node already exists
// add file node
node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1));
fileNode->fileSize = fileSize;
fileNode->custom = custom;
return true;
}
@ -214,6 +257,20 @@ public:
return true;
}
bool getDirectory(std::string_view path, FSCVirtualFile*& dirIterator)
{
FSCPath p(path);
if (p.GetNodeCount() == 0)
return false;
node_t* node = getByNodePath(p, p.GetNodeCount(), false);
if (node == nullptr)
return false;
if (node->type != NODETYPE_DIRECTORY)
return false;
dirIterator = new DirectoryIterator(node);
return true;
}
bool removeFile(std::string_view path)
{
FSCPath p(path);

View file

@ -212,4 +212,4 @@ bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTarg
// redirect device
void fscDeviceRedirect_map();
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority);
void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority);

View file

@ -11,7 +11,7 @@ struct RedirectEntry
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
RedirectEntry* existingEntry;
@ -24,7 +24,7 @@ void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& t
delete existingEntry;
}
RedirectEntry* entry = new RedirectEntry(targetFilePath, priority);
redirectTree.addFile(virtualSourcePath, entry);
redirectTree.addFile(virtualSourcePath, fileSize, entry);
}
class fscDeviceTypeRedirect : public fscDeviceC
@ -32,8 +32,15 @@ class fscDeviceTypeRedirect : public fscDeviceC
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
{
RedirectEntry* redirectionEntry;
if (redirectTree.getFile(path, redirectionEntry))
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && redirectTree.getFile(path, redirectionEntry))
return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus);
FSCVirtualFile* dirIterator;
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR) && redirectTree.getDirectory(path, dirIterator))
return dirIterator;
return nullptr;
}

View file

@ -830,7 +830,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC
{
virtualMountPath = fs::path("vol/content/") / virtualMountPath;
}
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority);
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority);
}
}
}

View file

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

View file

@ -989,8 +989,6 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
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)
{
cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice
@ -1000,38 +998,31 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
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 ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL);
const auto [vpad0Active, vpad0Toggle] = getVPADScreenActive(0);
const auto [vpad1Active, vpad1Toggle] = getVPADScreenActive(1);
bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed;
bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC;
const bool altScreenRequested = (!ctrlPressed && tabPressed) || vpad0Active || vpad1Active;
const bool togglePressed = (ctrlPressed && tabPressed) || vpad0Toggle || vpad1Toggle;
static bool togglePressedLast = false;
if (ctrlPressed && tabPressed)
{
if (ctrlTabHotkeyPressed == false)
{
alwaysDisplayDRC = !alwaysDisplayDRC;
ctrlTabHotkeyPressed = true;
}
}
else
ctrlTabHotkeyPressed = false;
bool& isDRCPrimary = LatteGPUState.isDRCPrimary;
if (alwaysDisplayDRC)
showDRC = !tabPressed;
if(togglePressed && !togglePressedLast)
isDRCPrimary = !isDRCPrimary;
togglePressedLast = togglePressed;
if (!showDRC)
{
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;
}
}
bool showDRC = swkbd_hasKeyboardInputHook() == false && (isDRCPrimary ^ altScreenRequested);
if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive())
LatteRenderTarget_copyToBackbuffer(texView, true);

View file

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

View file

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

View file

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

View file

@ -26,6 +26,9 @@ struct SwapchainInfoVk
bool IsValid() const;
void WaitAvailableFence();
void ResetAvailableFence() const;
bool AcquireImage();
// retrieve semaphore of last acquire for submitting a wait operation
// only one wait operation must be submitted per acquire (which submits a single signal operation)
@ -68,6 +71,9 @@ struct SwapchainInfoVk
VkSwapchainKHR m_swapchain{};
Vector2i m_desiredExtent{};
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)
@ -81,6 +87,8 @@ struct SwapchainInfoVk
private:
uint32 m_acquireIndex = 0;
std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex
VkFence m_imageAvailableFence{};
VkFence m_awaitableFence = VK_NULL_HANDLE;
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
std::array<uint32, 2> m_swapchainQueueFamilyIndices;

View file

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

View file

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

View file

@ -7,6 +7,7 @@
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h"
@ -29,6 +30,7 @@
#include <glslang/Public/ShaderLang.h>
#include <wx/msgdlg.h>
#include <wx/intl.h> // for localization
#ifndef VK_API_VERSION_MAJOR
#define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU)
@ -45,7 +47,9 @@ const std::vector<const char*> kOptionalDeviceExtensions =
VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet
VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME
VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME,
VK_KHR_PRESENT_WAIT_EXTENSION_NAME,
VK_KHR_PRESENT_ID_EXTENSION_NAME
};
const std::vector<const char*> kRequiredDeviceExtensions =
@ -123,7 +127,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
VkApplicationInfo app_info{};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = EMULATOR_NAME;
app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR);
app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH);
app_info.pEngineName = EMULATOR_NAME;
app_info.engineVersion = app_info.applicationVersion;
app_info.apiVersion = apiVersion;
@ -250,12 +254,24 @@ void VulkanRenderer::GetDeviceFeatures()
pcc.pNext = prevStruct;
prevStruct = &pcc;
VkPhysicalDevicePresentIdFeaturesKHR pidf{};
pidf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
pidf.pNext = prevStruct;
prevStruct = &pidf;
VkPhysicalDevicePresentWaitFeaturesKHR pwf{};
pwf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR;
pwf.pNext = prevStruct;
prevStruct = &pwf;
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
physicalDeviceFeatures2.pNext = prevStruct;
vkGetPhysicalDeviceFeatures2(m_physicalDevice, &physicalDeviceFeatures2);
cemuLog_log(LogType::Force, "Vulkan: present_wait extension: {}", (pwf.presentWait && pidf.presentId) ? "supported" : "unsupported");
/* Get Vulkan device properties and limits */
VkPhysicalDeviceFloatControlsPropertiesKHR pfcp{};
prevStruct = nullptr;
@ -285,7 +301,7 @@ void VulkanRenderer::GetDeviceFeatures()
cemuLog_log(LogType::Force, "VK_EXT_pipeline_creation_cache_control not supported. Cannot use asynchronous shader and pipeline compilation");
// if async shader compilation is enabled show warning message
if (GetConfig().async_compile)
wxMessageBox(_("The currently installed graphics driver does not support the Vulkan extension necessary for asynchronous shader compilation. Asynchronous compilation cannot be used.\n \nRequired extension: VK_EXT_pipeline_creation_cache_control\n\nInstalling the latest graphics driver may solve this error."), _("Information"), wxOK | wxCENTRE);
LatteOverlay_pushNotification(_("Async shader compile is enabled but not supported by the graphics driver\nCemu will use synchronous compilation which can cause additional stutter").utf8_string(), 10000);
}
if (!m_featureControl.deviceExtensions.custom_border_color_without_format)
{
@ -337,7 +353,7 @@ VulkanRenderer::VulkanRenderer()
VkApplicationInfo app_info{};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = EMULATOR_NAME;
app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR);
app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH);
app_info.pEngineName = EMULATOR_NAME;
app_info.engineVersion = app_info.applicationVersion;
app_info.apiVersion = apiVersion;
@ -488,6 +504,24 @@ VulkanRenderer::VulkanRenderer()
customBorderColorFeature.customBorderColors = VK_TRUE;
customBorderColorFeature.customBorderColorWithoutFormat = VK_TRUE;
}
// enable VK_KHR_present_id
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeature{};
if(m_featureControl.deviceExtensions.present_wait)
{
presentIdFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
presentIdFeature.pNext = deviceExtensionFeatures;
deviceExtensionFeatures = &presentIdFeature;
presentIdFeature.presentId = VK_TRUE;
}
// enable VK_KHR_present_wait
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeature{};
if(m_featureControl.deviceExtensions.present_wait)
{
presentWaitFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR;
presentWaitFeature.pNext = deviceExtensionFeatures;
deviceExtensionFeatures = &presentWaitFeature;
presentWaitFeature.presentWait = VK_TRUE;
}
std::vector<const char*> 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);
if (m_featureControl.deviceExtensions.shader_float_controls)
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{};
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.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
info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME);
// check for framedebuggers
info.debugMarkersSupported = false;
@ -1853,6 +1892,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers()
if (fenceStatus == VK_SUCCESS)
{
ProcessDestructionQueue();
m_uniformVarBufferReadIndex = m_cmdBufferUniformRingbufIndices[m_commandBufferSyncIndex];
m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size();
memoryManager->cleanupBuffers(m_countCommandBufferFinished);
m_countCommandBufferFinished++;
@ -1946,6 +1986,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer...");
WaitForNextFinishedCommandBuffer();
}
m_cmdBufferUniformRingbufIndices[nextCmdBufferIndex] = m_cmdBufferUniformRingbufIndices[m_commandBufferIndex];
m_commandBufferIndex = nextCmdBufferIndex;
@ -2198,6 +2239,8 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
else
{
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)
{
// RGBA formats
@ -2439,6 +2482,11 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
// used by Color Splash and Resident Evil
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format?
formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify
case Latte::E_GX2SURFFMT::R32_X8_FLOAT:
// seen in Disney Infinity 3.0
formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT;
formatInfoOut->decoder = TextureDecoder_NullData64::getInstance();
break;
default:
cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format);
cemu_assert_debug(false);
@ -2686,11 +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);
}
const size_t currentFrameCmdBufferID = GetCurrentCommandBufferId();
VkSemaphore presentSemaphore = chainInfo.m_presentSemaphores[chainInfo.swapchainImageIndex];
SubmitCommandBuffer(presentSemaphore); // submit all command and signal semaphore
cemu_assert_debug(m_numSubmittedCmdBuffers > 0);
// wait for the previous frame to finish rendering
WaitCommandBufferFinished(m_commandBufferIDOfPrevFrame);
m_commandBufferIDOfPrevFrame = currentFrameCmdBufferID;
chainInfo.WaitAvailableFence();
VkPresentIdKHR presentId = {};
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.swapchainCount = 1;
@ -2700,6 +2758,24 @@ void VulkanRenderer::SwapBuffer(bool mainWindow)
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &presentSemaphore;
// if present_wait is available and enabled, add frame markers to present requests
// and limit the number of queued present operations
if (m_featureControl.deviceExtensions.present_wait && chainInfo.m_maxQueued > 0)
{
presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR;
presentId.swapchainCount = 1;
presentId.pPresentIds = &chainInfo.m_presentId;
presentInfo.pNext = &presentId;
if(chainInfo.m_queueDepth >= chainInfo.m_maxQueued)
{
uint64 waitFrameId = chainInfo.m_presentId - chainInfo.m_queueDepth;
vkWaitForPresentKHR(m_logicalDevice, chainInfo.m_swapchain, waitFrameId, 40'000'000);
chainInfo.m_queueDepth--;
}
}
VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo);
if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR)
{
@ -2708,6 +2784,12 @@ void VulkanRenderer::SwapBuffer(bool mainWindow)
if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
chainInfo.m_shouldRecreate = true;
if(result >= 0)
{
chainInfo.m_queueDepth++;
chainInfo.m_presentId++;
}
chainInfo.hasDefinedSwapchainImage = false;
chainInfo.swapchainImageIndex = -1;
@ -3482,13 +3564,13 @@ void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType,
switch (shaderType)
{
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;
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;
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;
default:
cemu_assert_debug(false);

View file

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

View file

@ -375,24 +375,20 @@ float s_vkUniformData[512 * 4];
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader)
{
auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return s_vkUniformData + (index / 4); };
auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); };
sint32 shaderAluConst;
sint32 shaderUniformRegisterOffset;
switch (shader->shaderType)
{
case LatteConst::ShaderType::Vertex:
shaderAluConst = 0x400;
shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START;
break;
case LatteConst::ShaderType::Pixel:
shaderAluConst = 0;
shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START;
break;
case LatteConst::ShaderType::Geometry:
shaderAluConst = 0; // geometry shader has no ALU const
shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START;
break;
default:
UNREACHABLE;
@ -445,7 +441,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
}
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++)
{
if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
@ -455,26 +451,63 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
}
}
// 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;
}
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;
memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize);
m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize;
m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1;
m_uniformVarBufferWriteIndex += uniformSize;
// update dynamic offset
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset;
// flush if not coherent
if (!m_uniformVarBufferMemoryIsCoherent)
{
uint32 nonCoherentAtomSizeM1 = m_featureControl.limits.nonCoherentAtomSize - 1;
VkMappedMemoryRange flushedRange{};
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
flushedRange.memory = m_uniformVarBufferMemory;
flushedRange.offset = uniformOffset;
flushedRange.size = (shader->uniform.uniformRangeSize + nonCoherentAtomSizeM1) & ~nonCoherentAtomSizeM1;
flushedRange.size = uniformSize;
vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange);
}
}
@ -494,7 +527,7 @@ void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderSta
{
for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex])
{
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].unformBufferOffset[itr];
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].uniformBufferOffset[itr];
numDynOffsets++;
}
}
@ -1357,6 +1390,24 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
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
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);
}
// 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;
if (!isFirst)
@ -1613,13 +1648,13 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader
switch (shaderType)
{
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;
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;
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;
default:
UNREACHABLE;

View file

@ -137,6 +137,10 @@ namespace iosu
this->task_settings.taskType = settings->taskType;
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);
case FPD_REQUEST_ID::GetFriendListEx:
return CallHandler_GetFriendListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
case FPD_REQUEST_ID::UpdateCommentAsync:
return CallHandler_UpdateCommentAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
case FPD_REQUEST_ID::UpdatePreferenceAsync:
return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync:
@ -719,18 +721,23 @@ namespace iosu
nnResult CallHandler_GetMyComment(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
{
static constexpr uint32 MY_COMMENT_LENGTH = 0x12; // are comments utf16? Buffer length is 0x24
if(numVecIn != 0 || numVecOut != 1)
return FPResult_InvalidIPCParam;
if(vecOut->size != MY_COMMENT_LENGTH*sizeof(uint16be))
{
cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size");
return FPResult_InvalidIPCParam;
}
std::basic_string<uint16be> myComment;
myComment.resize(MY_COMMENT_LENGTH);
memcpy(vecOut->basePhys.GetPtr(), myComment.data(), MY_COMMENT_LENGTH*sizeof(uint16be));
return 0;
if(g_fpd.nexFriendSession)
{
if(vecOut->size != MY_COMMENT_LENGTH * sizeof(uint16be))
{
cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size");
return FPResult_InvalidIPCParam;
}
nexComment myNexComment;
g_fpd.nexFriendSession->getMyComment(myNexComment);
myComment = StringHelpers::FromUtf8(myNexComment.commentString);
}
myComment.insert(0, 1, '\0');
memcpy(vecOut->basePhys.GetPtr(), myComment.c_str(), MY_COMMENT_LENGTH * sizeof(uint16be));
return FPResult_Ok;
}
nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
@ -1143,6 +1150,36 @@ namespace iosu
return FPResult_Ok;
}
nnResult CallHandler_UpdateCommentAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
{
std::unique_lock _l(g_fpd.mtxFriendSession);
if (numVecIn != 1 || numVecOut != 0)
return FPResult_InvalidIPCParam;
if (!g_fpd.nexFriendSession)
return FPResult_RequestFailed;
uint32 messageLength = vecIn[0].size / sizeof(uint16be);
DeclareInputPtr(newComment, uint16be, messageLength, 0);
if (messageLength == 0 || newComment[messageLength-1] != 0)
{
cemuLog_log(LogType::Force, "UpdateCommentAsync: Message must contain at least a null-termination character");
return FPResult_InvalidIPCParam;
}
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
auto utf8_comment = StringHelpers::ToUtf8(newComment, messageLength);
nexComment temporaryComment;
temporaryComment.ukn0 = 0;
temporaryComment.commentString = utf8_comment;
temporaryComment.ukn1 = 0;
g_fpd.nexFriendSession->updateCommentAsync(temporaryComment, [cmd](NexFriends::RpcErrorCode result) {
if (result != NexFriends::ERR_NONE)
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
ServiceCallAsyncRespond(cmd, FPResult_Ok);
});
return FPResult_Ok;
}
nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
{
std::unique_lock _l(g_fpd.mtxFriendSession);

View file

@ -212,6 +212,7 @@ namespace iosu
static const int RELATIONSHIP_FRIEND = 3;
static const int GAMEMODE_MAX_MESSAGE_LENGTH = 0x80; // limit includes null-terminator character, so only 0x7F actual characters can be used
static const int MY_COMMENT_LENGTH = 0x12;
enum class FPD_REQUEST_ID
{
@ -245,6 +246,7 @@ namespace iosu
CheckSettingStatusAsync = 0x7596,
GetFriendListEx = 0x75F9,
GetFriendRequestListEx = 0x76C1,
UpdateCommentAsync = 0x7726,
UpdatePreferenceAsync = 0x7727,
RemoveFriendAsync = 0x7789,
DeleteFriendFlagsAsync = 0x778A,

View file

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

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_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();
};

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
// to avoid an infinite loop we have no choice but to yield the thread even it is in an uninterruptible state
if( !OSIsInterruptEnabled() )
cemuLog_log(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)");
cemuLog_logOnce(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)");
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();

View file

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

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));
}
typedef struct
namespace nn::nfp
{
/* +0x00 */ uint8 characterId[3];
/* +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)
struct RomInfo
{
nnNfpUnlock();
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code
return;
/* +0x00 */ uint8 characterId[3];
/* +0x03 */ uint8 amiiboSeries;
/* +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];
romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1];
romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed
nnResult GetNfpRomInfo(RomInfo* romInfo)
{
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
romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed
romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed
nnNfpUnlock();
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0));
}
nnResult GetNfpReadOnlyInfo(ReadOnlyInfo* readOnlyInfo)
{
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(readOnlyInfo);
nnNfpUnlock();
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0);
}
};
typedef struct
{
@ -880,13 +898,13 @@ void nnNfp_update()
if (amiiboElapsedTouchTime >= 1500)
{
nnNfp_unloadAmiibo();
if (nfp_data.deactivateEvent)
{
coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent);
coreinit::OSSignalEvent(osEvent);
}
}
nnNfpUnlock();
if (nfp_data.deactivateEvent)
{
coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent);
coreinit::OSSignalEvent(osEvent);
}
}
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", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom);
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", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo);
@ -1028,7 +1044,9 @@ namespace nn::nfp
{
nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc
cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder);
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)
{
scope_exit _se([&](){coreinit::OSSignalEvent(event);});
stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);});
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)
{
scope_exit _se([&](){coreinit::OSSignalEvent(event);});
stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);});
if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE))
return OLV_RESULT_MISSING_DATA;

View file

@ -1,4 +1,6 @@
#include "BackendEmulated.h"
#include "Dimensions.h"
#include "Infinity.h"
#include "Skylander.h"
#include "config/CemuConfig.h"
@ -33,5 +35,12 @@ namespace nsyshid::backend::emulated
auto device = std::make_shared<InfinityBaseDevice>();
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

View file

@ -15,7 +15,7 @@ namespace nsyshid::backend::libusb
if (m_initReturnCode < 0)
{
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);
return;
}
@ -35,7 +35,7 @@ namespace nsyshid::backend::libusb
if (ret != LIBUSB_SUCCESS)
{
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);
}
else
@ -415,7 +415,7 @@ namespace nsyshid::backend::libusb
if (ret < 0)
{
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);
libusb_free_device_list(devices, 1);
return false;
@ -439,8 +439,8 @@ namespace nsyshid::backend::libusb
{
this->m_libusbHandle = nullptr;
cemuLog_log(LogType::Force,
"nsyshid::DeviceLibusb::open(): failed to open device; return code: %i",
ret);
"nsyshid::DeviceLibusb::open(): failed to open device: {}",
libusb_strerror(ret));
libusb_free_device_list(devices, 1);
return false;
}

View file

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

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, 20> digest = {};
SHA_CTX ctx;
SHA1_Init(&ctx);
SHA1_Update(&ctx, sha1Data.data(), sha1Data.size());
SHA1_Final(digest.data(), &ctx);
OPENSSL_cleanse(&ctx, sizeof(ctx));
SHA1(sha1Data.data(), sha1Data.size(), digest.data());
// Infinity AES keys are the first 16 bytes of the SHA1 Digest, every set of 4 bytes need to be
// reversed due to endianness
std::array<uint8, 16> key = {};

View file

@ -1210,6 +1210,14 @@ void nsysnetExport_select(PPCInterpreter_t* hCPU)
timeval tv = { 0 };
if (timeOut == NULL)
{
// return immediately
cemuLog_log(LogType::Socket, "select returned immediately because of null timeout");
osLib_returnFromFunction(hCPU, 0);
return;
}
uint64 msTimeout = (_swapEndianU32(timeOut->tv_usec) / 1000) + (_swapEndianU32(timeOut->tv_sec) * 1000);
uint32 startTime = GetTickCount();
while (true)

View file

@ -509,7 +509,7 @@ namespace ntag
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);
// Encrypt

View file

@ -12,6 +12,7 @@
enum class KPAD_ERROR : sint32
{
NONE = 0,
NO_SAMPLE_DATA = -1,
NO_CONTROLLER = -2,
NOT_INITIALIZED = -5,
};
@ -106,6 +107,9 @@ void padscoreExport_WPADProbe(PPCInterpreter_t* hCPU)
}
else
{
if(type)
*type = 253;
osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER);
}
}
@ -420,9 +424,12 @@ void padscoreExport_KPADSetConnectCallback(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, old_callback.GetMPTR());
}
uint64 g_kpadLastRead[InputManager::kMaxWPADControllers] = {0};
bool g_kpadIsInited = true;
sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype<KPAD_ERROR>* errResult)
{
if (channel >= InputManager::kMaxWPADControllers)
{
debugBreakpoint();
@ -446,6 +453,19 @@ sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, bety
return 0;
}
// On console new input samples are only received every few ms and calling KPADRead(Ex) clears the internal queue regardless of length value
// thus calling KPADRead(Ex) again too soon on the same channel will result in no data being returned
// Games that depend on this: Affordable Space Adventures
uint64 currentTime = coreinit::OSGetTime();
uint64 timeDif = currentTime - g_kpadLastRead[channel];
if(length == 0 || timeDif < coreinit::EspressoTime::ConvertNsToTimerTicks(1000000))
{
if (errResult)
*errResult = KPAD_ERROR::NO_SAMPLE_DATA;
return 0;
}
g_kpadLastRead[channel] = currentTime;
memset(samplingBufs, 0x00, sizeof(KPADStatus_t));
samplingBufs->wpadErr = WPAD_ERR_NONE;
samplingBufs->data_format = controller->get_data_format();
@ -474,7 +494,6 @@ void padscoreExport_KPADReadEx(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, samplesRead);
}
bool debugUseDRC1 = true;
void padscoreExport_KPADRead(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(channel, 0);
@ -726,7 +745,8 @@ namespace padscore
// call sampling callback
for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i)
{
if (g_padscore.controller_data[i].sampling_callback) {
if (g_padscore.controller_data[i].sampling_callback)
{
if (const auto controller = instance.get_wpad_controller(i))
{
cemuLog_log(LogType::InputAPI, "Calling WPADsamplingCallback({})", i);
@ -741,7 +761,7 @@ namespace padscore
{
OSCreateAlarm(&g_padscore.alarm);
const uint64 start_tick = coreinit::coreinit_getOSTime();
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock(); // once a second
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);
}

View file

@ -427,7 +427,7 @@ namespace proc_ui
}
if(callbackType != ProcUICallbackId::AcquireForeground)
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)
@ -437,7 +437,7 @@ namespace proc_ui
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)

View file

@ -522,10 +522,10 @@ namespace snd_core
// called periodically to check for AX updates
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 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 auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700));
constexpr static auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3)));
constexpr static auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3));
constexpr static auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900));
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
bool additional_blocks_required = false;

View file

@ -50,7 +50,6 @@
extern bool isLaunchTypeELF;
bool debugUseDRC = true;
VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] =
{
{{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}},
@ -240,19 +239,20 @@ namespace vpad
status->tpProcessed2.validity = VPAD_TP_VALIDITY_INVALID_XY;
const auto controller = InputManager::instance().get_vpad_controller(channel);
if (!controller || debugUseDRC == false)
if (!controller)
{
// no controller
// most games expect the Wii U GamePad to be connected, so even if the user has not set it up we should still return empty samples for channel 0
if(channel != 0)
{
if (error)
*error = VPAD_READ_ERR_NO_CONTROLLER;
if (length > 0)
status->vpadErr = -1;
return 0;
}
if (error)
*error = VPAD_READ_ERR_NONE; // VPAD_READ_ERR_NO_DATA; // VPAD_READ_ERR_NO_CONTROLLER;
*error = VPAD_READ_ERR_NONE;
return 1;
//osLib_returnFromFunction(hCPU, 1); return;
}
if (channel != 0)
{
debugBreakpoint();
}
const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled();
@ -274,9 +274,7 @@ namespace vpad
// not ready yet
if (error)
*error = VPAD_READ_ERR_NONE;
return 0;
//osLib_returnFromFunction(hCPU, 0); return;
}
else if (dif <= ESPRESSO_TIMER_CLOCK)
{

View file

@ -91,7 +91,11 @@ bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args)
else
{
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...));
#endif
cemuLog_log(type, std::basic_string_view(text.data(), text.size()));
}
return true;

View file

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

View file

@ -297,7 +297,9 @@ public:
void writeData(nexPacketBuffer* pb) const override
{
cemu_assert_unimplemented();
pb->writeU8(ukn0);
pb->writeString(commentString.c_str());
pb->writeU64(ukn1);
}
void readData(nexPacketBuffer* pb) override
@ -554,6 +556,7 @@ public:
bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId);
bool isOnline();
void getMyPreference(nexPrincipalPreference& preference);
void getMyComment(nexComment& comment);
// asynchronous API (data has to be requested)
bool addProvisionalFriend(char* name, std::function<void(RpcErrorCode)> cb);
@ -565,6 +568,7 @@ public:
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)
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 setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid));
@ -619,6 +623,7 @@ private:
// local friend state
nexPresenceV2 myPresence;
nexPrincipalPreference myPreference;
nexComment myComment;
std::recursive_mutex mtx_lists;
std::vector<nexFriend> list_friends;

View file

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

View file

@ -395,16 +395,10 @@ void vectorRemoveByIndex(std::vector<T>& vec, const size_t index)
vec.erase(vec.begin() + index);
}
template<typename T1, typename T2>
int match_any_of(T1 value, T2 compareTo)
template<typename T1, typename... Types>
bool match_any_of(T1&& value, Types&&... others)
{
return value == compareTo;
}
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...);
return ((value == others) || ...);
}
// we cache the frequency in a static variable
@ -502,13 +496,6 @@ bool future_is_ready(std::future<T>& f)
#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
// 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>
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);
}
@ -579,13 +568,34 @@ struct fmt::formatter<betype<T>> : fmt::formatter<T>
}
};
// useful C++23 stuff that isn't yet widely supported
// std::to_underlying
// useful future C++ stuff
namespace stdx
{
// std::to_underlying
template <typename EnumT, typename = std::enable_if_t < std::is_enum<EnumT>{} >>
constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept {
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
#define EMULATOR_NAME "Cemu"
#define EMULATOR_VERSION_LEAD 2
#define EMULATOR_VERSION_MAJOR 0
// the minor version is used for experimental builds to indicate the build index. Set by command line option from CI build script
// if zero, the version text will be constructed as LEAD.MAJOR, otherwise as LEAD.MAJOR-MINOR
#if defined(EMULATOR_VERSION_MINOR) && EMULATOR_VERSION_MINOR == 0
#define EMULATOR_VERSION_SUFFIX ""
#else
#define EMULATOR_VERSION_SUFFIX " (experimental)"
#endif
#ifndef EMULATOR_VERSION_MINOR
#define EMULATOR_VERSION_MINOR 0
#endif
#define _XSTRINGFY(s) _STRINGFY(s)
#define _STRINGFY(s) #s
#if EMULATOR_VERSION_MINOR != 0
#if defined(EMULATOR_HASH) && EMULATOR_VERSION_MINOR == 999999
#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#if EMULATOR_VERSION_MAJOR != 0
#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX)
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX)
#else
#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX)
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX)
#endif
#else
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) EMULATOR_VERSION_SUFFIX)
#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) EMULATOR_VERSION_SUFFIX)
// no version provided. Only show commit hash
#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX)
#endif
#endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -116,9 +116,11 @@ bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string&
#elif BOOST_OS_MACOS
urlStr.append("&platform=macos_bundle_x86");
#elif
#error Name for current platform is missing
#endif
const auto& config = GetConfig();
if(config.receive_untested_updates)
urlStr.append("&allowNewUpdates=1");
curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);

View file

@ -115,7 +115,7 @@ void DownloadGraphicPacksWindow::UpdateThread()
curlDownloadFileState_t tempDownloadState;
std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?");
char temp[64];
sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR);
sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH);
queryUrl.append(temp);
queryUrl.append("&");
sprintf(temp, "t=%u", (uint32)std::chrono::seconds(std::time(NULL)).count()); // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do)

View file

@ -1,4 +1,4 @@
#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"
#include "EmulatedUSBDeviceFrame.h"
#include <algorithm>
@ -8,14 +8,17 @@
#include "util/helpers/helpers.h"
#include "Cafe/OS/libs/nsyshid/nsyshid.h"
#include "Cafe/OS/libs/nsyshid/Dimensions.h"
#include "Common/FileStream.h"
#include <wx/arrstr.h>
#include <wx/button.h>
#include <wx/combobox.h>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/filedlg.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/notebook.h>
#include <wx/panel.h>
@ -29,7 +32,6 @@
#include <wx/wfstream.h>
#include "resource/embedded/resources.h"
#include "EmulatedUSBDeviceFrame.h"
EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent)
: wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition,
@ -44,6 +46,7 @@ EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent)
notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal"));
notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base"));
notebook->AddPage(AddDimensionsPage(notebook), _("Dimensions Toypad"));
sizer->Add(notebook, 1, wxEXPAND | wxALL, 2);
@ -120,8 +123,52 @@ wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook)
return panel;
}
wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber,
wxStaticBox* box)
wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook)
{
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);
@ -184,6 +231,44 @@ wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumbe
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)
{
wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "",
@ -307,8 +392,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot)
return;
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");
errorMessage.ShowModal();
@ -351,6 +436,80 @@ wxString CreateSkylanderDialog::GetFilePath() const
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)
: wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150))
{
@ -447,76 +606,231 @@ wxString CreateInfinityFigureDialog::GetFilePath() const
return m_filePath;
}
void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot)
void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index)
{
wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "",
"BIN files (*.bin)|*.bin",
wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "",
"Dimensions files (*.bin)|*.bin",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
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();
return;
}
LoadFigurePath(slot, openFileDialog.GetPath());
}
std::array<uint8, 0x2D * 0x04> file_data;
void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path)
{
std::unique_ptr<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true));
if (!infFile)
if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size())
{
wxMessageDialog errorMessage(this, "File Open Error");
wxMessageDialog errorMessage(this, "Failed to read minifig file data");
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);
ClearMinifig(pad, index);
uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot);
m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second);
uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index);
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);
CreateInfinityFigureDialog create_dlg(this, slot);
nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true);
m_dimensionSlots[index]->ChangeValue("None");
m_dimSlots[index] = std::nullopt;
}
void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index)
{
CreateDimensionFigureDialog create_dlg(this);
create_dlg.ShowModal();
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");
nsyshid::g_infinitybase.RemoveFigure(slot);
}
if (!m_dimSlots[index])
return;
void EmulatedUSBDeviceFrame::UpdateSkylanderEdits()
{
for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++)
MoveDimensionFigureDialog move_dlg(this, index);
nsyshid::g_dimensionstoypad.TempRemove(index);
move_dlg.ShowModal();
if (move_dlg.GetReturnCode() == 1)
{
std::string displayString;
if (auto sd = m_skySlots[i])
nsyshid::g_dimensionstoypad.MoveFigure(move_dlg.GetNewPad(), move_dlg.GetNewIndex(), pad, index);
if (index != move_dlg.GetNewIndex())
{
auto [portalSlot, skyId, skyVar] = sd.value();
displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar);
m_dimSlots[move_dlg.GetNewIndex()] = m_dimSlots[index];
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 wxTextCtrl;
class EmulatedUSBDeviceFrame : public wxFrame {
class EmulatedUSBDeviceFrame : public wxFrame
{
public:
EmulatedUSBDeviceFrame(wxWindow* parent);
~EmulatedUSBDeviceFrame();
std::array<std::optional<uint32>, 7> GetCurrentMinifigs();
private:
wxCheckBox* m_emulatePortal;
wxCheckBox* m_emulateBase;
wxCheckBox* m_emulateToypad;
std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots;
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<uint32>, 7> m_dimSlots;
wxPanel* AddSkylanderPage(wxNotebook* notebook);
wxPanel* AddInfinityPage(wxNotebook* notebook);
wxPanel* AddDimensionsPage(wxNotebook* notebook);
wxBoxSizer* AddSkylanderRow(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 LoadSkylanderPath(uint8 slot, wxString path);
void CreateSkylander(uint8 slot);
void ClearSkylander(uint8 slot);
void UpdateSkylanderEdits();
void LoadFigure(uint8 slot);
void LoadFigurePath(uint8 slot, wxString path);
void CreateFigure(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:
explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const;
@ -52,11 +66,37 @@ class CreateSkylanderDialog : public wxDialog {
wxString m_filePath;
};
class CreateInfinityFigureDialog : public wxDialog {
class CreateInfinityFigureDialog : public wxDialog
{
public:
explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const;
protected:
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->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
sint32 checkboxCount = 0;
auto CountRowElement = [&]()
{
checkboxCount++;
if(checkboxCount != 2)
return;
second_row->AddSpacer(10);
checkboxCount = 0;
};
auto InsertEmptyRow = [&]()
{
while(checkboxCount != 0)
CountRowElement();
second_row->AddSpacer(10);
second_row->AddSpacer(10);
second_row->AddSpacer(10);
};
const int topflag = wxALIGN_CENTER_VERTICAL | wxALL;
m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position"));
m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu"));
second_row->Add(m_save_window_position_size, 0, topflag, 5);
second_row->AddSpacer(10);
CountRowElement();
//second_row->AddSpacer(10);
m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position"));
m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it"));
second_row->Add(m_save_padwindow_position_size, 0, topflag, 5);
CountRowElement();
const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM;
m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence"));
m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!"));
second_row->Add(m_discord_presence, 0, botflag, 5);
CountRowElement();
#ifndef ENABLE_DISCORD_RPC
m_discord_presence->Disable();
#endif
second_row->AddSpacer(10);
//second_row->AddSpacer(10);
m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar"));
m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top"));
second_row->Add(m_fullscreen_menubar, 0, botflag, 5);
CountRowElement();
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
second_row->Add(m_auto_update, 0, botflag, 5);
#if BOOST_OS_LINUX
if (!std::getenv("APPIMAGE")) {
m_auto_update->Disable();
}
#endif
second_row->AddSpacer(10);
m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot"));
m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder"));
second_row->Add(m_save_screenshot, 0, botflag, 5);
CountRowElement();
m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver"));
m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game."));
second_row->Add(m_disable_screensaver, 0, botflag, 5);
CountRowElement();
// Enable/disable feral interactive gamemode
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode"));
m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed."));
second_row->Add(m_feral_gamemode, 0, botflag, 5);
CountRowElement();
#endif
// temporary workaround because feature crashes on macOS
@ -191,6 +208,22 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
m_disable_screensaver->Enable(false);
#endif
// InsertEmptyRow();
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
second_row->Add(m_auto_update, 0, botflag, 5);
CountRowElement();
m_receive_untested_releases = new wxCheckBox(box, wxID_ANY, _("Receive untested updates"));
m_receive_untested_releases->SetToolTip(_("When checking for updates, include brand new and untested releases. These may contain bugs!"));
second_row->Add(m_receive_untested_releases, 0, botflag, 5);
#if BOOST_OS_LINUX
if (!std::getenv("APPIMAGE")) {
m_auto_update->Disable();
}
#endif
box_sizer->Add(second_row, 0, wxEXPAND, 5);
}
@ -899,6 +932,7 @@ void GeneralSettings2::StoreConfig()
config.fullscreen_menubar = m_fullscreen_menubar->IsChecked();
config.check_update = m_auto_update->IsChecked();
config.save_screenshot = m_save_screenshot->IsChecked();
config.receive_untested_updates = m_receive_untested_releases->IsChecked();
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
config.feral_gamemode = m_feral_gamemode->IsChecked();
#endif
@ -1536,6 +1570,7 @@ void GeneralSettings2::ApplyConfig()
m_fullscreen_menubar->SetValue(config.fullscreen_menubar);
m_auto_update->SetValue(config.check_update);
m_receive_untested_releases->SetValue(config.receive_untested_updates);
m_save_screenshot->SetValue(config.save_screenshot);
m_disable_screensaver->SetValue(config.disable_screensaver);

View file

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

View file

@ -458,10 +458,10 @@ void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event)
m_shown_graphic_pack = gp;
m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10);
m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 20);
m_graphic_pack_name->GetGrandParent()->Layout();
m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10);
m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 20);
m_graphic_pack_description->GetGrandParent()->Layout();
m_right_panel->FitInside();

View file

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

View file

@ -8,7 +8,7 @@ template <>
struct fmt::formatter<wxString> : formatter<string_view>
{
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);
}

View file

@ -114,6 +114,11 @@ InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position,
this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this);
}
InputAPIAddWindow::~InputAPIAddWindow()
{
discard_thread_result();
}
void InputAPIAddWindow::on_add_button(wxCommandEvent& event)
{
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)
{
discard_thread_result();
if (m_input_api->GetSelection() == wxNOT_FOUND)
return;
@ -239,19 +246,25 @@ void InputAPIAddWindow::on_controller_dropdown(wxCommandEvent& event)
m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr);
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();
wxCommandEvent event(wxControllersRefreshed);
event.SetEventObject(m_controller_list);
event.SetClientObject(new wxCustomData(std::move(available_controllers)));
event.SetInt(provider->api());
event.SetString(selected_uuid);
wxPostEvent(this, event);
m_search_running = false;
}).detach();
{
std::lock_guard lock{data->mutex};
if(!data->discardResult)
{
wxCommandEvent event(wxControllersRefreshed);
event.SetEventObject(m_controller_list);
event.SetClientObject(new wxCustomData(std::move(available_controllers)));
event.SetInt(provider->api());
event.SetString(selected_uuid);
wxPostEvent(this, event);
m_search_running = false;
}
}
}, m_search_thread_data).detach();
}
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:
InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector<ControllerPtr>& controllers);
~InputAPIAddWindow();
bool is_valid() const { return m_type.has_value() && m_controller != nullptr; }
InputAPI::Type get_type() const { return m_type.value(); }
@ -38,6 +39,8 @@ private:
void on_controller_selected(wxCommandEvent& event);
void on_controllers_refreshed(wxCommandEvent& event);
void discard_thread_result();
wxChoice* m_input_api;
wxComboBox* m_controller_list;
wxButton* m_ok_button;
@ -50,4 +53,10 @@ private:
std::vector<ControllerPtr> m_controllers;
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/textctrl.h>
#include <wx/slider.h>
#include <wx/checkbox.h>
#include "gui/helpers/wxControlObject.h"
@ -131,11 +132,23 @@ VPADInputPanel::VPADInputPanel(wxWindow* parent)
}
// Blow Mic
row = 9;
row = 8;
add_button_row(main_sizer, row, column, VPADController::kButtonId_Mic, _("blow mic"));
row++;
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);
static_cast<VPADController*>(emulated_controller.get())->set_screen_toggle(m_togglePadViewCheckBox->GetValue());
if(emulated_controller)
{
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"
class wxInputDraw;
class wxCheckBox;
class VPADInputPanel : public InputPanel
{
@ -11,11 +12,13 @@ public:
VPADInputPanel(wxWindow* parent);
void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override;
virtual void load_controller(const EmulatedControllerPtr& controller) override;
private:
void OnVolumeChange(wxCommandEvent& event);
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, const wxString &label);

View file

@ -127,7 +127,7 @@ using EmulatedControllerPtr = std::shared_ptr<EmulatedController>;
template <>
struct fmt::formatter<EmulatedController::Type> : formatter<string_view> {
template <typename FormatContext>
auto format(EmulatedController::Type v, FormatContext& ctx) {
auto format(EmulatedController::Type v, FormatContext& ctx) const {
switch (v)
{
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;
}
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_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);
@ -86,9 +88,13 @@ public:
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:
bool m_mic_active = false;
bool m_screen_active = false;
bool m_screen_active_toggle = false;
uint32be m_last_holdvalue = 0;
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
VS_VERSION_INFO VERSIONINFO
FILEVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0
PRODUCTVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0
FILEVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0
PRODUCTVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -94,7 +94,7 @@ BEGIN
VALUE "LegalCopyright", "Team Cemu"
VALUE "OriginalFilename", "Cemu.exe"
VALUE "ProductName", "Cemu"
VALUE "ProductVersion", xstr(EMULATOR_VERSION_LEAD) "." xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX "\0"
VALUE "ProductVersion", xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) "." xstr(EMULATOR_VERSION_PATCH) EMULATOR_VERSION_SUFFIX "\0"
END
END
BLOCK "VarFileInfo"

View file

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

View file

@ -194,7 +194,7 @@ namespace robin_hood {
// workaround missing "is_trivially_copyable" in g++ < 5.0
// 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__)
#else
# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value

View file

@ -1,29 +1,6 @@
#include "crc32.h"
#if defined(_MSC_VER) || defined(__MINGW32__)
#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] =
constexpr uint32 Crc32Lookup[8][256] =
{
{
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
@ -301,20 +278,7 @@ unsigned int Crc32Lookup[8][256] =
}
};
/// swap endianess
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 crc32_calc_slice_by_8(uint32 previousCrc32, const void* data, size_t length)
{
uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF
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)
{
if constexpr (std::endian::native == std::endian::big){
uint32_t one = *current++ ^ swap(crc);
uint32_t one = *current++ ^ _swapEndianU32(crc);
uint32_t two = *current++;
crc = Crc32Lookup[0][two & 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];
}
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;
}
const uint8_t* currentChar = (const uint8_t*)current;
const uint8* currentChar = (const uint8*)current;
// remaining 1 to 7 bytes (standard algorithm)
while (length-- != 0)
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
}
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)
{
return crc32_calc_slice_by_8(c, data, length);
}
unsigned char* p = (unsigned char*)data;
const uint8* p = (const uint8*)data;
if (length == 0)
return c;
c ^= 0xFFFFFFFF;
while (length)
{
unsigned char temp = *p;
temp ^= (unsigned char)c;
uint8 temp = *p;
temp ^= (uint8)c;
c = (c >> 8) ^ Crc32Lookup[0][temp];
// next
length--;

View file

@ -1,8 +1,8 @@
#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);
}