Compare commits
84 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad9f97cd8d | ||
|
|
1b1b99fbd5 | ||
|
|
d794ced303 | ||
|
|
5082efef6c | ||
|
|
86ccc87111 | ||
|
|
12baf88dc8 | ||
|
|
f711d1ce52 | ||
|
|
70abb13a43 | ||
|
|
c2c3f2c3e7 | ||
|
|
3b77dec188 | ||
|
|
4a493cb10f | ||
|
|
c8da75b7ed | ||
|
|
56b8a9ba6e | ||
|
|
c891497b61 | ||
|
|
00616c7ad8 | ||
|
|
75d6fe3669 | ||
|
|
e48e22eb52 | ||
|
|
da0fd51642 | ||
|
|
08d81e0bd8 | ||
|
|
b59b967280 | ||
|
|
37b4f79b54 | ||
|
|
8d4458ef24 | ||
|
|
89af4bfba8 | ||
|
|
3a5f9409c8 | ||
|
|
7172339c7a | ||
|
|
9c32f29af1 | ||
|
|
4a7a771340 | ||
|
|
eb726677b2 | ||
|
|
92813f01a4 | ||
|
|
49870baea4 | ||
|
|
344006b856 | ||
|
|
8dafe15600 | ||
|
|
44ccec7846 | ||
|
|
882dfa36ae | ||
|
|
672e61d802 | ||
|
|
60aa942210 | ||
|
|
809126c94a | ||
|
|
7e3cdfc453 | ||
|
|
d5d6322640 | ||
|
|
6b1cb73350 | ||
|
|
e4bc7b8611 | ||
|
|
b2c2138af7 | ||
|
|
b862d5d8d8 | ||
|
|
36c77761cf | ||
|
|
7a5d235d94 | ||
|
|
d1f3c121a0 | ||
|
|
e9109cb5f2 | ||
|
|
cb2a33babc | ||
|
|
a278fa6e2a | ||
|
|
9554c67809 | ||
|
|
e85bda5f31 | ||
|
|
b5a06bc419 | ||
|
|
b06ef5d530 | ||
|
|
0a63d43ad6 | ||
|
|
2c91fbf7f1 | ||
|
|
463cc9559f | ||
|
|
d55046c5e9 | ||
|
|
1c7dae966d | ||
|
|
7fda6de5cb | ||
|
|
e93e898df5 | ||
|
|
56be24d50a | ||
|
|
949917babc | ||
|
|
9c6cd93195 | ||
|
|
063b23cc58 | ||
|
|
4834961736 | ||
|
|
caa138b33f | ||
|
|
19a4e12e6e | ||
|
|
848f69eb19 | ||
|
|
9dc9e501de | ||
|
|
12f7d42d32 | ||
|
|
45b343d1d0 | ||
|
|
c976613ab2 | ||
|
|
e339ec0e00 | ||
|
|
3dbaafe1f3 | ||
|
|
f958cbc737 | ||
|
|
606cdb17d3 | ||
|
|
658e1ee426 | ||
|
|
83a24ad638 | ||
|
|
9d3b190465 | ||
|
|
260430c849 | ||
|
|
6b58db9ccd | ||
|
|
d77fe3b1c2 | ||
|
|
69bd6cd490 | ||
|
|
dc915aff62 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
2
.github/workflows/verify.yml
vendored
@@ -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
@@ -2,7 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Build directory
|
||||
[Bb]uild/
|
||||
[Bb]uild*/
|
||||
doc-build/
|
||||
|
||||
# Generated source files
|
||||
|
||||
11
.reuse/dep5
@@ -6,6 +6,7 @@ Files: dist/english_plurals/*
|
||||
dist/icons/controller/*.png
|
||||
dist/icons/overlay/*.png
|
||||
dist/languages/*
|
||||
dist/qt_themes/*/icons/48x48/sd_card.png
|
||||
dist/qt_themes/*/icons/index.theme
|
||||
dist/qt_themes/default/style.qss
|
||||
Copyright: yuzu Emulator Project
|
||||
@@ -51,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
|
||||
@@ -66,11 +69,9 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png
|
||||
Copyright: Ionic (http://ionic.io/)
|
||||
License: MIT
|
||||
|
||||
|
||||
Files: dist/qt_themes/*/icons/48x48/sd_card.png
|
||||
dist/qt_themes/colorful/icons/48x48/star.png
|
||||
dist/qt_themes/default/icons/16x16/checked.png
|
||||
dist/qt_themes/default/icons/16x16/failed.png
|
||||
Files: dist/qt_themes/colorful/icons/48x48/star.png
|
||||
dist/qt_themes/colorful/icons/16x16/checked.png
|
||||
dist/qt_themes/colorful/icons/16x16/failed.png
|
||||
Copyright: SVG Repo
|
||||
License: CC0-1.0
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
Before Width: | Height: | Size: 414 B After Width: | Height: | Size: 414 B |
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
BIN
dist/qt_themes/colorful/icons/16x16/info.png
vendored
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
dist/qt_themes/colorful/icons/16x16/sync.png
vendored
Normal file
|
After Width: | Height: | Size: 548 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 678 B |
BIN
dist/qt_themes/colorful/icons/48x48/sd_card.png
vendored
|
Before Width: | Height: | Size: 981 B After Width: | Height: | Size: 228 B |
1
dist/qt_themes/colorful/icons/index.theme
vendored
@@ -1,7 +1,6 @@
|
||||
[Icon Theme]
|
||||
Name=colorful
|
||||
Comment=Colorful theme
|
||||
Inherits=default
|
||||
Directories=16x16,48x48,256x256
|
||||
|
||||
[16x16]
|
||||
|
||||
6
dist/qt_themes/colorful/style.qrc
vendored
@@ -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>
|
||||
|
||||
10
dist/qt_themes/colorful_dark/style.qrc
vendored
@@ -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">
|
||||
|
||||
11
dist/qt_themes/default/default.qrc
vendored
@@ -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>
|
||||
|
||||
BIN
dist/qt_themes/default/icons/48x48/sd_card.png
vendored
|
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 198 B |
3
dist/qt_themes/default/icons/index.theme
vendored
@@ -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
|
||||
|
||||
BIN
dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
vendored
|
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 214 B |
4
dist/qt_themes/qdarkstyle/icons/index.theme
vendored
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Icon Theme]
|
||||
Name=qdarkstyle_midnight_blue
|
||||
Comment=dark theme
|
||||
Inherits=default
|
||||
Inherits=colorful
|
||||
Directories=16x16,48x48,256x256
|
||||
|
||||
[16x16]
|
||||
|
||||
@@ -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);
|
||||
@@ -47,16 +47,6 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
|
||||
return *adsp;
|
||||
}
|
||||
|
||||
void AudioCore::PauseSinks(const bool pausing) const {
|
||||
if (pausing) {
|
||||
output_sink->PauseStreams();
|
||||
input_sink->PauseStreams();
|
||||
} else {
|
||||
output_sink->UnpauseStreams();
|
||||
input_sink->UnpauseStreams();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCore::SetNVDECActive(bool active) {
|
||||
nvdec_active = active;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace AudioCore {
|
||||
|
||||
class AudioManager;
|
||||
/**
|
||||
* Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP.
|
||||
* Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
|
||||
*/
|
||||
class AudioCore {
|
||||
public:
|
||||
@@ -57,14 +57,6 @@ public:
|
||||
*/
|
||||
AudioRenderer::ADSP::ADSP& GetADSP();
|
||||
|
||||
/**
|
||||
* Pause the sink. Called from the core.
|
||||
*
|
||||
* @param pausing - Is this pause due to an actual pause, or shutdown?
|
||||
* Unfortunately, shutdown also pauses streams, which can cause issues.
|
||||
*/
|
||||
void PauseSinks(bool pausing) const;
|
||||
|
||||
/**
|
||||
* Toggle NVDEC state, used to avoid stall in playback.
|
||||
*
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace AudioCore {
|
||||
* Responsible for the input/output events, set by the stream backend when buffers are consumed, and
|
||||
* waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
|
||||
* recycling going.
|
||||
* In a real Switch this is not a seprate class, and exists entirely within the audio manager.
|
||||
* In a real Switch this is not a separate class, and exists entirely within the audio manager.
|
||||
* On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
|
||||
* wait on multiple events at once, and the events are not needed by the backend.
|
||||
*/
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
void ClearEvents();
|
||||
|
||||
private:
|
||||
/// Lock, used bythe audio manager
|
||||
/// Lock, used by the audio manager
|
||||
std::mutex event_lock;
|
||||
/// Array of events, one per system type (see Type), last event is used to terminate
|
||||
std::array<std::atomic<bool>, 4> events_signalled;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -59,9 +59,10 @@ public:
|
||||
/**
|
||||
* Get a list of audio in device names.
|
||||
*
|
||||
* @oaram names - Output container to write names to.
|
||||
* @param max_count - Maximum numebr of deivce names to write. Unused
|
||||
* @param names - Output container to write names to.
|
||||
* @param max_count - Maximum number of device names to write. Unused
|
||||
* @param filter - Should the list be filtered? Unused.
|
||||
*
|
||||
* @return Number of names written.
|
||||
*/
|
||||
u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -76,14 +65,10 @@ public:
|
||||
|
||||
private:
|
||||
/**
|
||||
* Main thread, waiting on a manager signal and calling the registered fucntion.
|
||||
* Main thread, waiting on a manager signal and calling the registered function.
|
||||
*/
|
||||
void ThreadFunc();
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Have sessions started palying?
|
||||
bool sessions_started{};
|
||||
/// Is the main thread running?
|
||||
std::atomic<bool> running{};
|
||||
/// Unused
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() {
|
||||
return *system_manager;
|
||||
}
|
||||
|
||||
auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count)
|
||||
-> Result {
|
||||
Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
|
||||
u64& out_count) const {
|
||||
if (!CheckValidRevision(params.revision)) {
|
||||
return Service::Audio::ERR_INVALID_REVISION;
|
||||
}
|
||||
@@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) {
|
||||
session_ids[--session_count] = session_id;
|
||||
}
|
||||
|
||||
u32 Manager::GetSessionCount() {
|
||||
u32 Manager::GetSessionCount() const {
|
||||
std::scoped_lock l{session_lock};
|
||||
return session_count;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
* @param out_count - Output size of the required workbuffer.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count);
|
||||
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
|
||||
|
||||
/**
|
||||
* Get a new session id.
|
||||
@@ -60,14 +60,14 @@ public:
|
||||
*
|
||||
* @return The number of active sessions.
|
||||
*/
|
||||
u32 GetSessionCount();
|
||||
u32 GetSessionCount() const;
|
||||
|
||||
/**
|
||||
* Add a renderer system to the manager.
|
||||
* The system will be reguarly called to generate commands for the AudioRenderer.
|
||||
* The system will be regularly called to generate commands for the AudioRenderer.
|
||||
*
|
||||
* @param system - The system to add.
|
||||
* @return True if the system was sucessfully added, otherwise false.
|
||||
* @return True if the system was successfully added, otherwise false.
|
||||
*/
|
||||
bool AddSystem(System& system);
|
||||
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
* Remove a renderer system from the manager.
|
||||
*
|
||||
* @param system - The system to remove.
|
||||
* @return True if the system was sucessfully removed, otherwise false.
|
||||
* @return True if the system was successfully removed, otherwise false.
|
||||
*/
|
||||
bool RemoveSystem(System& system);
|
||||
|
||||
@@ -94,7 +94,7 @@ private:
|
||||
/// Number of active renderers
|
||||
u32 session_count{};
|
||||
/// Lock for interacting with the sessions
|
||||
std::mutex session_lock{};
|
||||
mutable std::mutex session_lock{};
|
||||
/// Regularly generates commands from the registered systems for the AudioRenderer
|
||||
std::unique_ptr<SystemManager> system_manager{};
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
*
|
||||
* @param buffer - The new buffer.
|
||||
*/
|
||||
void AppendBuffer(AudioBuffer& buffer) {
|
||||
void AppendBuffer(const AudioBuffer& buffer) {
|
||||
std::scoped_lock l{lock};
|
||||
buffers[appended_index] = buffer;
|
||||
appended_count++;
|
||||
@@ -88,10 +88,12 @@ public:
|
||||
/**
|
||||
* Release all registered buffers.
|
||||
*
|
||||
* @param timestamp - The released timestamp for this buffer.
|
||||
* @param core_timing - The CoreTiming instance
|
||||
* @param session - The device session
|
||||
*
|
||||
* @return Is the buffer was released.
|
||||
*/
|
||||
bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
|
||||
bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
|
||||
std::scoped_lock l{lock};
|
||||
bool buffer_released{false};
|
||||
while (registered_count > 0) {
|
||||
|
||||
@@ -73,12 +73,12 @@ void DeviceSession::Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
|
||||
for (size_t i = 0; i < buffers.size(); i++) {
|
||||
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
|
||||
for (const auto& buffer : buffers) {
|
||||
Sink::SinkBuffer new_buffer{
|
||||
.frames = buffers[i].size / (channel_count * sizeof(s16)),
|
||||
.frames = buffer.size / (channel_count * sizeof(s16)),
|
||||
.frames_played = 0,
|
||||
.tag = buffers[i].tag,
|
||||
.tag = buffer.tag,
|
||||
.consumed = false,
|
||||
};
|
||||
|
||||
@@ -86,21 +86,21 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
|
||||
std::vector<s16> samples{};
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
} else {
|
||||
std::vector<s16> samples(buffers[i].size / sizeof(s16));
|
||||
system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
|
||||
std::vector<s16> samples(buffer.size / sizeof(s16));
|
||||
system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
|
||||
void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
|
||||
if (type == Sink::StreamType::In) {
|
||||
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
|
||||
system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const {
|
||||
bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
|
||||
return played_sample_count >= buffer.end_timestamp;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,22 +62,23 @@ public:
|
||||
*
|
||||
* @param buffers - The buffers to play.
|
||||
*/
|
||||
void AppendBuffers(std::span<AudioBuffer> buffers) const;
|
||||
void AppendBuffers(std::span<const AudioBuffer> buffers) const;
|
||||
|
||||
/**
|
||||
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
|
||||
*
|
||||
* @param buffer - The buffer to write to.
|
||||
*/
|
||||
void ReleaseBuffer(AudioBuffer& buffer) const;
|
||||
void ReleaseBuffer(const AudioBuffer& buffer) const;
|
||||
|
||||
/**
|
||||
* Check if the buffer for the given tag has been consumed by the backend.
|
||||
*
|
||||
* @param tag - Unqiue tag of the buffer to check.
|
||||
* @param buffer - the buffer to check.
|
||||
*
|
||||
* @return true if the buffer has been consumed, otherwise false.
|
||||
*/
|
||||
bool IsBufferConsumed(AudioBuffer& buffer) const;
|
||||
bool IsBufferConsumed(const AudioBuffer& buffer) const;
|
||||
|
||||
/**
|
||||
* Start this device session, starting the backend stream.
|
||||
|
||||
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
|
||||
return event->GetReadableEvent();
|
||||
}
|
||||
|
||||
f32 In::GetVolume() {
|
||||
f32 In::GetVolume() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetVolume();
|
||||
}
|
||||
@@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) {
|
||||
system.SetVolume(volume);
|
||||
}
|
||||
|
||||
bool In::ContainsAudioBuffer(u64 tag) {
|
||||
bool In::ContainsAudioBuffer(u64 tag) const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.ContainsAudioBuffer(tag);
|
||||
}
|
||||
|
||||
u32 In::GetBufferCount() {
|
||||
u32 In::GetBufferCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetBufferCount();
|
||||
}
|
||||
|
||||
u64 In::GetPlayedSampleCount() {
|
||||
u64 In::GetPlayedSampleCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetPlayedSampleCount();
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
*
|
||||
* @return The current volume.
|
||||
*/
|
||||
f32 GetVolume();
|
||||
f32 GetVolume() const;
|
||||
|
||||
/**
|
||||
* Set the system volume.
|
||||
@@ -117,21 +117,21 @@ public:
|
||||
* @param tag - The tag to search for.
|
||||
* @return True if the buffer is in the system, otherwise false.
|
||||
*/
|
||||
bool ContainsAudioBuffer(u64 tag);
|
||||
bool ContainsAudioBuffer(u64 tag) const;
|
||||
|
||||
/**
|
||||
* Get the maximum number of buffers.
|
||||
*
|
||||
* @return The maximum number of buffers.
|
||||
*/
|
||||
u32 GetBufferCount();
|
||||
u32 GetBufferCount() const;
|
||||
|
||||
/**
|
||||
* Get the total played sample count for this audio in.
|
||||
*
|
||||
* @return The played sample count.
|
||||
*/
|
||||
u64 GetPlayedSampleCount();
|
||||
u64 GetPlayedSampleCount() const;
|
||||
|
||||
private:
|
||||
/// The AudioIn::Manager this audio in is registered with
|
||||
|
||||
@@ -34,16 +34,16 @@ size_t System::GetSessionId() const {
|
||||
return session_id;
|
||||
}
|
||||
|
||||
std::string_view System::GetDefaultDeviceName() {
|
||||
std::string_view System::GetDefaultDeviceName() const {
|
||||
return "BuiltInHeadset";
|
||||
}
|
||||
|
||||
std::string_view System::GetDefaultUacDeviceName() {
|
||||
std::string_view System::GetDefaultUacDeviceName() const {
|
||||
return "Uac";
|
||||
}
|
||||
|
||||
Result System::IsConfigValid(const std::string_view device_name,
|
||||
const AudioInParameter& in_params) {
|
||||
const AudioInParameter& in_params) const {
|
||||
if ((device_name.size() > 0) &&
|
||||
(device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
|
||||
return Service::Audio::ERR_INVALID_DEVICE_NAME;
|
||||
@@ -114,12 +114,14 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
|
||||
}
|
||||
|
||||
const auto timestamp{buffers.GetNextTimestamp()};
|
||||
AudioBuffer new_buffer{.start_timestamp = timestamp,
|
||||
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
|
||||
.played_timestamp = 0,
|
||||
.samples = buffer.samples,
|
||||
.tag = tag,
|
||||
.size = buffer.size};
|
||||
const AudioBuffer new_buffer{
|
||||
.start_timestamp = timestamp,
|
||||
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
|
||||
.played_timestamp = 0,
|
||||
.samples = buffer.samples,
|
||||
.tag = tag,
|
||||
.size = buffer.size,
|
||||
};
|
||||
|
||||
buffers.AppendBuffer(new_buffer);
|
||||
RegisterBuffers();
|
||||
@@ -200,11 +202,11 @@ void System::SetVolume(const f32 volume_) {
|
||||
session->SetVolume(volume_);
|
||||
}
|
||||
|
||||
bool System::ContainsAudioBuffer(const u64 tag) {
|
||||
bool System::ContainsAudioBuffer(const u64 tag) const {
|
||||
return buffers.ContainsBuffer(tag);
|
||||
}
|
||||
|
||||
u32 System::GetBufferCount() {
|
||||
u32 System::GetBufferCount() const {
|
||||
return buffers.GetAppendedRegisteredCount();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
*
|
||||
* @return The default audio input device name.
|
||||
*/
|
||||
std::string_view GetDefaultDeviceName();
|
||||
std::string_view GetDefaultDeviceName() const;
|
||||
|
||||
/**
|
||||
* Get the default USB audio input device name.
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
*
|
||||
* @return The default USB audio input device name.
|
||||
*/
|
||||
std::string_view GetDefaultUacDeviceName();
|
||||
std::string_view GetDefaultUacDeviceName() const;
|
||||
|
||||
/**
|
||||
* Is the given initialize config valid?
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
* @param in_params - Input parameters, see AudioInParameter.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params);
|
||||
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
|
||||
|
||||
/**
|
||||
* Initialize this system.
|
||||
@@ -208,7 +208,7 @@ public:
|
||||
/**
|
||||
* Set this system's current volume.
|
||||
*
|
||||
* @param The new volume.
|
||||
* @param volume The new volume.
|
||||
*/
|
||||
void SetVolume(f32 volume);
|
||||
|
||||
@@ -218,14 +218,14 @@ public:
|
||||
* @param tag - Unique tag to search for.
|
||||
* @return True if the buffer is in the system, otherwise false.
|
||||
*/
|
||||
bool ContainsAudioBuffer(u64 tag);
|
||||
bool ContainsAudioBuffer(u64 tag) const;
|
||||
|
||||
/**
|
||||
* Get the maximum number of usable buffers (default 32).
|
||||
*
|
||||
* @return The number of buffers.
|
||||
*/
|
||||
u32 GetBufferCount();
|
||||
u32 GetBufferCount() const;
|
||||
|
||||
/**
|
||||
* Get the total number of samples played by this system.
|
||||
|
||||
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
|
||||
return event->GetReadableEvent();
|
||||
}
|
||||
|
||||
f32 Out::GetVolume() {
|
||||
f32 Out::GetVolume() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetVolume();
|
||||
}
|
||||
@@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) {
|
||||
system.SetVolume(volume);
|
||||
}
|
||||
|
||||
bool Out::ContainsAudioBuffer(const u64 tag) {
|
||||
bool Out::ContainsAudioBuffer(const u64 tag) const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.ContainsAudioBuffer(tag);
|
||||
}
|
||||
|
||||
u32 Out::GetBufferCount() {
|
||||
u32 Out::GetBufferCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetBufferCount();
|
||||
}
|
||||
|
||||
u64 Out::GetPlayedSampleCount() {
|
||||
u64 Out::GetPlayedSampleCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetPlayedSampleCount();
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
*
|
||||
* @return The current volume.
|
||||
*/
|
||||
f32 GetVolume();
|
||||
f32 GetVolume() const;
|
||||
|
||||
/**
|
||||
* Set the system volume.
|
||||
@@ -117,21 +117,21 @@ public:
|
||||
* @param tag - The tag to search for.
|
||||
* @return True if the buffer is in the system, otherwise false.
|
||||
*/
|
||||
bool ContainsAudioBuffer(u64 tag);
|
||||
bool ContainsAudioBuffer(u64 tag) const;
|
||||
|
||||
/**
|
||||
* Get the maximum number of buffers.
|
||||
*
|
||||
* @return The maximum number of buffers.
|
||||
*/
|
||||
u32 GetBufferCount();
|
||||
u32 GetBufferCount() const;
|
||||
|
||||
/**
|
||||
* Get the total played sample count for this audio out.
|
||||
*
|
||||
* @return The played sample count.
|
||||
*/
|
||||
u64 GetPlayedSampleCount();
|
||||
u64 GetPlayedSampleCount() const;
|
||||
|
||||
private:
|
||||
/// The AudioOut::Manager this audio out is registered with
|
||||
|
||||
@@ -27,11 +27,12 @@ void System::Finalize() {
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
}
|
||||
|
||||
std::string_view System::GetDefaultOutputDeviceName() {
|
||||
std::string_view System::GetDefaultOutputDeviceName() const {
|
||||
return "DeviceOut";
|
||||
}
|
||||
|
||||
Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) {
|
||||
Result System::IsConfigValid(std::string_view device_name,
|
||||
const AudioOutParameter& in_params) const {
|
||||
if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
|
||||
return Service::Audio::ERR_INVALID_DEVICE_NAME;
|
||||
}
|
||||
@@ -113,12 +114,14 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
|
||||
}
|
||||
|
||||
const auto timestamp{buffers.GetNextTimestamp()};
|
||||
AudioBuffer new_buffer{.start_timestamp = timestamp,
|
||||
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
|
||||
.played_timestamp = 0,
|
||||
.samples = buffer.samples,
|
||||
.tag = tag,
|
||||
.size = buffer.size};
|
||||
const AudioBuffer new_buffer{
|
||||
.start_timestamp = timestamp,
|
||||
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
|
||||
.played_timestamp = 0,
|
||||
.samples = buffer.samples,
|
||||
.tag = tag,
|
||||
.size = buffer.size,
|
||||
};
|
||||
|
||||
buffers.AppendBuffer(new_buffer);
|
||||
RegisterBuffers();
|
||||
@@ -198,11 +201,11 @@ void System::SetVolume(const f32 volume_) {
|
||||
session->SetVolume(volume_);
|
||||
}
|
||||
|
||||
bool System::ContainsAudioBuffer(const u64 tag) {
|
||||
bool System::ContainsAudioBuffer(const u64 tag) const {
|
||||
return buffers.ContainsBuffer(tag);
|
||||
}
|
||||
|
||||
u32 System::GetBufferCount() {
|
||||
u32 System::GetBufferCount() const {
|
||||
return buffers.GetAppendedRegisteredCount();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
*
|
||||
* @return The default audio output device name.
|
||||
*/
|
||||
std::string_view GetDefaultOutputDeviceName();
|
||||
std::string_view GetDefaultOutputDeviceName() const;
|
||||
|
||||
/**
|
||||
* Is the given initialize config valid?
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
* @param in_params - Input parameters, see AudioOutParameter.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params);
|
||||
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
|
||||
|
||||
/**
|
||||
* Initialize this system.
|
||||
@@ -199,7 +199,7 @@ public:
|
||||
/**
|
||||
* Set this system's current volume.
|
||||
*
|
||||
* @param The new volume.
|
||||
* @param volume The new volume.
|
||||
*/
|
||||
void SetVolume(f32 volume);
|
||||
|
||||
@@ -209,14 +209,14 @@ public:
|
||||
* @param tag - Unique tag to search for.
|
||||
* @return True if the buffer is in the system, otherwise false.
|
||||
*/
|
||||
bool ContainsAudioBuffer(u64 tag);
|
||||
bool ContainsAudioBuffer(u64 tag) const;
|
||||
|
||||
/**
|
||||
* Get the maximum number of usable buffers (default 32).
|
||||
*
|
||||
* @return The number of buffers.
|
||||
*/
|
||||
u32 GetBufferCount();
|
||||
u32 GetBufferCount() const;
|
||||
|
||||
/**
|
||||
* Get the total number of samples played by this system.
|
||||
|
||||
@@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
|
||||
return render_mailbox.GetRemainCommandCount(session_id);
|
||||
}
|
||||
|
||||
void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
|
||||
void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
|
||||
render_mailbox.SetCommandBuffer(session_id, command_buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,6 @@ public:
|
||||
|
||||
/**
|
||||
* Stop the ADSP.
|
||||
*
|
||||
* @return True if started or already running, otherwise false.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
@@ -133,7 +131,7 @@ public:
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @param command_buffer - The command buffer to process.
|
||||
*/
|
||||
void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer);
|
||||
void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
|
||||
|
||||
/**
|
||||
* Clear the command buffers (does not clear the time taken or the remaining command count)
|
||||
|
||||
@@ -47,11 +47,11 @@ 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];
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
|
||||
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
|
||||
command_buffers[session_id] = buffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
/**
|
||||
* Send a message from the host to the AudioRenderer.
|
||||
*
|
||||
* @param message_ - The message to send to the AudioRenderer.
|
||||
* @param message - The message to send to the AudioRenderer.
|
||||
*/
|
||||
void HostSendMessage(RenderMessage message);
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
/**
|
||||
* Send a message from the AudioRenderer to the host.
|
||||
*
|
||||
* @param message_ - The message to send to the host.
|
||||
* @param message - The message to send to the host.
|
||||
*/
|
||||
void ADSPSendMessage(RenderMessage message);
|
||||
|
||||
@@ -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).
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
* @param session_id - The session id to get (0 or 1).
|
||||
* @param buffer - The command buffer to set.
|
||||
*/
|
||||
void SetCommandBuffer(u32 session_id, CommandBuffer& buffer);
|
||||
void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
|
||||
|
||||
/**
|
||||
* Get the total render time taken for the last command lists sent.
|
||||
@@ -163,7 +163,7 @@ public:
|
||||
/**
|
||||
* Start the AudioRenderer.
|
||||
*
|
||||
* @param The mailbox to use for this session.
|
||||
* @param mailbox The mailbox to use for this session.
|
||||
*/
|
||||
void Start(AudioRenderer_Mailbox* mailbox);
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ public:
|
||||
/**
|
||||
* Initialize the processor.
|
||||
*
|
||||
* @param system_ - The core system.
|
||||
* @param buffer - The command buffer to process.
|
||||
* @param size - The size of the buffer.
|
||||
* @param stream_ - The stream to be used for sending the samples.
|
||||
* @param system - The core system.
|
||||
* @param buffer - The command buffer to process.
|
||||
* @param size - The size of the buffer.
|
||||
* @param stream - The stream to be used for sending the samples.
|
||||
*/
|
||||
void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
|
||||
|
||||
@@ -72,7 +72,8 @@ public:
|
||||
/**
|
||||
* Process the command list.
|
||||
*
|
||||
* @param index - Index of the current command list.
|
||||
* @param session_id - Session ID for the commands being processed.
|
||||
*
|
||||
* @return The time taken to process.
|
||||
*/
|
||||
u64 Process(u32 session_id);
|
||||
@@ -89,7 +90,7 @@ public:
|
||||
u8* commands{};
|
||||
/// The command buffer size
|
||||
u64 commands_buffer_size{};
|
||||
/// The maximum processing time alloted
|
||||
/// The maximum processing time allotted
|
||||
u64 max_process_time{};
|
||||
/// The number of commands in the buffer
|
||||
u32 command_count{};
|
||||
|
||||
@@ -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]);
|
||||
@@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
|
||||
output_sink.SetDeviceVolume(volume);
|
||||
}
|
||||
|
||||
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
|
||||
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
|
||||
return output_sink.GetDeviceVolume();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -73,7 +65,7 @@ public:
|
||||
* @param name - Name of the device to check. Unused.
|
||||
* @return Volume of the device.
|
||||
*/
|
||||
f32 GetDeviceVolume(std::string_view name);
|
||||
f32 GetDeviceVolume(std::string_view name) const;
|
||||
|
||||
private:
|
||||
/// Backend output sink for the device
|
||||
|
||||
@@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() {
|
||||
error_count = 0;
|
||||
}
|
||||
|
||||
void BehaviorInfo::AppendError(ErrorInfo& error) {
|
||||
void BehaviorInfo::AppendError(const ErrorInfo& error) {
|
||||
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
|
||||
error.error_code.raw, error.address);
|
||||
if (error_count < MaxErrors) {
|
||||
@@ -42,7 +42,7 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
|
||||
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
|
||||
out_count = std::min(error_count, MaxErrors);
|
||||
|
||||
for (size_t i = 0; i < MaxErrors; i++) {
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
*
|
||||
* @param error - The new error.
|
||||
*/
|
||||
void AppendError(ErrorInfo& error);
|
||||
void AppendError(const ErrorInfo& error);
|
||||
|
||||
/**
|
||||
* Copy errors to the given output container.
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
* @param out_errors - Output container to receive the errors.
|
||||
* @param out_count - The number of errors written.
|
||||
*/
|
||||
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count);
|
||||
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
|
||||
|
||||
/**
|
||||
* Update the behaviour flags.
|
||||
|
||||
@@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
|
||||
Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
|
||||
auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
|
||||
behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public:
|
||||
* @param behaviour - Behaviour to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateErrorInfo(BehaviorInfo& behaviour);
|
||||
Result UpdateErrorInfo(const BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update splitter.
|
||||
|
||||
@@ -191,6 +191,7 @@ public:
|
||||
* @param volume - Current mix volume used for calculating the ramp.
|
||||
* @param prev_volume - Previous mix volume, used for calculating the ramp,
|
||||
* also applied to the input.
|
||||
* @param prev_samples - Previous sample buffer. Used for depopping.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
|
||||
@@ -208,6 +209,7 @@ public:
|
||||
* @param volumes - Current mix volumes used for calculating the ramp.
|
||||
* @param prev_volumes - Previous mix volumes, used for calculating the ramp,
|
||||
* also applied to the input.
|
||||
* @param prev_samples - Previous sample buffer. Used for depopping.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
|
||||
@@ -297,11 +299,11 @@ public:
|
||||
/**
|
||||
* Generate a device sink command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - The sink_info to generate this command from.
|
||||
* @session_id - System session id this command is generated from.
|
||||
* @samples_buffer - The buffer to be sent to the sink if upsampling is not used.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - The sink_info to generate this command from.
|
||||
* @param session_id - System session id this command is generated from.
|
||||
* @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
|
||||
*/
|
||||
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
|
||||
u32 session_id, std::span<s32> samples_buffer);
|
||||
|
||||
@@ -197,9 +197,9 @@ public:
|
||||
/**
|
||||
* Generate an I3DL2 reverb effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - I3DL2Reverb effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - I3DL2Reverb effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id);
|
||||
@@ -207,18 +207,18 @@ public:
|
||||
/**
|
||||
* Generate an aux effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id);
|
||||
@@ -226,10 +226,10 @@ public:
|
||||
/**
|
||||
* Generate a light limiter effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Limiter effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param effect_index - Index for the statistics state.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Limiter effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param effect_index - Index for the statistics state.
|
||||
*/
|
||||
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id, u32 effect_index);
|
||||
@@ -238,21 +238,20 @@ public:
|
||||
* Generate a capture effect command.
|
||||
* Writes a mix buffer back to game memory.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Capture effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Capture effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a compressor effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Compressor effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Compressor effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
const s32 node_id);
|
||||
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate all effect commands for a mix.
|
||||
@@ -318,8 +317,9 @@ public:
|
||||
* Generate a performance command.
|
||||
* Used to report performance metrics of the AudioRenderer back to the game.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - Sink info to generate the commands from.
|
||||
* @param node_id - Node ID of the mix this command is generated for
|
||||
* @param state - Output state of the generated performance command
|
||||
* @param entry_addresses - Addresses to be written
|
||||
*/
|
||||
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
|
||||
const PerformanceEntryAddresses& entry_addresses);
|
||||
|
||||
@@ -7,17 +7,7 @@
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Mix input mix buffer into output mix buffer, with volume applied to the input.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param ramp - Ramp applied to volume every sample.
|
||||
* @param sample_count - Number of samples to process.
|
||||
* @return The final gained input sample, used for depopping.
|
||||
*/
|
||||
|
||||
template <size_t Q>
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
|
||||
const f32 ramp_, const u32 sample_count) {
|
||||
@@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
|
||||
return sample.to_int();
|
||||
}
|
||||
|
||||
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32,
|
||||
const u32);
|
||||
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
|
||||
const u32);
|
||||
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
|
||||
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
|
||||
|
||||
void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
|
||||
@@ -61,13 +61,13 @@ struct MixRampCommand : ICommand {
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param ramp - Ramp applied to volume every sample.
|
||||
* @param volume_ - Volume applied to the input.
|
||||
* @param ramp_ - Ramp applied to volume every sample.
|
||||
* @param sample_count - Number of samples to process.
|
||||
* @return The final gained input sample, used for depopping.
|
||||
*/
|
||||
template <size_t Q>
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
|
||||
const f32 ramp_, const u32 sample_count);
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
|
||||
u32 sample_count);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand {
|
||||
std::array<s16, MaxMixBuffers> inputs;
|
||||
/// Output mix buffer indexes for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> outputs;
|
||||
/// Previous mix vloumes for each mix buffer
|
||||
/// Previous mix volumes for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> prev_volumes;
|
||||
/// Current mix vloumes for each mix buffer
|
||||
/// Current mix volumes for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> volumes;
|
||||
/// Pointer to the previous sample buffer, used for depop
|
||||
CpuAddr previous_samples;
|
||||
|
||||
@@ -15,15 +15,15 @@ class EffectContext {
|
||||
public:
|
||||
/**
|
||||
* Initialize the effect context
|
||||
* @param effect_infos List of effect infos for this context
|
||||
* @param effect_count The number of effects in the list
|
||||
* @param result_states_cpu The workbuffer of result states for the CPU for this context
|
||||
* @param result_states_dsp The workbuffer of result states for the DSP for this context
|
||||
* @param state_count The number of result states
|
||||
* @param effect_infos_ - List of effect infos for this context
|
||||
* @param effect_count_ - The number of effects in the list
|
||||
* @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
|
||||
* @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
|
||||
* @param dsp_state_count - The number of result states
|
||||
*/
|
||||
void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
|
||||
void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
|
||||
std::span<EffectResultState> result_states_cpu_,
|
||||
std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count);
|
||||
std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
|
||||
|
||||
/**
|
||||
* Get the EffectInfo for a given index
|
||||
|
||||
@@ -291,7 +291,7 @@ public:
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
virtual void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
@@ -305,7 +305,7 @@ public:
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
virtual void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
return out_sample;
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> Read() {
|
||||
Common::FixedPoint<50, 14> Read() const {
|
||||
return *output;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> TapOut(const s32 index) {
|
||||
Common::FixedPoint<50, 14> TapOut(const s32 index) const {
|
||||
auto out{input - (index + 1)};
|
||||
if (out < buffer.data()) {
|
||||
out += max_delay + 1;
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
return out_sample;
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> Read() {
|
||||
Common::FixedPoint<50, 14> Read() const {
|
||||
return *output;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> TapOut(const s32 index) {
|
||||
Common::FixedPoint<50, 14> TapOut(const s32 index) const {
|
||||
auto out{input - (index + 1)};
|
||||
if (out < buffer.data()) {
|
||||
out += sample_count;
|
||||
|
||||
@@ -19,8 +19,8 @@ public:
|
||||
/**
|
||||
* Setup a new AddressInfo.
|
||||
*
|
||||
* @param cpu_address - The CPU address of this region.
|
||||
* @param size - The size of this region.
|
||||
* @param cpu_address_ - The CPU address of this region.
|
||||
* @param size_ - The size of this region.
|
||||
*/
|
||||
void Setup(CpuAddr cpu_address_, u64 size_) {
|
||||
cpu_address = cpu_address_;
|
||||
@@ -42,7 +42,6 @@ public:
|
||||
* Assign this region to a memory pool.
|
||||
*
|
||||
* @param memory_pool_ - Memory pool to assign.
|
||||
* @return The CpuAddr address of this region.
|
||||
*/
|
||||
void SetPool(MemoryPoolInfo* memory_pool_) {
|
||||
memory_pool = memory_pool_;
|
||||
|
||||
@@ -56,7 +56,7 @@ class NodeStates {
|
||||
*
|
||||
* @return The current stack position.
|
||||
*/
|
||||
u32 Count() {
|
||||
u32 Count() const {
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class NodeStates {
|
||||
*
|
||||
* @return The node on the top of the stack.
|
||||
*/
|
||||
u32 top() {
|
||||
u32 top() const {
|
||||
return stack[pos - 1];
|
||||
}
|
||||
|
||||
@@ -112,11 +112,11 @@ public:
|
||||
/**
|
||||
* Initialize the node states.
|
||||
*
|
||||
* @param buffer - The workbuffer to use. Unused.
|
||||
* @param buffer_ - The workbuffer to use. Unused.
|
||||
* @param node_buffer_size - The size of the workbuffer. Unused.
|
||||
* @param count - The number of nodes in the graph.
|
||||
*/
|
||||
void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count);
|
||||
void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
|
||||
|
||||
/**
|
||||
* Sort the graph. Only calls DepthFirstSearch.
|
||||
|
||||
@@ -73,7 +73,8 @@ public:
|
||||
* Calculate the required size for the performance workbuffer.
|
||||
*
|
||||
* @param behavior - Check which version is supported.
|
||||
* @param params - Input parameters.
|
||||
* @param params - Input parameters.
|
||||
*
|
||||
* @return Required workbuffer size.
|
||||
*/
|
||||
static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
|
||||
@@ -104,7 +105,7 @@ public:
|
||||
* @param workbuffer - Workbuffer to use for performance frames.
|
||||
* @param workbuffer_size - Size of the workbuffer.
|
||||
* @param params - Input parameters.
|
||||
* @param behavior - Behaviour to check version and data format.
|
||||
* @param behavior - Behaviour to check version and data format.
|
||||
* @param memory_pool - Used to translate the workbuffer address for the DSP.
|
||||
*/
|
||||
virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
|
||||
@@ -160,7 +161,8 @@ public:
|
||||
* workbuffer, to be written by the AudioRenderer.
|
||||
*
|
||||
* @param addresses - Filled with pointers to the new detail, which should be passed
|
||||
* to the AudioRenderer with Performance commands to be written.
|
||||
* to the AudioRenderer with Performance commands to be written.
|
||||
* @param detail_type - Performance detail type.
|
||||
* @param entry_type - The type of this detail. See PerformanceEntryType
|
||||
* @param node_id - Node id for this detail.
|
||||
* @return True if a new detail was created and the offsets are valid, otherwise false.
|
||||
|
||||
@@ -22,9 +22,7 @@ SystemManager::SystemManager(Core::System& core_)
|
||||
thread_event{Core::Timing::CreateEvent(
|
||||
"AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
|
||||
return ThreadFunc2(time);
|
||||
})} {
|
||||
core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
|
||||
}
|
||||
})} {}
|
||||
|
||||
SystemManager::~SystemManager() {
|
||||
Stop();
|
||||
@@ -125,11 +123,4 @@ std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void SystemManager::PauseCallback(bool paused) {
|
||||
if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
|
||||
update.store(true);
|
||||
update.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -73,13 +73,6 @@ private:
|
||||
*/
|
||||
std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
|
||||
|
||||
/**
|
||||
* Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
|
||||
*
|
||||
* @param paused - Are we pausing or resuming?
|
||||
*/
|
||||
void PauseCallback(bool paused);
|
||||
|
||||
enum class StreamState {
|
||||
Filling,
|
||||
Steady,
|
||||
@@ -106,8 +99,6 @@ private:
|
||||
std::shared_ptr<Core::Timing::EventType> thread_event;
|
||||
/// Atomic for main thread to wait on
|
||||
std::atomic<bool> update{};
|
||||
/// Current state of the streams
|
||||
StreamState state{StreamState::Filling};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
/**
|
||||
* Free the given upsampler.
|
||||
*
|
||||
* @param The upsampler to be freed.
|
||||
* @param info The upsampler to be freed.
|
||||
*/
|
||||
void Free(UpsamplerInfo* info);
|
||||
|
||||
|
||||
@@ -185,7 +185,8 @@ public:
|
||||
/**
|
||||
* Does this voice ned an update?
|
||||
*
|
||||
* @param params - Input parametetrs to check matching.
|
||||
* @param params - Input parameters to check matching.
|
||||
*
|
||||
* @return True if this voice needs an update, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateParameters(const InParameter& params) const;
|
||||
@@ -194,9 +195,9 @@ public:
|
||||
* Update the parameters of this voice.
|
||||
*
|
||||
* @param error_info - Output error code.
|
||||
* @param params - Input parametters to udpate from.
|
||||
* @param params - Input parameters to update from.
|
||||
* @param pool_mapper - Used to map buffers.
|
||||
* @param behavior - behavior to check supported features.
|
||||
* @param behavior - behavior to check supported features.
|
||||
*/
|
||||
void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
|
||||
const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
|
||||
@@ -218,12 +219,12 @@ public:
|
||||
/**
|
||||
* Update all wavebuffers.
|
||||
*
|
||||
* @param error_infos - Output 2D array of errors, 2 per wavebuffer.
|
||||
* @param error_count - Number of errors provided. Unused.
|
||||
* @param params - Input parametters to be used for the update.
|
||||
* @param error_infos - Output 2D array of errors, 2 per wavebuffer.
|
||||
* @param error_count - Number of errors provided. Unused.
|
||||
* @param params - Input parameters to be used for the update.
|
||||
* @param voice_states - The voice states for each channel in this voice to be updated.
|
||||
* @param pool_mapper - Used to map the wavebuffers.
|
||||
* @param behavior - Used to check for supported features.
|
||||
* @param pool_mapper - Used to map the wavebuffers.
|
||||
* @param behavior - Used to check for supported features.
|
||||
*/
|
||||
void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
|
||||
u32 error_count, const InParameter& params,
|
||||
@@ -233,13 +234,13 @@ public:
|
||||
/**
|
||||
* Update a wavebuffer.
|
||||
*
|
||||
* @param error_infos - Output array of errors.
|
||||
* @param error_info - Output array of errors.
|
||||
* @param wave_buffer - The wavebuffer to be updated.
|
||||
* @param wave_buffer_internal - Input parametters to be used for the update.
|
||||
* @param sample_format - Sample format of the wavebuffer.
|
||||
* @param valid - Is this wavebuffer valid?
|
||||
* @param pool_mapper - Used to map the wavebuffers.
|
||||
* @param behavior - Used to check for supported features.
|
||||
* @param behavior - Used to check for supported features.
|
||||
*/
|
||||
void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
|
||||
const WaveBufferInternal& wave_buffer_internal,
|
||||
@@ -276,7 +277,7 @@ public:
|
||||
/**
|
||||
* Check if this voice has any mixing connections.
|
||||
*
|
||||
* @return True if this voice participes in mixing, otherwise false.
|
||||
* @return True if this voice participates in mixing, otherwise false.
|
||||
*/
|
||||
bool HasAnyConnection() const;
|
||||
|
||||
@@ -301,7 +302,8 @@ public:
|
||||
/**
|
||||
* Update this voice on command generation.
|
||||
*
|
||||
* @param voice_states - Voice states for these wavebuffers.
|
||||
* @param voice_context - Voice context for these wavebuffers.
|
||||
*
|
||||
* @return True if this voice should be generated, otherwise false.
|
||||
*/
|
||||
bool UpdateForCommandGeneration(VoiceContext& voice_context);
|
||||
|
||||
@@ -129,20 +129,13 @@ public:
|
||||
* Default false.
|
||||
*/
|
||||
void Start(bool resume = false) override {
|
||||
if (!ctx) {
|
||||
if (!ctx || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resume && was_playing) {
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
|
||||
}
|
||||
paused = false;
|
||||
} else if (!resume) {
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
|
||||
}
|
||||
paused = false;
|
||||
paused = false;
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,16 +144,15 @@ public:
|
||||
*/
|
||||
void Stop() override {
|
||||
Unstall();
|
||||
if (!ctx) {
|
||||
|
||||
if (!ctx || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = true;
|
||||
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
|
||||
}
|
||||
|
||||
was_playing.store(!paused);
|
||||
paused = true;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -286,18 +278,6 @@ void CubebSink::CloseStreams() {
|
||||
sink_streams.clear();
|
||||
}
|
||||
|
||||
void CubebSink::PauseStreams() {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void CubebSink::UnpauseStreams() {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->Start(true);
|
||||
}
|
||||
}
|
||||
|
||||
f32 CubebSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
|
||||
@@ -34,8 +34,7 @@ public:
|
||||
* May differ from the device's channel count.
|
||||
* @param name - Name of this stream.
|
||||
* @param type - Type of this stream, render/in/out.
|
||||
* @param event - Audio render only, a signal used to prevent the renderer running too
|
||||
* fast.
|
||||
*
|
||||
* @return A pointer to the created SinkStream
|
||||
*/
|
||||
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
@@ -53,16 +52,6 @@ public:
|
||||
*/
|
||||
void CloseStreams() override;
|
||||
|
||||
/**
|
||||
* Pause all streams.
|
||||
*/
|
||||
void PauseStreams() override;
|
||||
|
||||
/**
|
||||
* Unpause all streams.
|
||||
*/
|
||||
void UnpauseStreams() override;
|
||||
|
||||
/**
|
||||
* Get the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
@@ -101,7 +90,7 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of conencted devices from Cubeb.
|
||||
* Get a list of connected devices from Cubeb.
|
||||
*
|
||||
* @param capture - Return input (capture) devices if true, otherwise output devices.
|
||||
*/
|
||||
|
||||
@@ -44,8 +44,6 @@ public:
|
||||
|
||||
void CloseStream(SinkStream*) override {}
|
||||
void CloseStreams() override {}
|
||||
void PauseStreams() override {}
|
||||
void UnpauseStreams() override {}
|
||||
f32 GetDeviceVolume() const override {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@@ -108,17 +108,12 @@ public:
|
||||
* Default false.
|
||||
*/
|
||||
void Start(bool resume = false) override {
|
||||
if (device == 0) {
|
||||
if (device == 0 || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resume && was_playing) {
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
paused = false;
|
||||
} else if (!resume) {
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
paused = false;
|
||||
}
|
||||
paused = false;
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,11 +121,11 @@ public:
|
||||
*/
|
||||
void Stop() override {
|
||||
Unstall();
|
||||
if (device == 0) {
|
||||
if (device == 0 || paused) {
|
||||
return;
|
||||
}
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
paused = true;
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -207,18 +202,6 @@ void SDLSink::CloseStreams() {
|
||||
sink_streams.clear();
|
||||
}
|
||||
|
||||
void SDLSink::PauseStreams() {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void SDLSink::UnpauseStreams() {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->Start();
|
||||
}
|
||||
}
|
||||
|
||||
f32 SDLSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
|
||||
@@ -32,8 +32,7 @@ public:
|
||||
* May differ from the device's channel count.
|
||||
* @param name - Name of this stream.
|
||||
* @param type - Type of this stream, render/in/out.
|
||||
* @param event - Audio render only, a signal used to prevent the renderer running too
|
||||
* fast.
|
||||
*
|
||||
* @return A pointer to the created SinkStream
|
||||
*/
|
||||
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
@@ -51,16 +50,6 @@ public:
|
||||
*/
|
||||
void CloseStreams() override;
|
||||
|
||||
/**
|
||||
* Pause all streams.
|
||||
*/
|
||||
void PauseStreams() override;
|
||||
|
||||
/**
|
||||
* Unpause all streams.
|
||||
*/
|
||||
void UnpauseStreams() override;
|
||||
|
||||
/**
|
||||
* Get the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
@@ -92,7 +81,7 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of conencted devices from Cubeb.
|
||||
* Get a list of connected devices from SDL.
|
||||
*
|
||||
* @param capture - Return input (capture) devices if true, otherwise output devices.
|
||||
*/
|
||||
|
||||
@@ -39,16 +39,6 @@ public:
|
||||
*/
|
||||
virtual void CloseStreams() = 0;
|
||||
|
||||
/**
|
||||
* Pause all streams.
|
||||
*/
|
||||
virtual void PauseStreams() = 0;
|
||||
|
||||
/**
|
||||
* Unpause all streams.
|
||||
*/
|
||||
virtual void UnpauseStreams() = 0;
|
||||
|
||||
/**
|
||||
* Create a new sink stream, kept within this sink, with a pointer returned for use.
|
||||
* Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
|
||||
@@ -58,8 +48,7 @@ public:
|
||||
* May differ from the device's channel count.
|
||||
* @param name - Name of this stream.
|
||||
* @param type - Type of this stream, render/in/out.
|
||||
* @param event - Audio render only, a signal used to prevent the renderer running too
|
||||
* fast.
|
||||
*
|
||||
* @return A pointer to the created SinkStream
|
||||
*/
|
||||
virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
|
||||
@@ -143,6 +143,12 @@ void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t n
|
||||
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
|
||||
size_t frames_written{0};
|
||||
|
||||
// If we're paused or going to shut down, we don't want to consume buffers as coretiming is
|
||||
// paused and we'll desync, so just return.
|
||||
if (system.IsPaused() || system.IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (queued_buffers > max_queue_size) {
|
||||
Stall();
|
||||
}
|
||||
@@ -193,13 +199,28 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
|
||||
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
|
||||
size_t frames_written{0};
|
||||
|
||||
// If we're paused or going to shut down, we don't want to consume buffers as coretiming is
|
||||
// paused and we'll desync, so just play silence.
|
||||
if (system.IsPaused() || system.IsShuttingDown()) {
|
||||
constexpr std::array<s16, 6> silence{};
|
||||
for (size_t i = frames_written; i < num_frames; i++) {
|
||||
std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
|
||||
// queued up (30+) but not all at once, which causes constant stalling here, so just let the
|
||||
// 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) {
|
||||
@@ -239,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public:
|
||||
*
|
||||
* @return The number of queued buffers.
|
||||
*/
|
||||
u32 GetQueueSize() {
|
||||
u32 GetQueueSize() const {
|
||||
return queued_buffers.load();
|
||||
}
|
||||
|
||||
@@ -214,14 +214,12 @@ protected:
|
||||
Core::System& system;
|
||||
/// Type of this stream
|
||||
StreamType type;
|
||||
/// Set by the audio render/in/out systen which uses this stream
|
||||
/// Set by the audio render/in/out system which uses this stream
|
||||
u32 system_channels{2};
|
||||
/// Channels supported by hardware
|
||||
u32 device_channels{2};
|
||||
/// Is this stream currently paused?
|
||||
std::atomic<bool> paused{true};
|
||||
/// Was this stream previously playing?
|
||||
std::atomic<bool> was_playing{false};
|
||||
/// Name of this stream
|
||||
std::string name{};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ find_package(Git QUIET)
|
||||
|
||||
add_custom_command(OUTPUT scm_rev.cpp
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSRC_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DSRC_DIR=${PROJECT_SOURCE_DIR}
|
||||
-DBUILD_REPOSITORY=${BUILD_REPOSITORY}
|
||||
-DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
|
||||
-DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
|
||||
@@ -31,13 +31,13 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
-DGIT_BRANCH=${GIT_BRANCH}
|
||||
-DBUILD_FULLNAME=${BUILD_FULLNAME}
|
||||
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
|
||||
-P ${PROJECT_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"
|
||||
"${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
@@ -166,6 +166,7 @@ if(ARCHITECTURE_x86_64)
|
||||
x64/xbyak_abi.h
|
||||
x64/xbyak_util.h
|
||||
)
|
||||
target_link_libraries(common PRIVATE xbyak)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
@@ -189,7 +190,7 @@ endif()
|
||||
create_target_directory_groups(common)
|
||||
|
||||
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 xbyak)
|
||||
target_link_libraries(common PRIVATE lz4::lz4)
|
||||
if (TARGET zstd::zstd)
|
||||
target_link_libraries(common PRIVATE zstd::zstd)
|
||||
else()
|
||||
|
||||
@@ -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"};
|
||||
|
||||
@@ -54,6 +54,10 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSet() {
|
||||
return is_set;
|
||||
}
|
||||
|
||||
private:
|
||||
std::condition_variable condvar;
|
||||
std::mutex mutex;
|
||||
|
||||
@@ -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
|
||||
@@ -525,6 +519,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
|
||||
|
||||
@@ -141,8 +141,6 @@ struct System::Impl {
|
||||
core_timing.SyncPause(false);
|
||||
is_paused = false;
|
||||
|
||||
audio_core->PauseSinks(false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -150,8 +148,6 @@ struct System::Impl {
|
||||
std::unique_lock<std::mutex> lk(suspend_guard);
|
||||
status = SystemResultStatus::Success;
|
||||
|
||||
audio_core->PauseSinks(true);
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
kernel.Suspend(true);
|
||||
is_paused = true;
|
||||
|
||||
@@ -73,7 +73,6 @@ void CoreTiming::Shutdown() {
|
||||
if (timer_thread) {
|
||||
timer_thread->join();
|
||||
}
|
||||
pause_callbacks.clear();
|
||||
ClearPendingEvents();
|
||||
timer_thread.reset();
|
||||
has_started = false;
|
||||
@@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) {
|
||||
if (!is_paused) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::SyncPause(bool is_paused) {
|
||||
@@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) {
|
||||
if (!is_paused) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused);
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreTiming::IsRunning() const {
|
||||
@@ -143,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,
|
||||
@@ -219,11 +214,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
|
||||
std::scoped_lock lock{basic_lock};
|
||||
pause_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
std::scoped_lock lock{advance_lock, basic_lock};
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
@@ -243,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<>());
|
||||
@@ -264,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;
|
||||
}
|
||||
@@ -278,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();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Core::Timing {
|
||||
/// A callback that may be scheduled for a particular core timing event.
|
||||
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
|
||||
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
|
||||
using PauseCallback = std::function<void(bool paused)>;
|
||||
|
||||
/// Contains the characteristics of a particular event.
|
||||
struct EventType {
|
||||
@@ -134,9 +133,6 @@ public:
|
||||
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
|
||||
std::optional<s64> Advance();
|
||||
|
||||
/// Register a callback function to be called when coretiming pauses.
|
||||
void RegisterPauseCallback(PauseCallback&& callback);
|
||||
|
||||
private:
|
||||
struct Event;
|
||||
|
||||
@@ -176,8 +172,6 @@ private:
|
||||
/// Cycle timing
|
||||
u64 ticks{};
|
||||
s64 downcount{};
|
||||
|
||||
std::vector<PauseCallback> pause_callbacks{};
|
||||
};
|
||||
|
||||
/// Creates a core timing event with the given name and callback.
|
||||
|
||||
@@ -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.");
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -255,6 +255,32 @@ void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) {
|
||||
GetWorkBufferSize(ctx);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) {
|
||||
OpusMultiStreamParametersEx param;
|
||||
std::memcpy(¶m, 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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
383
src/core/hle/service/nfp/amiibo_crypto.cpp
Normal 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
|
||||
98
src/core/hle/service/nfp/amiibo_crypto.h
Normal 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
|
||||