Compare commits

..

74 Commits

Author SHA1 Message Date
Fernando Sahmkow
a60b669ef4 MacroHLE: Add MultidrawIndirect HLE Macro. 2022-10-01 20:57:00 -04:00
bunnei
d9e375acc3 Merge pull request #8910 from vonchenplus/astc_decode_error
video_core: Modify astc texture decode error fill value
2022-09-30 15:34:11 -07:00
bunnei
dbb9d601df Merge pull request #8934 from german77/palma_release
service: hid: Partially implement palma controller
2022-09-29 12:48:28 -07:00
Morph
2e3aad41aa Merge pull request #8993 from lat9nq/drop-linuxdeploy
ci/linux: Drop linuxdeploy usage
2022-09-28 20:10:47 -04:00
bunnei
9015a512c2 Merge pull request #8940 from german77/silence
yuzu: Silence some clang warnings
2022-09-28 14:37:23 -07:00
Morph
0f6007b405 Merge pull request #8989 from merryhime/loader-nullptr
core/loader: Return nullptr if file is nullptr
2022-09-27 10:59:46 -04:00
lat9nq
ad9f97cd8d ci/linux: Drop linuxdeploy usage
Recent versions of Docker appear to cause the Qt linuxdeploy plugin to
throw a boost file copy error.

This switches from linuxdeploy to a script of mine I've been working on
for a while.
2022-09-26 00:35:03 -04:00
Merry
f55ed1cab0 core/loader: Return nullptr if file is nullptr 2022-09-25 19:31:35 +01:00
bunnei
465c46387d Merge pull request #8920 from abouvier/cmake-git
cmake: fix git detection
2022-09-24 23:11:45 -07:00
Narr the Reg
23589ad9b8 service: hid: Partially implement palma controller 2022-09-25 00:13:12 -05:00
bunnei
1b1b99fbd5 Merge pull request #8941 from Kelebek1/single_core_sucks
Do not try to pause core timing from the audio thread when using single-core
2022-09-23 22:40:30 -07:00
Morph
d794ced303 Merge pull request #8945 from Tachi107/typos
chore: fix some typos
2022-09-23 20:28:43 -04:00
Morph
5082efef6c Merge pull request #8948 from german77/order
yuzu: sort input profiles by name
2022-09-23 20:28:35 -04:00
Morph
86ccc87111 Merge pull request #8933 from lat9nq/ci-use-apprun-sh
ci/linux: Use AppRun.sh to start AppImage
2022-09-23 20:28:28 -04:00
Morph
12baf88dc8 Merge pull request #8930 from lat9nq/disable-vulkan-check
yuzu qt: Add option to disable startup Vulkan check
2022-09-23 20:28:13 -04:00
Narr the Reg
f711d1ce52 yuzu: sort input profiles by name 2022-09-23 12:31:35 -05:00
Morph
70abb13a43 Merge pull request #8943 from lioncash/netiface
sockets: Make fd member variable protected
2022-09-23 09:08:07 -04:00
Morph
c2c3f2c3e7 Merge pull request #8939 from lioncash/render
audio_renderer: Make GetCommandBuffer() take a u32
2022-09-23 09:07:52 -04:00
Morph
3b77dec188 Merge pull request #8938 from lioncash/manager
audio_manager: Minor cleanup related changes
2022-09-23 09:07:38 -04:00
Andrea Pappacoda
4a493cb10f chore: fix some typos
Fix some typos reported by Lintian
2022-09-23 13:38:23 +02:00
Lioncash
c8da75b7ed sockets: Make fd member variable protected
Other things shouldn't be able to directly mess around with the
descriptor
2022-09-22 08:20:54 -04:00
Kelebek1
56b8a9ba6e Do not try to pause core timing from the audio thread when using single-core 2022-09-22 01:23:57 +01:00
Narr the Reg
5a74ced59a yuzu: Silence some clang warnings 2022-09-21 11:51:31 -05:00
Lioncash
c891497b61 audio_renderer: Make GetCommandBuffer() take a u32
This function is only ever called with unsigned types, and all of the
other interface functions take session_id as a u32, so this makes the
class a little more consistent.
2022-09-21 10:00:57 -04:00
Lioncash
00616c7ad8 audio_manager: Forward declare result type
Moves the include into the cpp file to lessen header dependencies.
2022-09-21 09:43:59 -04:00
Lioncash
75d6fe3669 audio_manager: Remove redundant cast in ThreadFunc()
We can just use a local here to get rid of a second cast.
2022-09-21 09:35:22 -04:00
Lioncash
e48e22eb52 audio_manager: move std::functions in SetOutManager/SetInManager
Prevents unnecessary reallocations in the event the captured variables
are larger than the internal std::function buffer.
2022-09-21 09:32:15 -04:00
Lioncash
da0fd51642 audio_manager: Remove unused forward declarations
Allows us to get rid of some unnecessary forward declarations and
includes.
2022-09-21 09:29:35 -04:00
Lioncash
08d81e0bd8 audio_manager: Remove unused sessions_started member variable
This isn't used, so it can be removed.
2022-09-21 09:28:05 -04:00
Lioncash
b59b967280 audio_manager: Remove dependence on system state
This isn't used by the class, so this can be removed for the time being.
2022-09-21 09:26:42 -04:00
lat9nq
37b4f79b54 ci/linux: Use AppRun.sh to start AppImage
The current AppRun is more difficult to update. This script still
uses the old version of AppImageKit-checkrt, but now we use the shell
script version so we can set our own environment variables as the
application starts up.

This specific version searches for and sets the correct root CA file to
prevent SSL errors in yuzu.
2022-09-20 18:57:27 -04:00
bunnei
8d4458ef24 Merge pull request #8849 from Morph1984/parallel-astc
astc: Enable parallel CPU astc decoding
2022-09-19 12:17:51 -07:00
lat9nq
89af4bfba8 yuzu qt: Add option to disable startup Vulkan check
The startup check apparently confuses other programs when yuzu launches
2 processes and then quickly closes one of them. Though this isn't
really our issues it's also not a big deal for me to add an option to
work around that issue.
2022-09-19 14:46:12 -04:00
bunnei
3a5f9409c8 Merge pull request #8915 from vonchenplus/opus_multi_stream
core: implement HwOpus GetWorkBufferSizeForMultiStreamEx
2022-09-17 16:07:33 -07:00
Alexandre Bouvier
7239470dad cmake: fix git detection 2022-09-18 00:04:35 +02:00
bunnei
7172339c7a Merge pull request #8827 from german77/amiibo_release
core: nfp: Implement amiibo encryption
2022-09-17 11:11:21 -07:00
bunnei
9c32f29af1 Merge pull request #8650 from Kelebek1/vsync
[Coretiming/NVNFlinger] Improve multi-core vsync timing, and core timing accuracy
2022-09-17 11:10:54 -07:00
bunnei
4a7a771340 Merge pull request #8914 from lioncash/audio-const
audio_core: Mark several member functions as const
2022-09-16 23:51:31 -07:00
bunnei
eb726677b2 Merge pull request #8916 from Docteh/muilti_build
GIT: Modify .gitignore to ignore wildcard for build directories
2022-09-16 18:19:01 -07:00
bunnei
92813f01a4 Merge pull request #8906 from Docteh/fix_icons
UI: move icons from default into colorful theme.
2022-09-16 18:12:04 -07:00
Kyle K
49870baea4 GIT: Modify .gitignore to ignore wildcard for build directories
Helps if you have multiple build folders. There are other, dark ways to
hide extra build folders from git, but this is better.

See: https://github.com/citra-emu/citra/pull/6130
2022-09-16 10:58:29 -07:00
Morph
344006b856 Merge pull request #8869 from SachinVin/cmake
core/CMakeLists.txt: Remove duplicate files.
2022-09-16 13:11:33 -04:00
Morph
8dafe15600 Merge pull request #8649 from lat9nq/common-position-independent
common: Use PROJECT_SOURCE_DIR to find CMakeModules
2022-09-16 12:48:53 -04:00
Morph
44ccec7846 Merge pull request #8682 from lat9nq/dumpy
yuzu qt: Add option to create Windows crash dumps
2022-09-16 12:47:51 -04:00
Morph
882dfa36ae Merge pull request #6667 from lat9nq/ea-appimage
ci,linux: Support Patreon releases
2022-09-16 12:47:38 -04:00
FengChen
672e61d802 core: implement HwOpus GetWorkBufferSizeForMultiStreamEx 2022-09-17 00:28:56 +08:00
Morph
60aa942210 Merge pull request #8911 from lioncash/cexpr-string
audio_device: Make AudioDeviceName constructor constexpr
2022-09-16 10:18:58 -04:00
Morph
809126c94a astc: Enable parallel CPU astc decoding
Given the issues with GPU accelerated ASTC decoding with NVIDIA's latest drivers, parallelize astc decoding on the CPU.
Uses half the available threads in the system for astc decoding.
2022-09-16 10:16:42 -04:00
Kyle Kienapfel
9554c67809 UI: move icons from default into colorful theme.
colorful theme has been default theme for awhile. having colorful theme
try and grab icons from other theme doesn't work on Linux.

Also adding two additional icons, info is to hint to the user that they
should hit verify after pasting in a token, sync is to show that the
verification is occurring.
2022-09-15 23:00:49 -07:00
Lioncash
d55046c5e9 audio_device: Mark member functions as const where applicable
These member functions don't modify any internal state.
2022-09-15 09:06:17 -04:00
Lioncash
1c7dae966d audio_device: Make AudioDeviceName constructor constexpr
These are used as read-only arrays, so we can make the data read-only
and available at compile-time.

Now constructing an AudioDevice no longer needs to initialize some
tables
2022-09-15 09:03:40 -04:00
FengChen
88007077e2 video_core: Modify astc texture decode error fill value 2022-09-15 17:04:44 +08:00
SachinVin
9c6cd93195 core/CMakeLists.txt: Remove duplicate files. 2022-09-08 22:03:53 +05:30
german77
063b23cc58 core: nfp: Remove magic numbers 2022-09-07 09:49:43 -05:00
german77
4834961736 core: nfp: Workaround for lack of multiple nfp interfaces 2022-09-07 01:04:00 -05:00
Narr the Reg
caa138b33f core: nfp: Correct date and amiibo name 2022-09-07 01:04:00 -05:00
Narr the Reg
19a4e12e6e core: nfp: Implement Convert and RecreateApplicationArea, accuracy fixes 2022-09-07 01:04:00 -05:00
german77
848f69eb19 core: nfp: Implement amiibo encryption 2022-09-07 01:04:00 -05:00
lat9nq
9dc9e501de ci/windows: Upload debugging symbols 2022-09-04 21:36:35 -04:00
lat9nq
12f7d42d32 mini_dump: Address review feedback
Uses fmt::print as opposed to std::fprintf. Adds a missing return.
static's a single-use function. Initializes structs as opposed to
std::memset where possible. Fixes CMake linkage.

Co-authored-by: Lioncash <mathew1800@gmail.com>

mini_dump: Use a namespace

Co-authored-by: Lioncash <mathew1800@gmail.com>
2022-09-04 21:36:35 -04:00
lat9nq
45b343d1d0 ci,workflows: Enable crash dumps on MSVC builds
ci/windows: Enable crash dumps on MinGW builds
2022-09-04 21:36:34 -04:00
lat9nq
c976613ab2 vcpkg,cmake: Use vcpkg for dbghelp 2022-09-04 21:36:05 -04:00
lat9nq
e339ec0e00 mini_dump: Check for debugger before spawning a child
mini_dump: Clean up

mini_dump: Fix MSVC error

mini_dump: Silence MSVC warning C4700

Zero initialize deb_ev.

mini_dump: Add license info
2022-09-04 21:36:05 -04:00
lat9nq
3dbaafe1f3 mini_dump: Cleanup and add comments
Removes some unnecessary code.

wip
2022-09-04 21:36:05 -04:00
lat9nq
f958cbc737 yuzu: Use a debugger to generate minidumps
yuzu: Move mini_dump out of core

startup_checks: Better exception handling
2022-09-04 21:36:04 -04:00
Morph
606cdb17d3 core_timing: Sleep in discrete intervals, yield during spin 2022-08-02 01:01:54 -04:00
Kelebek1
658e1ee426 Add missing looping event schedule signal 2022-08-02 01:01:54 -04:00
Kelebek1
83a24ad638 Make coretiming waiting more accurate 2022-08-02 01:01:54 -04:00
Kelebek1
9d3b190465 Rework multi-core vsync 2022-08-01 23:51:53 -04:00
lat9nq
260430c849 common: Use PROJECT_SOURCE_DIR to find CMakeModules
Fixes CMake configuration when yuzu is a submodule of another project.
2022-08-01 23:18:56 -04:00
lat9nq
6b58db9ccd patreon step2: Use jobs to build for Windows and Linux
Apparently the two stages were not building in parallel. Specify
individual jobs that run MSVC and Linux building instead.
2022-07-31 21:07:46 -04:00
lat9nq
d77fe3b1c2 ci/linux: EA AppImage adjustments
Prevent AppImageLauncher from trying to integrate our AppImage on end
user systems. Don't include the basic yuzu executable with EA or
Mainline.
2022-07-31 03:25:29 -04:00
lat9nq
69bd6cd490 patreon step2: Enable Linux build
I sure as heck don't know what I'm doing :)

patreon_step2: Fix caching

:limesDance: still don't know what I'm doing :limesDance:
2022-07-31 01:35:12 -04:00
lat9nq
dc915aff62 ci,linux: Support Patreon releases
The Early Access AppImage needs to be accessible through liftinstall, so
a couple modifications need to made:

The DIR_NAME needs to not include the revision info.
The EA AppImage name cannot contain revision info.
The EA AppImage has to be packaged with the rest of the yuzu package,
which means both binaries and the source are bundled with it now in an
archive.

In addition, fix the source archive so yuzu can actually be built from
it.

upload: Copy AppImage to both mainline and EA release package
2022-07-31 01:35:11 -04:00
91 changed files with 3070 additions and 694 deletions

View File

@@ -33,16 +33,14 @@ DESTDIR="$PWD/AppDir" ninja install
rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
# Download tools needed to build an AppImage
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/linuxdeploy-x86_64.AppImage
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/linuxdeploy-plugin-qt-x86_64.AppImage
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/AppRun-patched-x86_64
wget -nc https://raw.githubusercontent.com/lat9nq/deploy/main/linux/deploy-linux.sh
wget -nc https://raw.githubusercontent.com/yuzu-emu/AppImageKit-checkrt/old/AppRun.sh
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/exec-x86_64.so
# Set executable bit
chmod 755 \
AppRun-patched-x86_64 \
deploy-linux.sh \
AppRun.sh \
exec-x86_64.so \
linuxdeploy-x86_64.AppImage \
linuxdeploy-plugin-qt-x86_64.AppImage
# Workaround for https://github.com/AppImage/AppImageKit/issues/828
export APPIMAGE_EXTRACT_AND_RUN=1
@@ -52,7 +50,7 @@ mkdir -p AppDir/usr/optional/libstdc++
mkdir -p AppDir/usr/optional/libgcc_s
# Deploy yuzu's needed dependencies
./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt
DEPLOY_QT=1 ./deploy-linux.sh AppDir/usr/bin/yuzu AppDir
# Workaround for libQt5MultimediaGstTools indirectly requiring libwayland-client and breaking Vulkan usage on end-user systems
find AppDir -type f -regex '.*libwayland-client\.so.*' -delete -print
@@ -60,6 +58,6 @@ find AppDir -type f -regex '.*libwayland-client\.so.*' -delete -print
# Workaround for building yuzu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al.
# See https://github.com/darealshinji/AppImageKit-checkrt
cp exec-x86_64.so AppDir/usr/optional/exec.so
cp AppRun-patched-x86_64 AppDir/AppRun
cp AppRun.sh AppDir/AppRun
cp --dereference /usr/lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/optional/libstdc++/libstdc++.so.6
cp --dereference /lib/x86_64-linux-gnu/libgcc_s.so.1 AppDir/usr/optional/libgcc_s/libgcc_s.so.1

View File

@@ -5,21 +5,24 @@
. .ci/scripts/common/pre-upload.sh
APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage"
REV_NAME="yuzu-linux-${GITDATE}-${GITREV}"
APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage"
BASE_NAME="yuzu-linux"
REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}"
ARCHIVE_NAME="${REV_NAME}.tar.xz"
COMPRESSION_FLAGS="-cJvf"
if [ "${RELEASE_NAME}" = "mainline" ]; then
DIR_NAME="${REV_NAME}"
if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
DIR_NAME="${BASE_NAME}-${RELEASE_NAME}"
else
DIR_NAME="${REV_NAME}_${RELEASE_NAME}"
DIR_NAME="${REV_NAME}-${RELEASE_NAME}"
fi
mkdir "$DIR_NAME"
cp build/bin/yuzu-cmd "$DIR_NAME"
cp build/bin/yuzu "$DIR_NAME"
if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then
cp build/bin/yuzu "$DIR_NAME"
fi
# Build an AppImage
cd build
@@ -32,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then
export APPIMAGE_EXTRACT_AND_RUN=1
fi
# Don't let AppImageLauncher ask to integrate EA
if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop
fi
if [ "${RELEASE_NAME}" = "mainline" ]; then
# Generate update information if releasing to mainline
./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}"
@@ -46,4 +54,9 @@ if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then
cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/"
fi
# Copy the AppImage to the general release directory and remove git revision info
if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
fi
. .ci/scripts/common/post-upload.sh

View File

@@ -21,6 +21,7 @@ cmake .. \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_CCACHE=ON \
-DYUZU_CRASH_DUMPS=ON \
-DYUZU_USE_BUNDLED_SDL2=OFF \
-DYUZU_USE_EXTERNAL_SDL2=OFF \
-DYUZU_TESTS=OFF \

View File

@@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") {
# None of the other GHA builds are including source, so commenting out today
#Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts"
# Are debug symbols important?
# cp .\build\bin\yuzu*.pdb .\pdb\
# Debugging symbols
cp .\build\bin\yuzu*.pdb .\artifacts\
# Write out a tag BUILD_TAG to environment for the Upload step
# We're getting ${{ github.event.number }} as $env:PR_NUMBER"

View File

@@ -9,7 +9,7 @@ parameters:
steps:
- script: choco install vulkan-sdk
displayName: 'Install vulkan-sdk'
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd ..
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
displayName: 'Configure CMake'
- task: MSBuild@1
displayName: 'Build'

View File

@@ -11,9 +11,30 @@ stages:
- stage: build
displayName: 'build'
jobs:
- job: build
- job: linux
timeoutInMinutes: 120
displayName: 'windows-msvc'
displayName: 'linux'
pool:
vmImage: ubuntu-latest
strategy:
maxParallel: 10
matrix:
linux:
BuildSuffix: 'linux'
ScriptFolder: 'linux'
steps:
- template: ./templates/sync-source.yml
parameters:
artifactSource: $(parameters.artifactSource)
needSubmodules: 'true'
- template: ./templates/build-single.yml
parameters:
artifactSource: 'false'
cache: $(parameters.cache)
version: $(DisplayVersion)
- job: msvc
timeoutInMinutes: 120
displayName: 'windows'
pool:
vmImage: windows-2022
steps:

View File

@@ -104,7 +104,7 @@ jobs:
run: |
glslangValidator --version
mkdir build
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DYUZU_CRASH_DUMPS=ON
- name: Build
run: cmake --build build
- name: Cache Summary

2
.gitignore vendored
View File

@@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Build directory
[Bb]uild/
[Bb]uild*/
doc-build/
# Generated source files

View File

@@ -52,6 +52,8 @@ Files: dist/qt_themes/colorful/icons/16x16/lock.png
dist/qt_themes/colorful/icons/48x48/chip.png
dist/qt_themes/colorful/icons/48x48/folder.png
dist/qt_themes/colorful_dark/icons/16x16/lock.png
dist/qt_themes/colorful/icons/16x16/info.png
dist/qt_themes/colorful/icons/16x16/sync.png
Copyright: Icons8
License: MIT
Comment: https://github.com/icons8/flat-color-icons
@@ -68,8 +70,8 @@ Copyright: Ionic (http://ionic.io/)
License: MIT
Files: dist/qt_themes/colorful/icons/48x48/star.png
dist/qt_themes/default/icons/16x16/checked.png
dist/qt_themes/default/icons/16x16/failed.png
dist/qt_themes/colorful/icons/16x16/checked.png
dist/qt_themes/colorful/icons/16x16/failed.png
Copyright: SVG Repo
License: CC0-1.0

View File

@@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
option(YUZU_TESTS "Compile tests" ON)
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
@@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
if (YUZU_TESTS)
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
endif()
if (YUZU_CRASH_DUMPS)
list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
endif()
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
@@ -447,6 +452,13 @@ elseif (WIN32)
# PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
endif()
if (YUZU_CRASH_DUMPS)
find_library(DBGHELP_LIBRARY dbghelp)
if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
endif()
endif()
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt)
endif()

View File

@@ -7,11 +7,6 @@ function(get_timestamp _var)
set(${_var} "${timestamp}" PARENT_SCOPE)
endfunction()
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
# Find the package here with the known path so that the GetGit commands can find it as well
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
# generate git/build information
include(GetGitRevisionDescription)
if(NOT GIT_REF_SPEC)
@@ -29,6 +24,7 @@ get_timestamp(BUILD_DATE)
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
set(REPO_NAME "")
set(BUILD_VERSION "0")
set(BUILD_ID ${DISPLAY_VERSION})
if (BUILD_REPOSITORY)
# regex capture the string nightly or canary into CMAKE_MATCH_1
string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY})
@@ -57,6 +53,4 @@ if (BUILD_REPOSITORY)
endif()
endif()
# The variable SRC_DIR must be passed into the script
# (since it uses the current build directory for all values of CMAKE_*_DIR)
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)

View File

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

View File

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

View File

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View File

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

View File

@@ -1,7 +1,6 @@
[Icon Theme]
Name=colorful
Comment=Colorful theme
Inherits=default
Directories=16x16,48x48,256x256
[16x16]

View File

@@ -6,14 +6,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC>
<qresource prefix="icons/colorful">
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/info.png">icons/16x16/info.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/sync.png">icons/16x16/sync.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>

View File

@@ -5,19 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC>
<qresource prefix="icons/colorful_dark">
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
<file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="qss_icons">

View File

@@ -5,23 +5,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC>
<qresource prefix="icons/default">
<!-- "colorful" is now the default theme, add new icons there -->
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
</qresource>
<qresource prefix="default">
<file>style.qss</file>

View File

@@ -1,6 +1,7 @@
[Icon Theme]
Name=default
Comment=default theme
Inherits=colorful
Directories=16x16,48x48,256x256
[16x16]
@@ -10,4 +11,4 @@ Size=16
Size=48
[256x256]
Size=256
Size=256

View File

@@ -1,7 +1,7 @@
[Icon Theme]
Name=qdarkstyle
Comment=dark theme
Inherits=default
Inherits=colorful
Directories=16x16,48x48,256x256
[16x16]
@@ -11,4 +11,4 @@ Size=16
Size=48
[256x256]
Size=256
Size=256

View File

@@ -1,7 +1,7 @@
[Icon Theme]
Name=qdarkstyle_midnight_blue
Comment=dark theme
Inherits=default
Inherits=colorful
Directories=16x16,48x48,256x256
[16x16]

View File

@@ -8,7 +8,7 @@
namespace AudioCore {
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} {
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
CreateSinks();
// Must be created after the sinks
adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);

View File

@@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
if (input_devices.size() > 1) {
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
names.emplace_back("Uac");
return 1;
}
return 0;

View File

@@ -1,14 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_in_manager.h"
#include "audio_core/audio_manager.h"
#include "audio_core/audio_out_manager.h"
#include "core/core.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore {
AudioManager::AudioManager(Core::System& system_) : system{system_} {
AudioManager::AudioManager() {
thread = std::jthread([this]() { ThreadFunc(); });
}
@@ -27,7 +26,7 @@ Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = buffer_func;
buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioOutManager, true);
}
@@ -43,7 +42,7 @@ Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = buffer_func;
buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioInManager, true);
}
@@ -60,19 +59,21 @@ void AudioManager::ThreadFunc() {
running = true;
while (running) {
auto timed_out{events.Wait(l, std::chrono::seconds(2))};
const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
if (events.CheckAudioEventSet(Event::Type::Max)) {
break;
}
for (size_t i = 0; i < buffer_events.size(); i++) {
if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) {
const auto event_type = static_cast<Event::Type>(i);
if (events.CheckAudioEventSet(event_type) || timed_out) {
if (buffer_events[i]) {
buffer_events[i]();
}
}
events.SetAudioEvent(Event::Type(i), false);
events.SetAudioEvent(event_type, false);
}
}
}

View File

@@ -10,22 +10,11 @@
#include <thread>
#include "audio_core/audio_event.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
union Result;
namespace AudioCore {
namespace AudioOut {
class Manager;
}
namespace AudioIn {
class Manager;
}
/**
* The AudioManager's main purpose is to wait for buffer events for the audio in and out managers,
* and call an associated callback to release buffers.
@@ -43,7 +32,7 @@ class AudioManager {
using BufferEventFunc = std::function<void()>;
public:
explicit AudioManager(Core::System& system);
explicit AudioManager();
/**
* Shutdown the audio manager.
@@ -80,10 +69,6 @@ private:
*/
void ThreadFunc();
/// Core system
Core::System& system;
/// Have sessions started palying?
bool sessions_started{};
/// Is the main thread running?
std::atomic<bool> running{};
/// Unused

View File

@@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
u32 Manager::GetAudioOutDeviceNames(
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
names.push_back({"DeviceOut"});
names.emplace_back("DeviceOut");
return 1;
}

View File

@@ -47,7 +47,7 @@ RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
return msg;
}
CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
return command_buffers[session_id];
}

View File

@@ -83,7 +83,7 @@ public:
* @param session_id - The session id to get (0 or 1).
* @return The command buffer.
*/
CommandBuffer& GetCommandBuffer(s32 session_id);
CommandBuffer& GetCommandBuffer(u32 session_id);
/**
* Set the command buffer with the given session id (0 or 1).

View File

@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <span>
#include "audio_core/audio_core.h"
#include "audio_core/common/feature_support.h"
#include "audio_core/renderer/audio_device.h"
@@ -9,14 +12,33 @@
namespace AudioCore::AudioRenderer {
constexpr std::array usb_device_names{
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
AudioDevice::AudioDeviceName{"AudioTvOutput"},
AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
};
constexpr std::array device_names{
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
AudioDevice::AudioDeviceName{"AudioTvOutput"},
};
constexpr std::array output_device_names{
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
AudioDevice::AudioDeviceName{"AudioTvOutput"},
AudioDevice::AudioDeviceName{"AudioExternalOutput"},
};
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
const u32 revision)
: output_sink{system.AudioCore().GetOutputSink()},
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
const size_t max_count) {
std::span<AudioDeviceName> names{};
const size_t max_count) const {
std::span<const AudioDeviceName> names{};
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
names = usb_device_names;
@@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
names = device_names;
}
u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(names[i]);
}
@@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
}
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
const size_t max_count) {
u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
const size_t max_count) const {
const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(output_device_names[i]);

View File

@@ -3,7 +3,7 @@
#pragma once
#include <span>
#include <string_view>
#include "audio_core/audio_render_manager.h"
@@ -23,21 +23,13 @@ namespace AudioRenderer {
class AudioDevice {
public:
struct AudioDeviceName {
std::array<char, 0x100> name;
std::array<char, 0x100> name{};
AudioDeviceName(const char* name_) {
std::strncpy(name.data(), name_, name.size());
constexpr AudioDeviceName(std::string_view name_) {
name_.copy(name.data(), name.size() - 1);
}
};
std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput", "AudioTvOutput",
"AudioUsbDeviceOutput"};
std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput", "AudioTvOutput"};
std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
"AudioExternalOutput"};
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
/**
@@ -47,7 +39,7 @@ public:
* @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written.
*/
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
/**
* Get a list of the available output devices.
@@ -57,7 +49,7 @@ public:
* @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written.
*/
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
/**
* Set the volume of all streams in the backend sink.

View File

@@ -214,8 +214,13 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// video play out without attempting to stall.
// Can hopefully remove this later with a more complete NVDEC implementation.
const auto nvdec_active{system.AudioCore().IsNVDECActive()};
if (!nvdec_active && queued_buffers > max_queue_size) {
// Core timing cannot be paused in single-core mode, so Stall ends up being called over and over
// and never recovers to a normal state, so just skip attempting to sync things on single-core.
if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) {
Stall();
} else if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
}
while (frames_written < num_frames) {
@@ -255,7 +260,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
if (stalled && queued_buffers <= max_queue_size) {
if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
}
}

View File

@@ -14,32 +14,7 @@ if (DEFINED ENV{DISPLAYVERSION})
set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
endif ()
# Pass the path to git to the GenerateSCMRev.cmake as well
find_package(Git QUIET)
add_custom_command(OUTPUT scm_rev.cpp
COMMAND ${CMAKE_COMMAND}
-DSRC_DIR=${CMAKE_SOURCE_DIR}
-DBUILD_REPOSITORY=${BUILD_REPOSITORY}
-DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
-DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
-DBUILD_TAG=${BUILD_TAG}
-DBUILD_ID=${DISPLAY_VERSION}
-DGIT_REF_SPEC=${GIT_REF_SPEC}
-DGIT_REV=${GIT_REV}
-DGIT_DESC=${GIT_DESC}
-DGIT_BRANCH=${GIT_BRANCH}
-DBUILD_FULLNAME=${BUILD_FULLNAME}
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
-P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
DEPENDS
# Check that the scm_rev files haven't changed
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
# technically we should regenerate if the git version changed, but its not worth the effort imo
"${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
VERBATIM
)
include(GenerateSCMRev)
add_library(common STATIC
algorithm.h
@@ -117,7 +92,7 @@ add_library(common STATIC
quaternion.h
reader_writer_queue.h
ring_buffer.h
scm_rev.cpp
${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
scm_rev.h
scope_exit.h
settings.cpp

View File

@@ -530,6 +530,8 @@ struct Values {
Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
Setting<bool> use_auto_stub{false, "use_auto_stub"};
Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"};
// Miscellaneous
Setting<std::string> log_filter{"*:Info", "log_filter"};

View File

@@ -54,6 +54,10 @@ public:
is_set = false;
}
[[nodiscard]] bool IsSet() {
return is_set;
}
private:
std::condition_variable condvar;
std::mutex mutex;

View File

@@ -4,12 +4,6 @@
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
arm/dynarmic/arm_dynarmic_32.cpp
arm/dynarmic/arm_dynarmic_32.h
arm/dynarmic/arm_dynarmic_64.cpp
arm/dynarmic/arm_dynarmic_64.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
arm/dynarmic/arm_exclusive_monitor.cpp
arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp
@@ -466,6 +460,8 @@ add_library(core STATIC
hle/service/hid/controllers/mouse.h
hle/service/hid/controllers/npad.cpp
hle/service/hid/controllers/npad.h
hle/service/hid/controllers/palma.cpp
hle/service/hid/controllers/palma.h
hle/service/hid/controllers/stubbed.cpp
hle/service/hid/controllers/stubbed.h
hle/service/hid/controllers/touchscreen.cpp
@@ -525,6 +521,9 @@ add_library(core STATIC
hle/service/ncm/ncm.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
hle/service/nfp/amiibo_crypto.cpp
hle/service/nfp/amiibo_crypto.h
hle/service/nfp/amiibo_types.h
hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h
hle/service/nfp/nfp_user.cpp

View File

@@ -134,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool absolute_time) {
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
{
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
event_queue.emplace_back(
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
event_queue.emplace_back(
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
event.Set();
}
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
@@ -229,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() {
basic_lock.lock();
if (evt.reschedule_time != 0) {
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + evt.reschedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + evt.reschedule_time;
}
const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count()
: evt.reschedule_time};
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + next_schedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + next_schedule_time;
}
event_queue.emplace_back(
Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@@ -250,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() {
}
if (!event_queue.empty()) {
const s64 next_time = event_queue.front().time - global_timer;
return next_time;
return event_queue.front().time;
} else {
return std::nullopt;
}
@@ -264,11 +267,29 @@ void CoreTiming::ThreadLoop() {
paused_set = false;
const auto next_time = Advance();
if (next_time) {
if (*next_time > 0) {
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
event.WaitFor(next_time_ns);
// There are more events left in the queue, wait until the next event.
const auto wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time > 0) {
// Assume a timer resolution of 1ms.
static constexpr s64 TimerResolutionNS = 1000000;
// Sleep in discrete intervals of the timer resolution, and spin the rest.
const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
if (sleep_time > 0) {
event.WaitFor(std::chrono::nanoseconds(sleep_time));
}
while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
// Yield to reduce thread starvation.
std::this_thread::yield();
}
if (event.IsSet()) {
event.Reset();
}
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to continue.
wait_set = true;
event.Wait();
}

View File

@@ -32,7 +32,7 @@ enum class MiiEditResult : u32 {
};
struct MiiEditCharInfo {
Service::Mii::MiiInfo mii_info{};
Service::Mii::CharInfo mii_info{};
};
static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size.");

View File

@@ -246,9 +246,8 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
const auto write_count =
static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
std::vector<AudioDevice::AudioDeviceName> device_names{};
std::string print_names{};
if (write_count > 0) {
device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut"));
device_names.emplace_back("DeviceOut");
LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut");
} else {
LOG_DEBUG(Service_Audio, "called. Empty buffer passed in.");

View File

@@ -252,7 +252,7 @@ private:
std::vector<AudioDevice::AudioDeviceName> out_names{};
u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
const u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
std::string out{};
for (u32 i = 0; i < out_count; i++) {
@@ -365,7 +365,7 @@ private:
std::vector<AudioDevice::AudioDeviceName> out_names{};
u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
std::string out{};
for (u32 i = 0; i < out_count; i++) {

View File

@@ -255,6 +255,32 @@ void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) {
GetWorkBufferSize(ctx);
}
void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) {
OpusMultiStreamParametersEx param;
std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
const auto sample_rate = param.sample_rate;
const auto channel_count = param.channel_count;
const auto number_streams = param.number_streams;
const auto number_stereo_streams = param.number_stereo_streams;
LOG_DEBUG(
Audio,
"called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
sample_rate, channel_count, number_streams, number_stereo_streams);
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
"Invalid sample rate");
const u32 worker_buffer_sz =
static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(worker_buffer_sz);
}
void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sample_rate = rp.Pop<u32>();
@@ -335,7 +361,7 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
{6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
{7, nullptr, "GetWorkBufferSizeForMultiStreamEx"},
{7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
};
RegisterHandlers(functions);
}

View File

@@ -11,6 +11,16 @@ class System;
namespace Service::Audio {
struct OpusMultiStreamParametersEx {
u32 sample_rate;
u32 channel_count;
u32 number_streams;
u32 number_stereo_streams;
u32 use_large_frame_size;
u32 padding;
std::array<u32, 64> channel_mappings;
};
class HwOpus final : public ServiceFramework<HwOpus> {
public:
explicit HwOpus(Core::System& system_);
@@ -21,6 +31,7 @@ private:
void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx);
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx);
void GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio

View File

@@ -660,7 +660,6 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
ASSERT(false);
break;
case Core::HID::NpadStyleIndex::ProController:
case Core::HID::NpadStyleIndex::Pokeball:
set_motion_state(sixaxis_fullkey_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::Handheld:
@@ -676,6 +675,11 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
case Core::HID::NpadStyleIndex::JoyconRight:
set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
break;
case Core::HID::NpadStyleIndex::Pokeball:
using namespace std::literals::chrono_literals;
set_motion_state(sixaxis_fullkey_state, motion_state[0]);
sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
break;
default:
break;
}

View File

@@ -0,0 +1,229 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core_timing.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/kernel_helpers.h"
namespace Service::HID {
Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
KernelHelpers::ServiceContext& service_context_)
: ControllerBase{hid_core_}, service_context{service_context_} {
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
}
Controller_Palma::~Controller_Palma() = default;
void Controller_Palma::OnInit() {}
void Controller_Palma::OnRelease() {}
void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
return;
}
}
Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
PalmaConnectionHandle& handle) {
active_handle.npad_id = npad_id;
handle = active_handle;
return ResultSuccess;
}
Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
ActivateController();
return ResultSuccess;
}
Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
const PalmaConnectionHandle& handle) const {
if (handle.npad_id != active_handle.npad_id) {
LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
}
return operation_complete_event->GetReadableEvent();
}
Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
PalmaOperationType& operation_type,
PalmaOperationData& data) const {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation_type = operation.operation;
data = operation.data;
return ResultSuccess;
}
Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
u64 palma_activity) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::PlayActivity;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
PalmaFrModeType fr_mode_) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
fr_mode = fr_mode_;
return ResultSuccess;
}
Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::ReadStep;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return ResultSuccess;
}
Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return ResultSuccess;
}
void Controller_Palma::ReadPalmaApplicationSection() {}
void Controller_Palma::WritePalmaApplicationSection() {}
Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::ReadUniqueCode;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
void Controller_Palma::WritePalmaActivityEntry() {}
Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
u64 unknown) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
u8* t_mem, u64 size) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::WriteWaveEntry;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
s32 database_id_version_) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
database_id_version = database_id_version_;
operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
operation.result = PalmaResultSuccess;
operation.data[0] = {};
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
operation.result = PalmaResultSuccess;
operation.data = {};
operation.data[0] = static_cast<u8>(database_id_version);
operation_complete_event->GetWritableEvent().Signal();
return ResultSuccess;
}
void Controller_Palma::SuspendPalmaFeature() {}
Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return operation.result;
}
void Controller_Palma::ReadPalmaPlayLog() {}
void Controller_Palma::ResetPalmaPlayLog() {}
void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
// If true controllers are able to be paired
is_connectable = is_all_connectable;
}
void Controller_Palma::SetIsPalmaPairedConnectable() {}
Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
// TODO: Do something
return ResultSuccess;
}
void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
void Controller_Palma::CancelWritePalmaWaveEntry() {}
void Controller_Palma::EnablePalmaBoostMode() {}
void Controller_Palma::GetPalmaBluetoothAddress() {}
void Controller_Palma::SetDisallowedPalmaConnection() {}
} // namespace Service::HID

View File

@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/errors.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Service::KernelHelpers {
class ServiceContext;
}
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
class Controller_Palma final : public ControllerBase {
public:
using PalmaOperationData = std::array<u8, 0x140>;
// This is nn::hid::PalmaOperationType
enum class PalmaOperationType {
PlayActivity,
SetFrModeType,
ReadStep,
EnableStep,
ResetStep,
ReadApplicationSection,
WriteApplicationSection,
ReadUniqueCode,
SetUniqueCodeInvalid,
WriteActivityEntry,
WriteRgbLedPatternEntry,
WriteWaveEntry,
ReadDataBaseIdentificationVersion,
WriteDataBaseIdentificationVersion,
SuspendFeature,
ReadPlayLog,
ResetPlayLog,
};
// This is nn::hid::PalmaWaveSet
enum class PalmaWaveSet : u64 {
Small,
Medium,
Large,
};
// This is nn::hid::PalmaFrModeType
enum class PalmaFrModeType : u64 {
Off,
B01,
B02,
B03,
Downloaded,
};
// This is nn::hid::PalmaFeature
enum class PalmaFeature : u64 {
FrMode,
RumbleFeedback,
Step,
MuteSwitch,
};
// This is nn::hid::PalmaOperationInfo
struct PalmaOperationInfo {
PalmaOperationType operation{};
Result result{PalmaResultSuccess};
PalmaOperationData data{};
};
static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
// This is nn::hid::PalmaActivityEntry
struct PalmaActivityEntry {
u32 rgb_led_pattern_index;
INSERT_PADDING_BYTES(2);
PalmaWaveSet wave_set;
u32 wave_index;
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
struct PalmaConnectionHandle {
Core::HID::NpadIdType npad_id;
INSERT_PADDING_BYTES(4); // Unknown
};
static_assert(sizeof(PalmaConnectionHandle) == 0x8,
"PalmaConnectionHandle has incorrect size.");
explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
KernelHelpers::ServiceContext& service_context_);
~Controller_Palma() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
Result InitializePalma(const PalmaConnectionHandle& handle);
Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
const PalmaConnectionHandle& handle) const;
Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
PalmaOperationType& operation_type,
PalmaOperationData& data) const;
Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
Result ReadPalmaStep(const PalmaConnectionHandle& handle);
Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
Result ResetPalmaStep(const PalmaConnectionHandle& handle);
Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave, u8* t_mem,
u64 size);
Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
s32 database_id_version_);
Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
void SetIsPalmaAllConnectable(bool is_all_connectable);
Result PairPalma(const PalmaConnectionHandle& handle);
void SetPalmaBoostMode(bool boost_mode);
private:
void ReadPalmaApplicationSection();
void WritePalmaApplicationSection();
void WritePalmaActivityEntry();
void SuspendPalmaFeature();
void ReadPalmaPlayLog();
void ResetPalmaPlayLog();
void SetIsPalmaPairedConnectable();
void CancelWritePalmaWaveEntry();
void EnablePalmaBoostMode();
void GetPalmaBluetoothAddress();
void SetDisallowedPalmaConnection();
bool is_connectable{};
s32 database_id_version{};
PalmaOperationInfo operation{};
PalmaFrModeType fr_mode{};
PalmaConnectionHandle active_handle{};
Core::HID::EmulatedController* controller;
Kernel::KEvent* operation_complete_event;
KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::HID

View File

@@ -7,6 +7,7 @@
namespace Service::HID {
constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
@@ -17,6 +18,7 @@ constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
constexpr Result NpadIsSameType{ErrorModule::HID, 602};
constexpr Result InvalidNpadId{ErrorModule::HID, 709};
constexpr Result NpadNotConnected{ErrorModule::HID, 710};
constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
} // namespace Service::HID

View File

@@ -27,6 +27,7 @@
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/hle/service/hid/controllers/mouse.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/hid/controllers/stubbed.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/xpad.h"
@@ -60,6 +61,7 @@ IAppletResource::IAppletResource(Core::System& system_,
MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
// Homebrew doesn't try to activate some controllers, so we activate them by default
GetController<Controller_NPad>(HidController::NPad).ActivateController();
@@ -310,36 +312,36 @@ Hid::Hid(Core::System& system_)
{406, nullptr, "GetNpadLeftRightInterfaceType"},
{407, nullptr, "GetNpadOfHighestBatteryLevel"},
{408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
{500, nullptr, "GetPalmaConnectionHandle"},
{501, nullptr, "InitializePalma"},
{502, nullptr, "AcquirePalmaOperationCompleteEvent"},
{503, nullptr, "GetPalmaOperationInfo"},
{504, nullptr, "PlayPalmaActivity"},
{505, nullptr, "SetPalmaFrModeType"},
{506, nullptr, "ReadPalmaStep"},
{507, nullptr, "EnablePalmaStep"},
{508, nullptr, "ResetPalmaStep"},
{509, nullptr, "ReadPalmaApplicationSection"},
{510, nullptr, "WritePalmaApplicationSection"},
{511, nullptr, "ReadPalmaUniqueCode"},
{512, nullptr, "SetPalmaUniqueCodeInvalid"},
{513, nullptr, "WritePalmaActivityEntry"},
{514, nullptr, "WritePalmaRgbLedPatternEntry"},
{515, nullptr, "WritePalmaWaveEntry"},
{516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
{517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
{518, nullptr, "SuspendPalmaFeature"},
{519, nullptr, "GetPalmaOperationResult"},
{520, nullptr, "ReadPalmaPlayLog"},
{521, nullptr, "ResetPalmaPlayLog"},
{500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
{501, &Hid::InitializePalma, "InitializePalma"},
{502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
{503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
{504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
{505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
{506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
{507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
{508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
{509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
{510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
{511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
{512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
{513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
{514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
{515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
{516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
{517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
{518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
{519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
{520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
{521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
{522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
{523, nullptr, "SetIsPalmaPairedConnectable"},
{524, nullptr, "PairPalma"},
{523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
{524, &Hid::PairPalma, "PairPalma"},
{525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
{526, nullptr, "CancelWritePalmaWaveEntry"},
{527, nullptr, "EnablePalmaBoostMode"},
{528, nullptr, "GetPalmaBluetoothAddress"},
{529, nullptr, "SetDisallowedPalmaConnection"},
{526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
{527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
{528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
{529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
{1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
{1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
@@ -1878,14 +1880,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) {
rb.Push(false);
}
void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
const auto is_palma_all_connectable{rp.Pop<bool>()};
struct Parameters {
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
parameters.npad_id, parameters.applet_resource_user_id);
Controller_Palma::PalmaConnectionHandle handle;
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(result);
rb.PushRaw(handle);
}
void Hid::InitializePalma(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.InitializePalma(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
}
void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
Controller_Palma::PalmaOperationType operation_type;
Controller_Palma::PalmaOperationData data;
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
if (result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(result);
rb.Push(static_cast<u64>(operation_type));
}
void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
const auto palma_activity{rp.Pop<u64>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
connection_handle.npad_id, palma_activity);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
connection_handle.npad_id, fr_mode);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.ReadPalmaStep(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
bool is_enabled;
INSERT_PADDING_WORDS_NOINIT(1);
Controller_Palma::PalmaConnectionHandle connection_handle;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
parameters.connection_handle.npad_id, parameters.is_enabled);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result =
controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
const auto result = controller.ResetPalmaStep(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.ReadPalmaUniqueCode(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.SetPalmaUniqueCodeInvalid(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) {
LOG_CRITICAL(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
const auto unknown{rp.Pop<u64>()};
const auto buffer = ctx.ReadBuffer();
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
connection_handle.npad_id, unknown);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.WritePalmaRgbLedPatternEntry(connection_handle, unknown);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
const auto unknown{rp.Pop<u64>()};
const auto t_mem_size{rp.Pop<u64>()};
const auto t_mem_handle{ctx.GetCopyHandle(0)};
const auto size{rp.Pop<u64>()};
ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
auto t_mem =
system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
if (t_mem.IsNull()) {
LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultUnknown);
return;
}
ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
LOG_WARNING(Service_HID,
"(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}",
applet_resource_user_id, is_palma_all_connectable);
"(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
"t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.WritePalmaWaveEntry(connection_handle, wave_set,
system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
s32 database_id_version;
INSERT_PADDING_WORDS_NOINIT(1);
Controller_Palma::PalmaConnectionHandle connection_handle;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
parameters.connection_handle.npad_id, parameters.database_id_version);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
parameters.database_id_version);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.GetPalmaDataBaseIdentificationVersion(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
.GetPalmaOperationResult(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
bool is_palma_all_connectable;
INSERT_PADDING_BYTES_NOINIT(7);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID,
"(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::PairPalma(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.PairPalma(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1897,6 +2246,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
applet_resource->GetController<Controller_Palma>(HidController::Palma)
.SetPalmaBoostMode(palma_boost_mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}

View File

@@ -33,6 +33,7 @@ enum class HidController : std::size_t {
NPad,
Gesture,
ConsoleSixAxisSensor,
Palma,
MaxControllers,
};
@@ -166,8 +167,36 @@ private:
void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx);
void GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx);
void InitializePalma(Kernel::HLERequestContext& ctx);
void AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx);
void GetPalmaOperationInfo(Kernel::HLERequestContext& ctx);
void PlayPalmaActivity(Kernel::HLERequestContext& ctx);
void SetPalmaFrModeType(Kernel::HLERequestContext& ctx);
void ReadPalmaStep(Kernel::HLERequestContext& ctx);
void EnablePalmaStep(Kernel::HLERequestContext& ctx);
void ResetPalmaStep(Kernel::HLERequestContext& ctx);
void ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx);
void WritePalmaApplicationSection(Kernel::HLERequestContext& ctx);
void ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx);
void SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx);
void WritePalmaActivityEntry(Kernel::HLERequestContext& ctx);
void WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx);
void WritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
void SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
void GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
void SuspendPalmaFeature(Kernel::HLERequestContext& ctx);
void GetPalmaOperationResult(Kernel::HLERequestContext& ctx);
void ReadPalmaPlayLog(Kernel::HLERequestContext& ctx);
void ResetPalmaPlayLog(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
void SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx);
void PairPalma(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
void CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
void EnablePalmaBoostMode(Kernel::HLERequestContext& ctx);
void GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx);
void SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx);
void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);

View File

@@ -542,7 +542,8 @@ Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_h
Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
const Core::IrSensor::IrCameraHandle& camera_handle) {
ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id");
const auto npad_id_max_index = static_cast<u8>(sizeof(StatusManager::device));
ASSERT_MSG(camera_handle.npad_id < npad_id_max_index, "invalid npad_id");
return shared_memory->device[camera_handle.npad_id];
}

View File

@@ -43,7 +43,7 @@ public:
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, nullptr, "Convert"},
{23, &IDatabaseService::Convert, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
{26, nullptr, "Append"},
@@ -130,7 +130,7 @@ private:
return;
}
std::vector<MiiInfo> values;
std::vector<CharInfo> values;
for (const auto& element : *result) {
values.emplace_back(element.info);
}
@@ -144,7 +144,7 @@ private:
void UpdateLatest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto info{rp.PopRaw<MiiInfo>()};
const auto info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
@@ -156,9 +156,9 @@ private:
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<MiiInfo>(*result);
rb.PushRaw<CharInfo>(*result);
}
void BuildRandom(Kernel::HLERequestContext& ctx) {
@@ -191,9 +191,9 @@ private:
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race));
rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
}
void BuildDefault(Kernel::HLERequestContext& ctx) {
@@ -210,14 +210,14 @@ private:
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<MiiInfo>(manager.BuildDefault(index));
rb.PushRaw<CharInfo>(manager.BuildDefault(index));
}
void GetIndex(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto info{rp.PopRaw<MiiInfo>()};
const auto info{rp.PopRaw<CharInfo>()};
LOG_DEBUG(Service_Mii, "called");
@@ -239,6 +239,18 @@ private:
rb.Push(ResultSuccess);
}
void Convert(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
LOG_INFO(Service_Mii, "called");
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
}
constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
return current_interface_version >= interface_version;
}

View File

@@ -42,7 +42,7 @@ std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& i
return out;
}
MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
MiiStoreBitFields bf;
std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
@@ -409,8 +409,8 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const {
return static_cast<u32>(count);
}
ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info,
SourceFlag source_flag) {
ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info,
SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ERROR_CANNOT_FIND_ENTRY;
}
@@ -419,14 +419,91 @@ ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info
return ERROR_CANNOT_FIND_ENTRY;
}
MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
}
MiiInfo MiiManager::BuildDefault(std::size_t index) {
CharInfo MiiManager::BuildDefault(std::size_t index) {
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
}
CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
Service::Mii::MiiManager manager;
auto mii = manager.BuildDefault(0);
// Check if mii data exist
if (mii_v3.mii_name[0] == 0) {
return mii;
}
// TODO: We are ignoring a bunch of data from the mii_v3
mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
mii.height = mii_v3.height;
mii.build = mii_v3.build;
memset(mii.name.data(), 0, sizeof(mii.name));
memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name));
mii.font_region = mii_v3.region_information.character_set;
mii.faceline_type = mii_v3.appearance_bits1.face_shape;
mii.faceline_color = mii_v3.appearance_bits1.skin_color;
mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
mii.faceline_make = mii_v3.appearance_bits2.makeup;
mii.hair_type = mii_v3.hair_style;
mii.hair_color = mii_v3.appearance_bits3.hair_color;
mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
// TODO: Validate mii data
return mii;
}
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result;
@@ -441,7 +518,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_
return result;
}
Result MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) {
Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
constexpr u32 INVALID_INDEX{0xFFFFFFFF};
index = INVALID_INDEX;

View File

@@ -19,11 +19,12 @@ public:
bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
bool IsFullDatabase() const;
u32 GetCount(SourceFlag source_flag) const;
ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag);
MiiInfo BuildRandom(Age age, Gender gender, Race race);
MiiInfo BuildDefault(std::size_t index);
ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
CharInfo BuildRandom(Age age, Gender gender, Race race);
CharInfo BuildDefault(std::size_t index);
CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const;
ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
Result GetIndex(const MiiInfo& info, u32& index);
Result GetIndex(const CharInfo& info, u32& index);
private:
const Common::UUID user_id{};

View File

@@ -86,7 +86,8 @@ enum class SourceFlag : u32 {
};
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
struct MiiInfo {
// nn::mii::CharInfo
struct CharInfo {
Common::UUID uuid;
std::array<char16_t, 11> name;
u8 font_region;
@@ -140,16 +141,16 @@ struct MiiInfo {
u8 mole_y;
u8 padding;
};
static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<MiiInfo>,
"All bits of MiiInfo must contribute to its value.");
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<CharInfo>,
"All bits of CharInfo must contribute to its value.");
#pragma pack(push, 4)
struct MiiInfoElement {
MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {}
MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
MiiInfo info{};
CharInfo info{};
Source source{};
};
static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
@@ -243,6 +244,131 @@ static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrec
static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
"MiiStoreBitFields is not trivially copyable.");
// This is nn::mii::Ver3StoreData
// Based on citra HLE::Applets::MiiData and PretendoNetwork.
// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
struct Ver3StoreData {
u8 version;
union {
u8 raw;
BitField<0, 1, u8> allow_copying;
BitField<1, 1, u8> profanity_flag;
BitField<2, 2, u8> region_lock;
BitField<4, 2, u8> character_set;
} region_information;
u16_be mii_id;
u64_be system_id;
u32_be specialness_and_creation_date;
std::array<u8, 0x6> creator_mac;
u16_be padding;
union {
u16 raw;
BitField<0, 1, u16> gender;
BitField<1, 4, u16> birth_month;
BitField<5, 5, u16> birth_day;
BitField<10, 4, u16> favorite_color;
BitField<14, 1, u16> favorite;
} mii_information;
std::array<char16_t, 0xA> mii_name;
u8 height;
u8 build;
union {
u8 raw;
BitField<0, 1, u8> disable_sharing;
BitField<1, 4, u8> face_shape;
BitField<5, 3, u8> skin_color;
} appearance_bits1;
union {
u8 raw;
BitField<0, 4, u8> wrinkles;
BitField<4, 4, u8> makeup;
} appearance_bits2;
u8 hair_style;
union {
u8 raw;
BitField<0, 3, u8> hair_color;
BitField<3, 1, u8> flip_hair;
} appearance_bits3;
union {
u32 raw;
BitField<0, 6, u32> eye_type;
BitField<6, 3, u32> eye_color;
BitField<9, 4, u32> eye_scale;
BitField<13, 3, u32> eye_vertical_stretch;
BitField<16, 5, u32> eye_rotation;
BitField<21, 4, u32> eye_spacing;
BitField<25, 5, u32> eye_y_position;
} appearance_bits4;
union {
u32 raw;
BitField<0, 5, u32> eyebrow_style;
BitField<5, 3, u32> eyebrow_color;
BitField<8, 4, u32> eyebrow_scale;
BitField<12, 3, u32> eyebrow_yscale;
BitField<16, 4, u32> eyebrow_rotation;
BitField<21, 4, u32> eyebrow_spacing;
BitField<25, 5, u32> eyebrow_y_position;
} appearance_bits5;
union {
u16 raw;
BitField<0, 5, u16> nose_type;
BitField<5, 4, u16> nose_scale;
BitField<9, 5, u16> nose_y_position;
} appearance_bits6;
union {
u16 raw;
BitField<0, 6, u16> mouth_type;
BitField<6, 3, u16> mouth_color;
BitField<9, 4, u16> mouth_scale;
BitField<13, 3, u16> mouth_horizontal_stretch;
} appearance_bits7;
union {
u8 raw;
BitField<0, 5, u8> mouth_y_position;
BitField<5, 3, u8> mustache_type;
} appearance_bits8;
u8 allow_copying;
union {
u16 raw;
BitField<0, 3, u16> bear_type;
BitField<3, 3, u16> facial_hair_color;
BitField<6, 4, u16> mustache_scale;
BitField<10, 5, u16> mustache_y_position;
} appearance_bits9;
union {
u16 raw;
BitField<0, 4, u16> glasses_type;
BitField<4, 3, u16> glasses_color;
BitField<7, 4, u16> glasses_scale;
BitField<11, 5, u16> glasses_y_position;
} appearance_bits10;
union {
u16 raw;
BitField<0, 1, u16> mole_enabled;
BitField<1, 4, u16> mole_scale;
BitField<5, 5, u16> mole_x_position;
BitField<10, 5, u16> mole_y_position;
} appearance_bits11;
std::array<u16_le, 0xA> author_name;
INSERT_PADDING_BYTES(0x4);
};
static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
struct MiiStoreData {
using Name = std::array<char16_t, 10>;

View File

@@ -0,0 +1,383 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool
// SPDX-License-Identifier: MIT
#include <array>
#include <mbedtls/aes.h>
#include <mbedtls/hmac_drbg.h>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/amiibo_crypto.h"
namespace Service::NFP::AmiiboCrypto {
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
const auto& amiibo_data = ntag_file.user_memory;
LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter);
LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
// Validate UUID
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) {
return false;
}
if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) !=
ntag_file.uuid[8]) {
return false;
}
// Check against all know constants on an amiibo binary
if (ntag_file.static_lock != 0xE00F) {
return false;
}
if (ntag_file.compability_container != 0xEEFF10F1U) {
return false;
}
if (amiibo_data.constant_value != 0xA5) {
return false;
}
if (amiibo_data.model_info.constant_value != 0x02) {
return false;
}
// dynamic_lock value apparently is not constant
// ntag_file.dynamic_lock == 0x0F0001
if (ntag_file.CFG0 != 0x04000000U) {
return false;
}
if (ntag_file.CFG1 != 0x5F) {
return false;
}
return true;
}
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
NTAG215File encoded_data{};
memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2));
encoded_data.static_lock = nfc_data.static_lock;
encoded_data.compability_container = nfc_data.compability_container;
encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
encoded_data.constant_value = nfc_data.user_memory.constant_value;
encoded_data.write_counter = nfc_data.user_memory.write_counter;
encoded_data.settings = nfc_data.user_memory.settings;
encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
encoded_data.title_id = nfc_data.user_memory.title_id;
encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
encoded_data.unknown = nfc_data.user_memory.unknown;
encoded_data.hash = nfc_data.user_memory.hash;
encoded_data.application_area = nfc_data.user_memory.application_area;
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid));
encoded_data.model_info = nfc_data.user_memory.model_info;
encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
encoded_data.dynamic_lock = nfc_data.dynamic_lock;
encoded_data.CFG0 = nfc_data.CFG0;
encoded_data.CFG1 = nfc_data.CFG1;
encoded_data.password = nfc_data.password;
return encoded_data;
}
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
EncryptedNTAG215File nfc_data{};
memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2));
memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid));
nfc_data.static_lock = encoded_data.static_lock;
nfc_data.compability_container = encoded_data.compability_container;
nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
nfc_data.user_memory.constant_value = encoded_data.constant_value;
nfc_data.user_memory.write_counter = encoded_data.write_counter;
nfc_data.user_memory.settings = encoded_data.settings;
nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
nfc_data.user_memory.title_id = encoded_data.title_id;
nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
nfc_data.user_memory.unknown = encoded_data.unknown;
nfc_data.user_memory.hash = encoded_data.hash;
nfc_data.user_memory.application_area = encoded_data.application_area;
nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
nfc_data.user_memory.model_info = encoded_data.model_info;
nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
nfc_data.dynamic_lock = encoded_data.dynamic_lock;
nfc_data.CFG0 = encoded_data.CFG0;
nfc_data.CFG1 = encoded_data.CFG1;
nfc_data.password = encoded_data.password;
return nfc_data;
}
u32 GetTagPassword(const TagUuid& uuid) {
// Verifiy that the generated password is correct
u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
return password;
}
HashSeed GetSeed(const NTAG215File& data) {
HashSeed seed{
.magic = data.write_counter,
.padding = {},
.uuid1 = {},
.uuid2 = {},
.keygen_salt = data.keygen_salt,
};
// Copy the first 8 bytes of uuid
memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
return seed;
}
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length;
const std::size_t string_size = key.type_string.size();
std::vector<u8> output(string_size + seedPart1Len);
// Copy whole type string
memccpy(output.data(), key.type_string.data(), '\0', string_size);
// Append (16 - magic_length) from the input seed
memcpy(output.data() + string_size, &seed, seedPart1Len);
// Append all bytes from magicBytes
output.insert(output.end(), key.magic_bytes.begin(),
key.magic_bytes.begin() + key.magic_length);
output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end());
output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end());
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
}
return output;
}
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed) {
// Initialize context
ctx.used = false;
ctx.counter = 0;
ctx.buffer_size = sizeof(ctx.counter) + seed.size();
memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
// Initialize HMAC context
mbedtls_md_init(&hmac_ctx);
mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
}
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
// If used at least once, reinitialize the HMAC
if (ctx.used) {
mbedtls_md_hmac_reset(&hmac_ctx);
}
ctx.used = true;
// Store counter in big endian, and increment it
ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
ctx.counter++;
// Do HMAC magic
mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
ctx.buffer_size);
mbedtls_md_hmac_finish(&hmac_ctx, output.data());
}
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
const auto seed = GetSeed(data);
// Generate internal seed
const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
// Initialize context
CryptoCtx ctx{};
mbedtls_md_context_t hmac_ctx;
CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
// Generate derived keys
DerivedKeys derived_keys{};
std::array<DrgbOutput, 2> temp{};
CryptoStep(ctx, hmac_ctx, temp[0]);
CryptoStep(ctx, hmac_ctx, temp[1]);
memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
// Cleanup context
mbedtls_md_free(&hmac_ctx);
return derived_keys;
}
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
mbedtls_aes_context aes;
std::size_t nc_off = 0;
std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
std::array<u8, sizeof(keys.aes_iv)> stream_block{};
const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));
constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
stream_block.data(),
reinterpret_cast<const unsigned char*>(&in_data.settings),
reinterpret_cast<unsigned char*>(&out_data.settings));
// Copy the rest of the data directly
out_data.uuid2 = in_data.uuid2;
out_data.static_lock = in_data.static_lock;
out_data.compability_container = in_data.compability_container;
out_data.constant_value = in_data.constant_value;
out_data.write_counter = in_data.write_counter;
out_data.uuid = in_data.uuid;
out_data.model_info = in_data.model_info;
out_data.keygen_salt = in_data.keygen_salt;
out_data.dynamic_lock = in_data.dynamic_lock;
out_data.CFG0 = in_data.CFG0;
out_data.CFG1 = in_data.CFG1;
out_data.password = in_data.password;
}
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin",
Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFP, "No keys detected");
return false;
}
if (keys_file.Read(unfixed_info) != 1) {
LOG_ERROR(Service_NFP, "Failed to read unfixed_info");
return false;
}
if (keys_file.Read(locked_secret) != 1) {
LOG_ERROR(Service_NFP, "Failed to read locked-secret");
return false;
}
return true;
}
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false;
}
// Generate keys
NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
const auto data_keys = GenerateKey(unfixed_info, encoded_data);
const auto tag_keys = GenerateKey(locked_secret, encoded_data);
// Decrypt
Cipher(data_keys, encoded_data, tag_data);
// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
// Regenerate data HMAC
constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
sizeof(HmacKey),
reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
reinterpret_cast<unsigned char*>(&tag_data.hmac_data));
if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
LOG_ERROR(Service_NFP, "hmac_data doesn't match");
return false;
}
if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
LOG_ERROR(Service_NFP, "hmac_tag doesn't match");
return false;
}
return true;
}
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false;
}
// Generate keys
const auto data_keys = GenerateKey(unfixed_info, tag_data);
const auto tag_keys = GenerateKey(locked_secret, tag_data);
NTAG215File encoded_tag_data{};
// Generate tag HMAC
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
// Init mbedtls HMAC context
mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
// Generate data HMAC
mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2); // Data
mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
sizeof(HashData)); // Tag HMAC
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid),
input_length);
mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
// HMAC cleanup
mbedtls_md_free(&ctx);
// Encrypt
Cipher(data_keys, tag_data, encoded_tag_data);
// Convert back to hardware
encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
return true;
}
} // namespace Service::NFP::AmiiboCrypto

View File

@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "core/hle/service/nfp/amiibo_types.h"
struct mbedtls_md_context_t;
namespace Service::NFP::AmiiboCrypto {
// Byte locations in Service::NFP::NTAG215File
constexpr std::size_t HMAC_DATA_START = 0x8;
constexpr std::size_t SETTINGS_START = 0x2c;
constexpr std::size_t WRITE_COUNTER_START = 0x29;
constexpr std::size_t HMAC_TAG_START = 0x1B4;
constexpr std::size_t UUID_START = 0x1D4;
constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed {
u16 magic;
std::array<u8, 0xE> padding;
std::array<u8, 0x8> uuid1;
std::array<u8, 0x8> uuid2;
std::array<u8, 0x20> keygen_salt;
};
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
struct InternalKey {
HmacKey hmac_key;
std::array<char, 0xE> type_string;
u8 reserved;
u8 magic_length;
std::array<u8, 0x10> magic_bytes;
std::array<u8, 0x20> xor_pad;
};
static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
struct CryptoCtx {
std::array<char, 480> buffer;
bool used;
std::size_t buffer_size;
s16 counter;
};
struct DerivedKeys {
std::array<u8, 0x10> aes_key;
std::array<u8, 0x10> aes_iv;
std::array<u8, 0x10> hmac_key;
};
static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
/// Validates that the amiibo file is not corrupted
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
/// Converts from encrypted file format to encoded file format
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
/// Converts from encoded file format to encrypted file format
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
/// Returns password needed to allow write access to protected memory
u32 GetTagPassword(const TagUuid& uuid);
// Generates Seed needed for key derivation
HashSeed GetSeed(const NTAG215File& data);
// Middle step on the generation of derived keys
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
// Initializes mbedtls context
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed);
// Feeds data to mbedtls context to generate the derived key
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output);
// Generates the derived key from amiibo data
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
// Encodes or decodes amiibo data
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
/// Loads both amiibo keys from key_retail.bin
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
/// Decodes encripted amiibo data returns true if output is valid
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
/// Encodes plain amiibo data returns true if output is valid
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
} // namespace Service::NFP::AmiiboCrypto

View File

@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "core/hle/service/mii/types.h"
namespace Service::NFP {
static constexpr std::size_t amiibo_name_length = 0xA;
enum class ServiceType : u32 {
User,
Debug,
System,
};
enum class State : u32 {
NonInitialized,
Initialized,
};
enum class DeviceState : u32 {
Initialized,
SearchingForTag,
TagFound,
TagRemoved,
TagMounted,
Unaviable,
Finalized,
};
enum class ModelType : u32 {
Amiibo,
};
enum class MountTarget : u32 {
Rom,
Ram,
All,
};
enum class AmiiboType : u8 {
Figure,
Card,
Yarn,
};
enum class AmiiboSeries : u8 {
SuperSmashBros,
SuperMario,
ChibiRobo,
YoshiWoollyWorld,
Splatoon,
AnimalCrossing,
EightBitMario,
Skylanders,
Unknown8,
TheLegendOfZelda,
ShovelKnight,
Unknown11,
Kiby,
Pokemon,
MarioSportsSuperstars,
MonsterHunter,
BoxBoy,
Pikmin,
FireEmblem,
Metroid,
Others,
MegaMan,
Diablo,
};
using TagUuid = std::array<u8, 10>;
using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>;
struct AmiiboDate {
u16 raw_date{};
u16 GetYear() const {
return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000);
}
u8 GetMonth() const {
return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1);
}
u8 GetDay() const {
return static_cast<u8>(raw_date & 0x001F);
}
};
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
struct Settings {
union {
u8 raw{};
BitField<4, 1, u8> amiibo_initialized;
BitField<5, 1, u8> appdata_initialized;
};
};
static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
struct AmiiboSettings {
Settings settings;
u8 country_code_id;
u16_be crc_counter; // Incremented each time crc is changed
AmiiboDate init_date;
AmiiboDate write_date;
u32_be crc;
std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
};
static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
struct AmiiboModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
u16 model_number;
AmiiboSeries series;
u8 constant_value; // Must be 02
INSERT_PADDING_BYTES(0x4); // Unknown
};
static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
struct NTAG215Password {
u32 PWD; // Password to allow write access
u16 PACK; // Password acknowledge reply
u16 RFUI; // Reserved for future use
};
static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
#pragma pack(1)
struct EncryptedAmiiboFile {
u8 constant_value; // Must be A5
u16 write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
AmiiboModelInfo model_info; // Encrypted amiibo model info
HashData keygen_salt; // Salt
HashData hmac_data; // Hash
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
u64_be title_id; // Encrypted Game id
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
std::array<u8, 0x2> unknown;
HashData hash; // Probably a SHA256-HMAC hash?
ApplicationArea application_area; // Encrypted Game data
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215File {
std::array<u8, 0x2> uuid2;
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
HashData hmac_data; // Hash
u8 constant_value; // Must be A5
u16 write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings;
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
u64_be title_id;
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id;
std::array<u8, 0x2> unknown;
HashData hash; // Probably a SHA256-HMAC hash?
ApplicationArea application_area; // Encrypted Game data
HashData hmac_tag; // Hash
std::array<u8, 0x8> uuid;
AmiiboModelInfo model_info;
HashData keygen_salt; // Salt
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
#pragma pack()
struct EncryptedNTAG215File {
TagUuid uuid; // Unique serial number
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
EncryptedAmiiboFile user_memory; // Writable data
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable.");
} // namespace Service::NFP

View File

@@ -4,7 +4,10 @@
#include <array>
#include <atomic>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -12,6 +15,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/amiibo_crypto.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
@@ -19,12 +23,14 @@ namespace Service::NFP {
namespace ErrCodes {
constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
constexpr Result NfcDisabled(ErrorModule::NFP, 80);
constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result TagRemoved(ErrorModule::NFP, 97);
constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
} // namespace ErrCodes
constexpr u32 ApplicationAreaSize = 0xD8;
IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
: ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
nfp_interface{nfp_interface_} {
@@ -39,7 +45,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
{7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
{8, &IUser::GetApplicationArea, "GetApplicationArea"},
{9, &IUser::SetApplicationArea, "SetApplicationArea"},
{10, nullptr, "Flush"},
{10, &IUser::Flush, "Flush"},
{11, nullptr, "Restore"},
{12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
{13, &IUser::GetTagInfo, "GetTagInfo"},
@@ -53,7 +59,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
{21, &IUser::GetNpadId, "GetNpadId"},
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, nullptr, "RecreateApplicationArea"},
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
@@ -87,11 +93,23 @@ void IUser::Finalize(Kernel::HLERequestContext& ctx) {
void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
std::vector<u64> devices;
// TODO(german77): Loop through all interfaces
devices.push_back(nfp_interface.GetHandle());
if (devices.size() == 0) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::DeviceNotFound);
return;
}
ctx.WriteBuffer(devices);
IPC::ResponseBuilder rb{ctx, 3};
@@ -105,6 +123,12 @@ void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
const auto nfp_protocol{rp.Pop<s32>()};
LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.StartDetection(nfp_protocol);
@@ -124,6 +148,12 @@ void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.StopDetection();
@@ -146,6 +176,12 @@ void IUser::Mount(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
model_type, mount_target);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.Mount();
@@ -165,6 +201,12 @@ void IUser::Unmount(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.Unmount();
@@ -186,6 +228,12 @@ void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
access_id);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.OpenApplicationArea(access_id);
@@ -205,9 +253,15 @@ void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
std::vector<u8> data{};
ApplicationArea data{};
const auto result = nfp_interface.GetApplicationArea(data);
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 3};
@@ -229,6 +283,12 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.SetApplicationArea(data);
@@ -243,6 +303,31 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
rb.Push(ErrCodes::DeviceNotFound);
}
void IUser::Flush(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.Flush();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::DeviceNotFound);
}
void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
@@ -251,6 +336,12 @@ void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
device_handle, access_id, data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.CreateApplicationArea(access_id, data);
@@ -270,6 +361,12 @@ void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
TagInfo tag_info{};
@@ -291,6 +388,12 @@ void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
RegisterInfo register_info{};
@@ -312,6 +415,12 @@ void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
CommonInfo common_info{};
@@ -333,6 +442,12 @@ void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
ModelInfo model_info{};
@@ -354,6 +469,12 @@ void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -373,6 +494,12 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -419,6 +546,12 @@ void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 3};
@@ -442,7 +575,7 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(ApplicationAreaSize);
rb.Push(sizeof(ApplicationArea));
return;
}
@@ -455,11 +588,45 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "(STUBBED) called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(availability_change_event->GetReadableEvent());
}
void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto access_id{rp.Pop<u32>()};
const auto data{ctx.ReadBuffer()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
device_handle, access_id, data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.RecreateApplicationArea(access_id, data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::DeviceNotFound);
}
Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
const char* name)
: ServiceFramework{system_, name}, module{std::move(module_)},
@@ -478,37 +645,43 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IUser>(*this, system);
}
bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
bool Module::Interface::LoadAmiiboFile(const std::string& filename) {
constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!amiibo_file.IsOpen()) {
LOG_ERROR(Service_NFP, "Amiibo is already on use");
return false;
}
// Workaround for files with missing password data
std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
if (amiibo_file.Read(buffer) < tag_size_without_password) {
LOG_ERROR(Service_NFP, "Failed to read amiibo file");
return false;
}
memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
LOG_INFO(Service_NFP, "Invalid amiibo");
return false;
}
file_path = filename;
return true;
}
bool Module::Interface::LoadAmiibo(const std::string& filename) {
if (device_state != DeviceState::SearchingForTag) {
LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
return false;
}
constexpr auto tag_size = sizeof(NTAG215File);
constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
std::vector<u8> amiibo_buffer = buffer;
if (amiibo_buffer.size() < tag_size_without_password) {
LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size());
if (!LoadAmiiboFile(filename)) {
return false;
}
// Ensure it has the correct size
if (amiibo_buffer.size() != tag_size) {
amiibo_buffer.resize(tag_size, 0);
}
LOG_INFO(Service_NFP, "Amiibo detected");
std::memcpy(&tag_data, buffer.data(), tag_size);
if (!IsAmiiboValid()) {
return false;
}
// This value can't be dumped from a tag. Generate it
tag_data.password.PWD = GetTagPassword(tag_data.uuid);
device_state = DeviceState::TagFound;
activate_event->GetWritableEvent().Signal();
return true;
@@ -517,55 +690,13 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
void Module::Interface::CloseAmiibo() {
LOG_INFO(Service_NFP, "Remove amiibo");
device_state = DeviceState::TagRemoved;
is_data_decoded = false;
is_application_area_initialized = false;
application_area_id = 0;
application_area_data.clear();
encrypted_tag_data = {};
tag_data = {};
deactivate_event->GetWritableEvent().Signal();
}
bool Module::Interface::IsAmiiboValid() const {
const auto& amiibo_data = tag_data.user_memory;
LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", tag_data.lock_bytes);
LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", tag_data.compability_container);
LOG_DEBUG(Service_NFP, "crypto_init=0x{0:x}", amiibo_data.crypto_init);
LOG_DEBUG(Service_NFP, "write_count={}", amiibo_data.write_count);
LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.fixed);
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", tag_data.dynamic_lock);
LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", tag_data.CFG0);
LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", tag_data.CFG1);
// Check against all know constants on an amiibo binary
if (tag_data.lock_bytes != 0xE00F) {
return false;
}
if (tag_data.compability_container != 0xEEFF10F1U) {
return false;
}
if ((amiibo_data.crypto_init & 0xFF) != 0xA5) {
return false;
}
if (amiibo_data.model_info.fixed != 0x02) {
return false;
}
if ((tag_data.dynamic_lock & 0xFFFFFF) != 0x0F0001) {
return false;
}
if (tag_data.CFG0 != 0x04000000U) {
return false;
}
if (tag_data.CFG1 != 0x5F) {
return false;
}
return true;
}
Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
return activate_event->GetReadableEvent();
}
@@ -576,13 +707,20 @@ Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
void Module::Interface::Initialize() {
device_state = DeviceState::Initialized;
is_data_decoded = false;
is_application_area_initialized = false;
encrypted_tag_data = {};
tag_data = {};
}
void Module::Interface::Finalize() {
if (device_state == DeviceState::TagMounted) {
Unmount();
}
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
StopDetection();
}
device_state = DeviceState::Unaviable;
is_application_area_initialized = false;
application_area_id = 0;
application_area_data.clear();
}
Result Module::Interface::StartDetection(s32 protocol_) {
@@ -618,42 +756,102 @@ Result Module::Interface::StopDetection() {
return ErrCodes::WrongDeviceState;
}
Result Module::Interface::Mount() {
if (device_state == DeviceState::TagFound) {
device_state = DeviceState::TagMounted;
Result Module::Interface::Flush() {
// Ignore write command if we can't encrypt the data
if (!is_data_decoded) {
return ResultSuccess;
}
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
EncryptedNTAG215File tmp_encrypted_tag_data{};
const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
Common::FS::FileType::BinaryFile};
if (!amiibo_file.IsOpen()) {
LOG_ERROR(Core, "Amiibo is already on use");
return ErrCodes::WriteAmiiboFailed;
}
// Workaround for files with missing password data
std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
if (amiibo_file.Read(buffer) < tag_size_without_password) {
LOG_ERROR(Core, "Failed to read amiibo file");
return ErrCodes::WriteAmiiboFailed;
}
memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) {
LOG_INFO(Service_NFP, "Invalid amiibo");
return ErrCodes::WriteAmiiboFailed;
}
bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0;
bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id ==
tag_data.model_info.character_id;
if (!is_uuid_equal || !is_character_equal) {
LOG_ERROR(Service_NFP, "Not the same amiibo");
return ErrCodes::WriteAmiiboFailed;
}
if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Failed to encode data");
return ErrCodes::WriteAmiiboFailed;
}
// Return to the start of the file
if (!amiibo_file.Seek(0)) {
LOG_ERROR(Service_NFP, "Error writing to file");
return ErrCodes::WriteAmiiboFailed;
}
if (!amiibo_file.Write(encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Error writing to file");
return ErrCodes::WriteAmiiboFailed;
}
return ResultSuccess;
}
Result Module::Interface::Mount() {
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
}
is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data);
LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded);
is_application_area_initialized = false;
device_state = DeviceState::TagMounted;
return ResultSuccess;
}
Result Module::Interface::Unmount() {
if (device_state == DeviceState::TagMounted) {
is_application_area_initialized = false;
application_area_id = 0;
application_area_data.clear();
device_state = DeviceState::TagFound;
return ResultSuccess;
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
}
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
is_data_decoded = false;
is_application_area_initialized = false;
device_state = DeviceState::TagFound;
return ResultSuccess;
}
Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
tag_info = {
.uuid = tag_data.uuid,
.uuid_length = static_cast<u8>(tag_data.uuid.size()),
.protocol = protocol,
.tag_type = static_cast<u32>(tag_data.user_memory.model_info.amiibo_type),
};
return ResultSuccess;
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
}
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
tag_info = {
.uuid = encrypted_tag_data.uuid,
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
.protocol = protocol,
.tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
};
return ResultSuccess;
}
Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
@@ -662,14 +860,28 @@ Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
return ErrCodes::WrongDeviceState;
}
// Read this data from the amiibo save file
if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
const auto& settings = tag_data.settings;
// TODO: Validate this data
common_info = {
.last_write_year = settings.write_date.GetYear(),
.last_write_month = settings.write_date.GetMonth(),
.last_write_day = settings.write_date.GetDay(),
.write_counter = settings.crc_counter,
.version = 1,
.application_area_size = sizeof(ApplicationArea),
};
return ResultSuccess;
}
// Generate a generic answer
common_info = {
.last_write_year = 2022,
.last_write_month = 2,
.last_write_day = 7,
.write_counter = tag_data.user_memory.write_count,
.write_counter = 0,
.version = 1,
.application_area_size = ApplicationAreaSize,
.application_area_size = sizeof(ApplicationArea),
};
return ResultSuccess;
}
@@ -680,26 +892,53 @@ Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
return ErrCodes::WrongDeviceState;
}
model_info = tag_data.user_memory.model_info;
const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
model_info = {
.character_id = model_info_data.character_id,
.character_variant = model_info_data.character_variant,
.amiibo_type = model_info_data.amiibo_type,
.model_number = model_info_data.model_number,
.series = model_info_data.series,
.constant_value = model_info_data.constant_value,
};
return ResultSuccess;
}
Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
Service::Mii::MiiManager manager;
// Read this data from the amiibo save file
if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
const auto& settings = tag_data.settings;
// TODO: Validate this data
register_info = {
.mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
.first_write_year = settings.init_date.GetYear(),
.first_write_month = settings.init_date.GetMonth(),
.first_write_day = settings.init_date.GetDay(),
.amiibo_name = GetAmiiboName(settings),
.font_region = {},
};
return ResultSuccess;
}
// Generate a generic answer
register_info = {
.mii_char_info = manager.BuildDefault(0),
.first_write_year = 2022,
.first_write_month = 2,
.first_write_day = 7,
.amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
.unknown = {},
.font_region = {},
};
return ResultSuccess;
}
@@ -707,31 +946,47 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
Result Module::Interface::OpenApplicationArea(u32 access_id) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (AmiiboApplicationDataExist(access_id)) {
application_area_data = LoadAmiiboApplicationData(access_id);
application_area_id = access_id;
is_application_area_initialized = true;
}
if (!is_application_area_initialized) {
// Fallback for lack of amiibo keys
if (!is_data_decoded) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
if (tag_data.settings.settings.appdata_initialized == 0) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
if (tag_data.application_area_id != access_id) {
LOG_WARNING(Service_NFP, "Wrong application area id");
return ErrCodes::WrongApplicationAreaId;
}
is_application_area_initialized = true;
return ResultSuccess;
}
Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const {
Result Module::Interface::GetApplicationArea(ApplicationArea& data) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (!is_application_area_initialized) {
LOG_ERROR(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
data = application_area_data;
data = tag_data.application_area;
return ResultSuccess;
}
@@ -739,46 +994,69 @@ Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const {
Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (!is_application_area_initialized) {
LOG_ERROR(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
application_area_data = data;
SaveAmiiboApplicationData(application_area_id, application_area_data);
if (data.size() != sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
return ResultSuccess;
}
Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (AmiiboApplicationDataExist(access_id)) {
if (tag_data.settings.settings.appdata_initialized != 0) {
LOG_ERROR(Service_NFP, "Application area already exist");
return ErrCodes::ApplicationAreaExist;
}
application_area_data = data;
application_area_id = access_id;
SaveAmiiboApplicationData(application_area_id, application_area_data);
if (data.size() != sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
tag_data.application_area_id = access_id;
return ResultSuccess;
}
bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const {
// TODO(german77): Check if file exist
return false;
}
Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
std::vector<u8> Module::Interface::LoadAmiiboApplicationData(u32 access_id) const {
// TODO(german77): Read file
std::vector<u8> data(ApplicationAreaSize);
return data;
}
if (data.size() != sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
void Module::Interface::SaveAmiiboApplicationData(u32 access_id,
const std::vector<u8>& data) const {
// TODO(german77): Save file
std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
tag_data.application_area_id = access_id;
return ResultSuccess;
}
u64 Module::Interface::GetHandle() const {
@@ -791,16 +1069,25 @@ DeviceState Module::Interface::GetCurrentState() const {
}
Core::HID::NpadIdType Module::Interface::GetNpadId() const {
return npad_id;
// Return first connected npad id as a workaround for lack of a single nfc interface per
// controller
return system.HIDCore().GetFirstNpadId();
}
u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const {
// Verifiy that the generated password is correct
u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
return password;
AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const {
std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
AmiiboName amiibo_name{};
// Convert from big endian to little endian
for (std::size_t i = 0; i < amiibo_name_length; i++) {
settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
}
// Convert from utf16 to utf8
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
return amiibo_name;
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {

View File

@@ -9,6 +9,7 @@
#include "common/common_funcs.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfp/amiibo_types.h"
#include "core/hle/service/service.h"
namespace Kernel {
@@ -21,71 +22,7 @@ enum class NpadIdType : u32;
} // namespace Core::HID
namespace Service::NFP {
enum class ServiceType : u32 {
User,
Debug,
System,
};
enum class State : u32 {
NonInitialized,
Initialized,
};
enum class DeviceState : u32 {
Initialized,
SearchingForTag,
TagFound,
TagRemoved,
TagMounted,
Unaviable,
Finalized,
};
enum class ModelType : u32 {
Amiibo,
};
enum class MountTarget : u32 {
Rom,
Ram,
All,
};
enum class AmiiboType : u8 {
Figure,
Card,
Yarn,
};
enum class AmiiboSeries : u8 {
SuperSmashBros,
SuperMario,
ChibiRobo,
YoshiWoollyWorld,
Splatoon,
AnimalCrossing,
EightBitMario,
Skylanders,
Unknown8,
TheLegendOfZelda,
ShovelKnight,
Unknown11,
Kiby,
Pokemon,
MarioSportsSuperstars,
MonsterHunter,
BoxBoy,
Pikmin,
FireEmblem,
Metroid,
Others,
MegaMan,
Diablo
};
using TagUuid = std::array<u8, 10>;
using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
struct TagInfo {
TagUuid uuid;
@@ -114,21 +51,19 @@ struct ModelInfo {
AmiiboType amiibo_type;
u16 model_number;
AmiiboSeries series;
u8 fixed; // Must be 02
INSERT_PADDING_BYTES(0x4); // Unknown
INSERT_PADDING_BYTES(0x20); // Probably a SHA256-(HMAC?) hash
INSERT_PADDING_BYTES(0x14); // SHA256-HMAC
u8 constant_value; // Must be 02
INSERT_PADDING_BYTES(0x38); // Unknown
};
static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
struct RegisterInfo {
Service::Mii::MiiInfo mii_char_info;
Service::Mii::CharInfo mii_char_info;
u16 first_write_year;
u8 first_write_month;
u8 first_write_day;
std::array<u8, 11> amiibo_name;
u8 unknown;
INSERT_PADDING_BYTES(0x98);
AmiiboName amiibo_name;
u8 font_region;
INSERT_PADDING_BYTES(0x7A);
};
static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
@@ -140,39 +75,9 @@ public:
const char* name);
~Interface() override;
struct EncryptedAmiiboFile {
u16 crypto_init; // Must be A5 XX
u16 write_count; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x20); // System crypts
INSERT_PADDING_BYTES(0x20); // SHA256-(HMAC?) hash
ModelInfo model_info; // This struct is bigger than documentation
INSERT_PADDING_BYTES(0xC); // SHA256-HMAC
INSERT_PADDING_BYTES(0x114); // section 1 encrypted buffer
INSERT_PADDING_BYTES(0x54); // section 2 encrypted buffer
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215Password {
u32 PWD; // Password to allow write access
u16 PACK; // Password acknowledge reply
u16 RFUI; // Reserved for future use
};
static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
struct NTAG215File {
TagUuid uuid; // Unique serial number
u16 lock_bytes; // Set defined pages as read only
u32 compability_container; // Defines available memory
EncryptedAmiiboFile user_memory; // Writable data
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
void CreateUserInterface(Kernel::HLERequestContext& ctx);
bool LoadAmiibo(const std::vector<u8>& buffer);
bool LoadAmiibo(const std::string& filename);
bool LoadAmiiboFile(const std::string& filename);
void CloseAmiibo();
void Initialize();
@@ -182,6 +87,7 @@ public:
Result StopDetection();
Result Mount();
Result Unmount();
Result Flush();
Result GetTagInfo(TagInfo& tag_info) const;
Result GetCommonInfo(CommonInfo& common_info) const;
@@ -189,9 +95,10 @@ public:
Result GetRegisterInfo(RegisterInfo& register_info) const;
Result OpenApplicationArea(u32 access_id);
Result GetApplicationArea(std::vector<u8>& data) const;
Result GetApplicationArea(ApplicationArea& data) const;
Result SetApplicationArea(const std::vector<u8>& data);
Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
u64 GetHandle() const;
DeviceState GetCurrentState() const;
@@ -204,27 +111,21 @@ public:
std::shared_ptr<Module> module;
private:
/// Validates that the amiibo file is not corrupted
bool IsAmiiboValid() const;
bool AmiiboApplicationDataExist(u32 access_id) const;
std::vector<u8> LoadAmiiboApplicationData(u32 access_id) const;
void SaveAmiiboApplicationData(u32 access_id, const std::vector<u8>& data) const;
/// return password needed to allow write access to protected memory
u32 GetTagPassword(const TagUuid& uuid) const;
AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
const Core::HID::NpadIdType npad_id;
DeviceState device_state{DeviceState::Unaviable};
KernelHelpers::ServiceContext service_context;
bool is_data_decoded{};
bool is_application_area_initialized{};
s32 protocol;
std::string file_path{};
Kernel::KEvent* activate_event;
Kernel::KEvent* deactivate_event;
DeviceState device_state{DeviceState::Unaviable};
KernelHelpers::ServiceContext service_context;
NTAG215File tag_data{};
s32 protocol;
bool is_application_area_initialized{};
u32 application_area_id;
std::vector<u8> application_area_data;
EncryptedNTAG215File encrypted_tag_data{};
};
};
@@ -243,6 +144,7 @@ private:
void OpenApplicationArea(Kernel::HLERequestContext& ctx);
void GetApplicationArea(Kernel::HLERequestContext& ctx);
void SetApplicationArea(Kernel::HLERequestContext& ctx);
void Flush(Kernel::HLERequestContext& ctx);
void CreateApplicationArea(Kernel::HLERequestContext& ctx);
void GetTagInfo(Kernel::HLERequestContext& ctx);
void GetRegisterInfo(Kernel::HLERequestContext& ctx);
@@ -255,6 +157,7 @@ private:
void GetNpadId(Kernel::HLERequestContext& ctx);
void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
KernelHelpers::ServiceContext service_context;

View File

@@ -38,20 +38,16 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) {
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
s64 delay = 0;
while (!stop_token.stop_requested()) {
vsync_signal.wait(false);
vsync_signal.store(false);
guard->lock();
const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count();
Compose();
const auto ticks = GetNextTicks();
const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count();
const s64 time_passed = time_end - time_start;
const s64 next_time = std::max<s64>(0, ticks - time_passed - delay);
guard->unlock();
if (next_time > 0) {
std::this_thread::sleep_for(std::chrono::nanoseconds{next_time});
}
delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time;
}
}
@@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr
guard = std::make_shared<std::mutex>();
// Schedule the screen composition events
composition_event = Core::Timing::CreateEvent(
multi_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
vsync_signal.store(true);
vsync_signal.notify_all();
return std::chrono::nanoseconds(GetNextTicks());
});
single_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock();
Compose();
return std::max(std::chrono::nanoseconds::zero(),
std::chrono::nanoseconds(GetNextTicks()) - ns_late);
return std::chrono::nanoseconds(GetNextTicks());
});
if (system.IsMulticore()) {
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
} else {
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event);
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
}
}
NVFlinger::~NVFlinger() {
if (!system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(composition_event, 0);
if (system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
vsync_thread.request_stop();
vsync_signal.store(true);
vsync_signal.notify_all();
} else {
system.CoreTiming().UnscheduleEvent(single_composition_event, {});
}
for (auto& display : displays) {

View File

@@ -126,12 +126,15 @@ private:
u32 swap_interval = 1;
/// Event that handles screen composition.
std::shared_ptr<Core::Timing::EventType> composition_event;
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
std::shared_ptr<Core::Timing::EventType> single_composition_event;
std::shared_ptr<std::mutex> guard;
Core::System& system;
std::atomic<bool> vsync_signal;
std::jthread vsync_thread;
KernelHelpers::ServiceContext service_context;

View File

@@ -929,7 +929,7 @@ BSD::BSD(Core::System& system_, const char* name)
proxy_packet_received = room_member->BindOnProxyPacketReceived(
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
} else {
LOG_ERROR(Service, "Network isn't initalized");
LOG_ERROR(Service, "Network isn't initialized");
}
}

View File

@@ -364,7 +364,7 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
std::vector<WSAPOLLFD> host_pollfds(pollfds.size());
std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) {
WSAPOLLFD result;
result.fd = fd.socket->fd;
result.fd = fd.socket->GetFD();
result.events = TranslatePollEvents(fd.events);
result.revents = 0;
return result;
@@ -430,12 +430,12 @@ std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
return {AcceptResult{}, GetAndLogLastError()};
}
AcceptResult result;
result.socket = std::make_unique<Socket>();
result.socket->fd = new_socket;
ASSERT(addrlen == sizeof(sockaddr_in));
result.sockaddr_in = TranslateToSockAddrIn(addr);
AcceptResult result{
.socket = std::make_unique<Socket>(new_socket),
.sockaddr_in = TranslateToSockAddrIn(addr),
};
return {std::move(result), Errno::SUCCESS};
}

View File

@@ -32,6 +32,10 @@ public:
std::unique_ptr<SocketBase> socket;
SockAddrIn sockaddr_in;
};
SocketBase() = default;
explicit SocketBase(SOCKET fd_) : fd{fd_} {}
virtual ~SocketBase() = default;
virtual SocketBase& operator=(const SocketBase&) = delete;
@@ -89,12 +93,19 @@ public:
virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
[[nodiscard]] SOCKET GetFD() const {
return fd;
}
protected:
SOCKET fd = INVALID_SOCKET;
};
class Socket : public SocketBase {
public:
Socket() = default;
explicit Socket(SOCKET fd_) : SocketBase{fd_} {}
~Socket() override;
Socket(const Socket&) = delete;

View File

@@ -244,6 +244,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
u64 program_id, std::size_t program_index) {
if (!file) {
return nullptr;
}
FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName());

View File

@@ -15,7 +15,7 @@ RoomNetwork::RoomNetwork() {
bool RoomNetwork::Init() {
if (enet_initialize() != 0) {
LOG_ERROR(Network, "Error initalizing ENet");
LOG_ERROR(Network, "Error initializing ENet");
return false;
}
m_room = std::make_shared<Room>();

View File

@@ -175,7 +175,7 @@ bool IsReference(IR::Inst& inst) {
}
void PrecolorInst(IR::Inst& phi) {
// Insert phi moves before references to avoid overwritting other phis
// Insert phi moves before references to avoid overwriting other phis
const size_t num_args{phi.NumArgs()};
for (size_t i = 0; i < num_args; ++i) {
IR::Block& phi_block{*phi.PhiBlock(i)};

View File

@@ -101,7 +101,7 @@ bool IsReference(IR::Inst& inst) {
}
void PrecolorInst(IR::Inst& phi) {
// Insert phi moves before references to avoid overwritting other phis
// Insert phi moves before references to avoid overwriting other phis
const size_t num_args{phi.NumArgs()};
for (size_t i = 0; i < num_args; ++i) {
IR::Block& phi_block{*phi.PhiBlock(i)};

View File

@@ -1065,7 +1065,7 @@ TexelWeightParams DecodeBlockInfo() {
void FillError(ivec3 coord) {
for (uint j = 0; j < block_dims.y; j++) {
for (uint i = 0; i < block_dims.x; i++) {
imageStore(dest_image, coord + ivec3(i, j, 0), vec4(1.0, 1.0, 0.0, 1.0));
imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
}
}
}

View File

@@ -3,6 +3,8 @@
#include <array>
#include <vector>
#include "common/scope_exit.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/macro/macro.h"
#include "video_core/macro/macro_hle.h"
@@ -58,6 +60,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.index_array.first = parameters[3];
maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
maxwell3d.regs.index_array.count = parameters[1];
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.regs.vb_element_base = element_base;
maxwell3d.regs.vb_base_instance = base_instance;
maxwell3d.mme_draw.instance_count = instance_count;
@@ -80,10 +83,67 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{
// Multidraw Indirect
void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
SCOPE_EXIT({
// Clean everything.
maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
maxwell3d.regs.index_array.count = 0;
maxwell3d.regs.vb_element_base = 0x0;
maxwell3d.regs.vb_base_instance = 0x0;
maxwell3d.mme_draw.instance_count = 0;
maxwell3d.CallMethodFromMME(0x8e3, 0x640);
maxwell3d.CallMethodFromMME(0x8e4, 0x0);
maxwell3d.CallMethodFromMME(0x8e5, 0x0);
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
});
const u32 start_indirect = parameters[0];
const u32 end_indirect = parameters[1];
if (start_indirect >= end_indirect) {
// Nothing to do.
return;
}
const auto topology =
static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
maxwell3d.regs.draw.topology.Assign(topology);
const u32 padding = parameters[3];
const std::size_t max_draws = parameters[4];
const u32 indirect_words = 5 + padding;
const std::size_t first_draw = start_indirect;
const std::size_t effective_draws = end_indirect - start_indirect;
const std::size_t last_draw = start_indirect + std::min(effective_draws, max_draws);
for (std::size_t index = first_draw; index < last_draw; index++) {
const std::size_t base = index * indirect_words + 5;
const u32 num_vertices = parameters[base];
const u32 instance_count = parameters[base + 1];
const u32 first_index = parameters[base + 2];
const u32 base_vertex = parameters[base + 3];
const u32 base_instance = parameters[base + 4];
maxwell3d.regs.index_array.first = first_index;
maxwell3d.regs.reg_array[0x446] = base_vertex;
maxwell3d.regs.index_array.count = num_vertices;
maxwell3d.regs.vb_element_base = base_vertex;
maxwell3d.regs.vb_base_instance = base_instance;
maxwell3d.mme_draw.instance_count = instance_count;
maxwell3d.CallMethodFromMME(0x8e3, 0x640);
maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
maxwell3d.CallMethodFromMME(0x8e5, base_instance);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
if (maxwell3d.ShouldExecute()) {
maxwell3d.Rasterizer().Draw(true, true);
}
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
}
constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
{0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
{0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
{0x0217920100488FF7, &HLE_0217920100488FF7},
{0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164},
}};
class HLEMacroImpl final : public CachedMacro {
@@ -99,6 +159,7 @@ private:
Engines::Maxwell3D& maxwell3d;
HLEFunction func;
};
} // Anonymous namespace
HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}

View File

@@ -13,7 +13,9 @@
#include <boost/container/static_vector.hpp>
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "video_core/textures/astc.h"
class InputBitStream {
@@ -1411,7 +1413,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
for (u32 j = 0; j < blockHeight; j++) {
for (u32 i = 0; i < blockWidth; i++) {
outBuf[j * blockWidth + i] = 0xFFFF00FF;
outBuf[j * blockWidth + i] = 0x00000000;
}
}
}
@@ -1650,29 +1652,41 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) {
u32 block_index = 0;
std::size_t depth_offset = 0;
for (u32 z = 0; z < depth; z++) {
for (u32 y = 0; y < height; y += block_height) {
for (u32 x = 0; x < width; x += block_width) {
const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
const u32 rows = Common::DivideUp(height, block_height);
const u32 cols = Common::DivideUp(width, block_width);
// Blocks can be at most 12x12
std::array<u32, 12 * 12> uncompData;
DecompressBlock(blockPtr, block_width, block_height, uncompData);
Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2,
"yuzu:ASTCDecompress"};
u32 decompWidth = std::min(block_width, width - x);
u32 decompHeight = std::min(block_height, height - y);
for (u32 z = 0; z < depth; ++z) {
const u32 depth_offset = z * height * width * 4;
for (u32 y_index = 0; y_index < rows; ++y_index) {
auto decompress_stride = [data, width, height, depth, block_width, block_height, output,
rows, cols, z, depth_offset, y_index] {
const u32 y = y_index * block_height;
for (u32 x_index = 0; x_index < cols; ++x_index) {
const u32 block_index = (z * rows * cols) + (y_index * cols) + x_index;
const u32 x = x_index * block_width;
const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
for (u32 jj = 0; jj < decompHeight; jj++) {
std::memcpy(outRow.data() + jj * width * 4,
uncompData.data() + jj * block_width, decompWidth * 4);
const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
// Blocks can be at most 12x12
std::array<u32, 12 * 12> uncompData;
DecompressBlock(blockPtr, block_width, block_height, uncompData);
u32 decompWidth = std::min(block_width, width - x);
u32 decompHeight = std::min(block_height, height - y);
const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
for (u32 h = 0; h < decompHeight; ++h) {
std::memcpy(outRow.data() + h * width * 4,
uncompData.data() + h * block_width, decompWidth * 4);
}
}
++block_index;
}
};
workers.QueueWork(std::move(decompress_stride));
}
depth_offset += height * width * 4;
workers.WaitForRequests();
}
}

View File

@@ -208,6 +208,16 @@ add_executable(yuzu
yuzu.rc
)
if (WIN32 AND YUZU_CRASH_DUMPS)
target_sources(yuzu PRIVATE
mini_dump.cpp
mini_dump.h
)
target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
endif()
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)

View File

@@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
input_profiles(std::make_unique<InputProfiles>()), system{system_} {
ui->setupUi(this);
player_widgets = {
@@ -291,7 +291,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>(
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
[this](const QGroupBox* player) { return player->isChecked(); }));
[](const QGroupBox* player) { return player->isChecked(); }));
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;

View File

@@ -15,8 +15,7 @@
namespace FS = Common::FS;
Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type)
: type(config_type), system{system_} {
Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
global = config_type == ConfigType::GlobalConfig;
Initialize(config_name);
@@ -546,6 +545,8 @@ void Config::ReadDebuggingValues() {
ReadBasicSetting(Settings::values.use_debug_asserts);
ReadBasicSetting(Settings::values.use_auto_stub);
ReadBasicSetting(Settings::values.enable_all_controllers);
ReadBasicSetting(Settings::values.create_crash_dumps);
ReadBasicSetting(Settings::values.perform_vulkan_check);
qt_config->endGroup();
}
@@ -1161,6 +1162,8 @@ void Config::SaveDebuggingValues() {
WriteBasicSetting(Settings::values.use_debug_asserts);
WriteBasicSetting(Settings::values.disable_macro_jit);
WriteBasicSetting(Settings::values.enable_all_controllers);
WriteBasicSetting(Settings::values.create_crash_dumps);
WriteBasicSetting(Settings::values.perform_vulkan_check);
qt_config->endGroup();
}
@@ -1547,7 +1550,6 @@ void Config::Reload() {
ReadValues();
// To apply default value changes
SaveValues();
system.ApplySettings();
}
void Config::Save() {

View File

@@ -25,7 +25,7 @@ public:
InputProfile,
};
explicit Config(Core::System& system_, const std::string& config_name = "qt-config",
explicit Config(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
@@ -194,8 +194,6 @@ private:
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
bool global;
Core::System& system;
};
// These metatype declarations cannot be in common/settings.h because core is devoid of QT

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDesktopServices>
#include <QMessageBox>
#include <QUrl>
#include "common/fs/path_util.h"
#include "common/logging/backend.h"
@@ -26,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
if (crash_dump_warning_shown) {
return;
}
QMessageBox::warning(this, tr("Restart Required"),
tr("yuzu is required to restart in order to apply this setting."),
QMessageBox::Ok, QMessageBox::Ok);
crash_dump_warning_shown = true;
});
}
ConfigureDebug::~ConfigureDebug() = default;
@@ -66,12 +77,20 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_loop_safety_checks->setChecked(
Settings::values.disable_shader_loop_safety_checks.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
#ifdef YUZU_USE_QT_WEB_ENGINE
ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
#else
ui->disable_web_applet->setEnabled(false);
ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled"));
ui->disable_web_applet->setText(tr("Web applet not compiled"));
#endif
#ifdef YUZU_DBGHELP
ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
#else
ui->create_crash_dumps->setEnabled(false);
ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
#endif
}
@@ -84,6 +103,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
@@ -98,6 +118,7 @@ void ConfigureDebug::ApplyConfiguration() {
ui->disable_loop_safety_checks->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
Settings::values.extended_logging = ui->extended_logging->isChecked();
Settings::values.perform_vulkan_check = ui->perform_vulkan_check->isChecked();
UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked();
Debugger::ToggleConsole();
Common::Log::Filter filter;

View File

@@ -32,4 +32,6 @@ private:
std::unique_ptr<Ui::ConfigureDebug> ui;
const Core::System& system;
bool crash_dump_warning_shown{false};
};

View File

@@ -7,60 +7,60 @@
</property>
<widget class="QWidget">
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Debugger</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Debugger</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QCheckBox" name="toggle_gdbstub">
<property name="text">
<string>Enable GDB Stub</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="gdbport_spinbox">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
</layout>
<widget class="QCheckBox" name="toggle_gdbstub">
<property name="text">
<string>Enable GDB Stub</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="gdbport_spinbox">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
@@ -231,6 +231,13 @@
<string>Debugging</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<widget class="QCheckBox" name="reporting_services">
<property name="text">
<string>Enable Verbose Reporting Services**</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="fs_access_log">
<property name="text">
@@ -238,20 +245,20 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="0" column="1">
<widget class="QCheckBox" name="dump_audio_commands">
<property name="text">
<string>Dump Audio Commands To Console**</string>
</property>
<property name="toolTip">
<string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
</property>
<property name="text">
<string>Dump Audio Commands To Console**</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="reporting_services">
<item row="2" column="1">
<widget class="QCheckBox" name="create_crash_dumps">
<property name="text">
<string>Enable Verbose Reporting Services**</string>
<string>Create Minidump After Crash</string>
</property>
</widget>
</item>
@@ -306,6 +313,16 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="perform_vulkan_check">
<property name="toolTip">
<string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
</property>
<property name="text">
<string>Perform Startup Vulkan Check</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -340,7 +357,6 @@
<tabstop>disable_loop_safety_checks</tabstop>
<tabstop>fs_access_log</tabstop>
<tabstop>reporting_services</tabstop>
<tabstop>dump_audio_commands</tabstop>
<tabstop>quest_flag</tabstop>
<tabstop>enable_cpu_debugging</tabstop>
<tabstop>use_debug_asserts</tabstop>

View File

@@ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system)
ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
profiles(std::make_unique<InputProfiles>()), system{system_} {
ui->setupUi(this);
}
@@ -163,10 +163,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
[this, input_subsystem, &hid_core] {
CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
});
connect(advanced, &ConfigureInputAdvanced::CallCameraDialog,
[this, input_subsystem, &hid_core] {
CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
});
connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
});
connect(ui->vibrationButton, &QPushButton::clicked,
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });

View File

@@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
game_config =
std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig);
game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
audio_tab = std::make_unique<ConfigureAudio>(system_, this);

View File

@@ -128,20 +128,25 @@ void ConfigureWeb::RefreshTelemetryID() {
void ConfigureWeb::OnLoginChanged() {
if (ui->edit_token->text().isEmpty()) {
user_verified = true;
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
// Empty = no icon
ui->label_token_verified->setPixmap(QPixmap());
ui->label_token_verified->setToolTip(QString());
} else {
user_verified = false;
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
// Show an info icon if it's been changed, clearer than showing failure
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setToolTip(
tr("Unverified, please click Verify before saving configuration", "Tooltip"));
}
}
void ConfigureWeb::VerifyLogin() {
ui->button_verify_login->setDisabled(true);
ui->button_verify_login->setText(tr("Verifying..."));
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verifying..."));
verify_watcher.setFuture(QtConcurrent::run(
[username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
@@ -155,13 +160,13 @@ void ConfigureWeb::OnLoginVerified() {
if (verify_watcher.result()) {
user_verified = true;
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
ui->username->setText(
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
} else {
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
ui->username->setText(tr("Unspecified"));
QMessageBox::critical(this, tr("Verification failed"),
tr("Verification failed. Check that you have entered your token "

View File

@@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
} // namespace
InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
InputProfiles::InputProfiles() {
const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
if (!FS::IsDir(input_profile_loc)) {
@@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
map_profiles.insert_or_assign(
name_without_ext, std::make_unique<Config>(system, name_without_ext,
Config::ConfigType::InputProfile));
name_without_ext,
std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
}
return true;
@@ -67,6 +67,8 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() {
profile_names.push_back(profile_name);
}
std::stable_sort(profile_names.begin(), profile_names.end());
return profile_names;
}
@@ -80,8 +82,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
}
map_profiles.insert_or_assign(
profile_name,
std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile));
profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
return SaveProfile(profile_name, player_index);
}

View File

@@ -15,7 +15,7 @@ class Config;
class InputProfiles {
public:
explicit InputProfiles(Core::System& system_);
explicit InputProfiles();
virtual ~InputProfiles();
std::vector<std::string> GetInputProfileNames();
@@ -31,6 +31,4 @@ private:
bool ProfileExistsInMap(const std::string& profile_name) const;
std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
Core::System& system;
};

View File

@@ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/uisettings.h"
#include "yuzu/util/clickable_label.h"
#ifdef YUZU_DBGHELP
#include "yuzu/mini_dump.h"
#endif
using namespace Common::Literals;
#ifdef USE_DISCORD_PRESENCE
@@ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() {
#endif // __linux__
}
GMainWindow::GMainWindow(bool has_broken_vulkan)
GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
config{std::make_unique<Config>(*system)},
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
#ifdef __linux__
@@ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig);
Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
system->ApplySettings();
}
// Save configurations
@@ -1996,7 +2000,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
const QString entry_type = [this, type] {
const QString entry_type = [type] {
switch (type) {
case InstalledEntryType::Game:
return tr("Contents");
@@ -2093,7 +2097,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path) {
const QString question = [this, target] {
const QString question = [target] {
switch (target) {
case GameListRemoveTarget::GlShaderCache:
return tr("Delete OpenGL Transferable Shader Cache?");
@@ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() {
Settings::values.disabled_addons.clear();
config = std::make_unique<Config>(*system);
config = std::make_unique<Config>();
UISettings::values.reset_to_defaults = false;
UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() {
UpdateStatusButtons();
controller_dialog->refreshConfiguration();
system->ApplySettings();
}
void GMainWindow::OnConfigureTas() {
@@ -3254,26 +3259,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
return;
}
QFile nfc_file{filename};
if (!nfc_file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Error opening Amiibo data file"),
tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
return;
}
const u64 nfc_file_size = nfc_file.size();
std::vector<u8> buffer(nfc_file_size);
const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
if (nfc_file_size != read_size) {
QMessageBox::warning(this, tr("Error reading Amiibo data file"),
tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
"was only able to read %2 bytes.")
.arg(nfc_file_size)
.arg(read_size));
return;
}
if (!nfc->LoadAmiibo(buffer)) {
if (!nfc->LoadAmiibo(filename.toStdString())) {
QMessageBox::warning(this, tr("Error loading Amiibo data"),
tr("Unable to load Amiibo data."));
}
@@ -4082,8 +4068,26 @@ void GMainWindow::changeEvent(QEvent* event) {
#endif
int main(int argc, char* argv[]) {
std::unique_ptr<Config> config = std::make_unique<Config>();
bool has_broken_vulkan = false;
if (StartupChecks(argv[0], &has_broken_vulkan)) {
bool is_child = false;
if (CheckEnvVars(&is_child)) {
return 0;
}
#ifdef YUZU_DBGHELP
PROCESS_INFORMATION pi;
if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
MiniDump::SpawnDebuggee(argv[0], pi)) {
// Delete the config object so that it doesn't save when the program exits
config.reset(nullptr);
MiniDump::DebugDebuggee(pi);
return 0;
}
#endif
if (StartupChecks(argv[0], &has_broken_vulkan,
Settings::values.perform_vulkan_check.GetValue())) {
return 0;
}
@@ -4135,7 +4139,7 @@ int main(int argc, char* argv[]) {
// generating shaders
setlocale(LC_ALL, "C");
GMainWindow main_window{has_broken_vulkan};
GMainWindow main_window{std::move(config), has_broken_vulkan};
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();

View File

@@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow {
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
explicit GMainWindow(bool has_broken_vulkan);
explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
~GMainWindow() override;
bool DropAction(QDropEvent* event);

202
src/yuzu/mini_dump.cpp Normal file
View File

@@ -0,0 +1,202 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdio>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <fmt/format.h>
#include <windows.h>
#include "yuzu/mini_dump.h"
#include "yuzu/startup_checks.h"
// dbghelp.h must be included after windows.h
#include <dbghelp.h>
namespace MiniDump {
void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
EXCEPTION_POINTERS* pep) {
char file_name[255];
const std::time_t the_time = std::time(nullptr);
std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
// Open the file
HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
return;
}
// Create the minidump
const MINIDUMP_TYPE dump_type = MiniDumpNormal;
const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
dump_type, (pep != 0) ? info : 0, 0, 0);
if (write_dump_status) {
fmt::print(stderr, "MiniDump created: {}", file_name);
} else {
fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
}
// Close the file
CloseHandle(file_handle);
}
void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
if (thread_handle == nullptr) {
fmt::print(stderr, "OpenThread failed ({})", GetLastError());
return;
}
// Get child process context
CONTEXT context = {};
context.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, &context)) {
fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
return;
}
// Create exception pointers for minidump
EXCEPTION_POINTERS ep;
ep.ExceptionRecord = &record;
ep.ContextRecord = &context;
MINIDUMP_EXCEPTION_INFORMATION info;
info.ThreadId = deb_ev.dwThreadId;
info.ExceptionPointers = &ep;
info.ClientPointers = false;
CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
if (CloseHandle(thread_handle) == 0) {
fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
}
}
bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
std::memset(&pi, 0, sizeof(pi));
// Don't debug if we are already being debugged
if (IsDebuggerPresent()) {
return false;
}
if (!SpawnChild(arg0, &pi, 0)) {
fmt::print(stderr, "warning: continuing without crash dumps");
return false;
}
const bool can_debug = DebugActiveProcess(pi.dwProcessId);
if (!can_debug) {
fmt::print(stderr,
"warning: DebugActiveProcess failed ({}), continuing without crash dumps",
GetLastError());
return false;
}
return true;
}
static const char* ExceptionName(DWORD exception) {
switch (exception) {
case EXCEPTION_ACCESS_VIOLATION:
return "EXCEPTION_ACCESS_VIOLATION";
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "EXCEPTION_DATATYPE_MISALIGNMENT";
case EXCEPTION_BREAKPOINT:
return "EXCEPTION_BREAKPOINT";
case EXCEPTION_SINGLE_STEP:
return "EXCEPTION_SINGLE_STEP";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "EXCEPTION_FLT_DENORMAL_OPERAND";
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
case EXCEPTION_FLT_INEXACT_RESULT:
return "EXCEPTION_FLT_INEXACT_RESULT";
case EXCEPTION_FLT_INVALID_OPERATION:
return "EXCEPTION_FLT_INVALID_OPERATION";
case EXCEPTION_FLT_OVERFLOW:
return "EXCEPTION_FLT_OVERFLOW";
case EXCEPTION_FLT_STACK_CHECK:
return "EXCEPTION_FLT_STACK_CHECK";
case EXCEPTION_FLT_UNDERFLOW:
return "EXCEPTION_FLT_UNDERFLOW";
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
case EXCEPTION_INT_OVERFLOW:
return "EXCEPTION_INT_OVERFLOW";
case EXCEPTION_PRIV_INSTRUCTION:
return "EXCEPTION_PRIV_INSTRUCTION";
case EXCEPTION_IN_PAGE_ERROR:
return "EXCEPTION_IN_PAGE_ERROR";
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "EXCEPTION_ILLEGAL_INSTRUCTION";
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
case EXCEPTION_STACK_OVERFLOW:
return "EXCEPTION_STACK_OVERFLOW";
case EXCEPTION_INVALID_DISPOSITION:
return "EXCEPTION_INVALID_DISPOSITION";
case EXCEPTION_GUARD_PAGE:
return "EXCEPTION_GUARD_PAGE";
case EXCEPTION_INVALID_HANDLE:
return "EXCEPTION_INVALID_HANDLE";
default:
return "unknown exception type";
}
}
void DebugDebuggee(PROCESS_INFORMATION& pi) {
DEBUG_EVENT deb_ev = {};
while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
if (!wait_success) {
fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
return;
}
switch (deb_ev.dwDebugEventCode) {
case OUTPUT_DEBUG_STRING_EVENT:
case CREATE_PROCESS_DEBUG_EVENT:
case CREATE_THREAD_DEBUG_EVENT:
case EXIT_PROCESS_DEBUG_EVENT:
case EXIT_THREAD_DEBUG_EVENT:
case LOAD_DLL_DEBUG_EVENT:
case RIP_EVENT:
case UNLOAD_DLL_DEBUG_EVENT:
// Continue on all other debug events
ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
break;
case EXCEPTION_DEBUG_EVENT:
EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
// We want to generate a crash dump if we are seeing the same exception again.
if (!deb_ev.u.Exception.dwFirstChance) {
fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
record.ExceptionCode, ExceptionName(record.ExceptionCode));
DumpFromDebugEvent(deb_ev, pi);
}
// Continue without handling the exception.
// Lets the debuggee use its own exception handler.
// - If one does not exist, we will see the exception once more where we make a minidump
// for. Then when it reaches here again, yuzu will probably crash.
// - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
// infinite loop of exceptions.
ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
break;
}
}
}
} // namespace MiniDump

19
src/yuzu/mini_dump.h Normal file
View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <windows.h>
#include <dbghelp.h>
namespace MiniDump {
void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
EXCEPTION_POINTERS* pep);
void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
void DebugDebuggee(PROCESS_INFORMATION& pi);
} // namespace MiniDump

View File

@@ -31,48 +31,68 @@ void CheckVulkan() {
}
}
bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
bool CheckEnvVars(bool* is_child) {
#ifdef _WIN32
// Check environment variable to see if we are the child
char variable_contents[8];
const DWORD startup_check_var =
GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) {
if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) {
CheckVulkan();
return true;
}
// Don't perform startup checks if we are a child process
char is_child_s[8];
const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8);
if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) {
*is_child = true;
return false;
} else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) {
std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
IS_CHILD_ENV_VAR, GetLastError());
return true;
}
#endif
return false;
}
bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) {
#ifdef _WIN32
// Set the startup variable for child processes
const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON");
const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
if (!env_var_set) {
std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
STARTUP_CHECK_ENV_VAR, GetLastError());
return false;
}
PROCESS_INFORMATION process_info;
std::memset(&process_info, '\0', sizeof(process_info));
if (perform_vulkan_check) {
// Spawn child process that performs Vulkan check
PROCESS_INFORMATION process_info;
std::memset(&process_info, '\0', sizeof(process_info));
if (!SpawnChild(arg0, &process_info)) {
return false;
}
if (!SpawnChild(arg0, &process_info, 0)) {
return false;
}
// Wait until the processs exits and get exit code from it
WaitForSingleObject(process_info.hProcess, INFINITE);
DWORD exit_code = STILL_ACTIVE;
const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
if (err == 0) {
std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
}
// Wait until the processs exits and get exit code from it
WaitForSingleObject(process_info.hProcess, INFINITE);
DWORD exit_code = STILL_ACTIVE;
const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
if (err == 0) {
std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
}
// Vulkan is broken if the child crashed (return value is not zero)
*has_broken_vulkan = (exit_code != 0);
// Vulkan is broken if the child crashed (return value is not zero)
*has_broken_vulkan = (exit_code != 0);
if (CloseHandle(process_info.hProcess) == 0) {
std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
}
if (CloseHandle(process_info.hThread) == 0) {
std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
if (CloseHandle(process_info.hProcess) == 0) {
std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
}
if (CloseHandle(process_info.hThread) == 0) {
std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
}
}
if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) {
@@ -81,32 +101,34 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
}
#elif defined(YUZU_UNIX)
const pid_t pid = fork();
if (pid == 0) {
CheckVulkan();
return true;
} else if (pid == -1) {
const int err = errno;
std::fprintf(stderr, "fork failed with error %d\n", err);
return false;
}
if (perform_vulkan_check) {
const pid_t pid = fork();
if (pid == 0) {
CheckVulkan();
return true;
} else if (pid == -1) {
const int err = errno;
std::fprintf(stderr, "fork failed with error %d\n", err);
return false;
}
// Get exit code from child process
int status;
const int r_val = wait(&status);
if (r_val == -1) {
const int err = errno;
std::fprintf(stderr, "wait failed with error %d\n", err);
return false;
// Get exit code from child process
int status;
const int r_val = wait(&status);
if (r_val == -1) {
const int err = errno;
std::fprintf(stderr, "wait failed with error %d\n", err);
return false;
}
// Vulkan is broken if the child crashed (return value is not zero)
*has_broken_vulkan = (status != 0);
}
// Vulkan is broken if the child crashed (return value is not zero)
*has_broken_vulkan = (status != 0);
#endif
return false;
}
#ifdef _WIN32
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) {
STARTUPINFOA startup_info;
std::memset(&startup_info, '\0', sizeof(startup_info));
@@ -120,7 +142,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
0, // dwCreationFlags
flags, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // lpCurrentDirectory
&startup_info, // lpStartupInfo

View File

@@ -7,11 +7,14 @@
#include <windows.h>
#endif
constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD";
constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
constexpr char ENV_VAR_ENABLED_TEXT[] = "ON";
void CheckVulkan();
bool StartupChecks(const char* arg0, bool* has_broken_vulkan);
bool CheckEnvVars(bool* is_child);
bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check);
#ifdef _WIN32
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi);
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags);
#endif

View File

@@ -31,6 +31,10 @@
"yuzu-tests": {
"description": "Compile tests",
"dependencies": [ "catch2" ]
},
"dbghelp": {
"description": "Compile Windows crash dump (Minidump) support",
"dependencies": [ "dbghelp" ]
}
},
"overrides": [