Compare commits
107 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81fff7aec0 | ||
|
|
deecd7f074 | ||
|
|
6f511c8006 | ||
|
|
47ccfabe18 | ||
|
|
f883cd4f0e | ||
|
|
25702b6256 | ||
|
|
d82b181d44 | ||
|
|
6c41d1cd7e | ||
|
|
3c54edae24 | ||
|
|
5a0a9c7449 | ||
|
|
3a20d9734f | ||
|
|
43503a69bf | ||
|
|
50ad745585 | ||
|
|
8eb1398f8d | ||
|
|
8e0c80f269 | ||
|
|
3728bbc22a | ||
|
|
57fe7fdec0 | ||
|
|
3d4a0b94e3 | ||
|
|
d45ad75404 | ||
|
|
0a662d009b | ||
|
|
25ee892d5e | ||
|
|
e1afeec76d | ||
|
|
f297e9ff22 | ||
|
|
2b9b695fa7 | ||
|
|
e03f46fb0e | ||
|
|
8d0b1a957e | ||
|
|
5c907f85fc | ||
|
|
0759df0aff | ||
|
|
ab6f8d8a1e | ||
|
|
634c6e24b0 | ||
|
|
1dbd22e695 | ||
|
|
99db7d23dd | ||
|
|
8566096794 | ||
|
|
87e7cc2d5a | ||
|
|
aacb473aa2 | ||
|
|
f4417eab8f | ||
|
|
ab47a660c8 | ||
|
|
2036504a82 | ||
|
|
e6eae4b815 | ||
|
|
3c09d9abe6 | ||
|
|
507a9c6a40 | ||
|
|
000ad558dd | ||
|
|
7c756baa77 | ||
|
|
5ea740beb5 | ||
|
|
100a4bd988 | ||
|
|
189a50bc2a | ||
|
|
b3c46d6948 | ||
|
|
466cd52ad4 | ||
|
|
2e9a810423 | ||
|
|
ca9901867e | ||
|
|
0366c18d87 | ||
|
|
47e4f6a52c | ||
|
|
38fc995f6c | ||
|
|
6fdd501113 | ||
|
|
8be6e1c522 | ||
|
|
4fde66e609 | ||
|
|
c17953978b | ||
|
|
d633397883 | ||
|
|
678d9ccad6 | ||
|
|
94c34f23d7 | ||
|
|
7fbaf62bac | ||
|
|
accdb84993 | ||
|
|
e29492d114 | ||
|
|
80bdb44ead | ||
|
|
c818728513 | ||
|
|
9aac7fbc22 | ||
|
|
6bfabdedfd | ||
|
|
a86e52a375 | ||
|
|
53be058e74 | ||
|
|
d648cd562a | ||
|
|
bfa60e2d4e | ||
|
|
514b74a098 | ||
|
|
49344111cc | ||
|
|
c56822a405 | ||
|
|
e55d086cc9 | ||
|
|
5d86c52a3a | ||
|
|
19c466dfb1 | ||
|
|
bcf1eafb8b | ||
|
|
2d410ddf4d | ||
|
|
92b70a3bf9 | ||
|
|
e8183f9ef0 | ||
|
|
b8ce87103d | ||
|
|
ea17b294ea | ||
|
|
fe8c7e66e2 | ||
|
|
02f8f1bb3e | ||
|
|
d8bcb1e973 | ||
|
|
f0551aef09 | ||
|
|
102db206e0 | ||
|
|
1bde5a3c6a | ||
|
|
86773a7f08 | ||
|
|
cb7c96b96a | ||
|
|
f352ad5c93 | ||
|
|
8812018c1d | ||
|
|
862131ead9 | ||
|
|
78d146f907 | ||
|
|
68658a8385 | ||
|
|
2903f3524e | ||
|
|
2c0b75a744 | ||
|
|
647992e666 | ||
|
|
532ec459b8 | ||
|
|
f6c53526b3 | ||
|
|
943662dc3c | ||
|
|
f2073217a4 | ||
|
|
c00ed8f4ff | ||
|
|
84b6059012 | ||
|
|
855e7237ff | ||
|
|
64dbc92b61 |
@@ -5,10 +5,11 @@ cd /yuzu
|
||||
ccache -s
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
|
||||
ninja
|
||||
|
||||
ccache -s
|
||||
|
||||
ctest -VV -C Release
|
||||
# Ignore zlib's tests, since they aren't gated behind a CMake option.
|
||||
ctest -VV -E "(example|example64)" -C Release
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
mkdir -p "ccache" || true
|
||||
chmod a+x ./.ci/scripts/linux/docker.sh
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh $1
|
||||
|
||||
@@ -13,7 +13,7 @@ echo '' >> /bin/cmd
|
||||
chmod +x /bin/cmd
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
|
||||
# Clean up the dirty hacks
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
mkdir -p "ccache" || true
|
||||
chmod a+x ./.ci/scripts/windows/docker.sh
|
||||
docker run -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.ci/scripts/windows/docker.sh
|
||||
docker run -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.ci/scripts/windows/docker.sh $1
|
||||
|
||||
32
.ci/scripts/windows/upload.ps1
Normal file
32
.ci/scripts/windows/upload.ps1
Normal file
@@ -0,0 +1,32 @@
|
||||
$GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
|
||||
$GITREV = $(git show -s --format='%h')
|
||||
$RELEASE_DIST = "yuzu-windows-msvc"
|
||||
|
||||
$MSVC_BUILD_ZIP = "yuzu-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
|
||||
$MSVC_BUILD_PDB = "yuzu-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
|
||||
$MSVC_SEVENZIP = "yuzu-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
|
||||
|
||||
$env:BUILD_ZIP = $MSVC_BUILD_ZIP
|
||||
$env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
|
||||
$env:BUILD_UPDATE = $MSVC_SEVENZIP
|
||||
|
||||
$BUILD_DIR = ".\build\bin\Release"
|
||||
|
||||
mkdir pdb
|
||||
Get-ChildItem "$BUILD_DIR\" -Recurse -Filter "*.pdb" | Copy-Item -destination .\pdb
|
||||
7z a -tzip $MSVC_BUILD_PDB .\pdb\*.pdb
|
||||
rm "$BUILD_DIR\*.pdb"
|
||||
mkdir $RELEASE_DIST
|
||||
mkdir "artifacts"
|
||||
|
||||
Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
|
||||
rm "$RELEASE_DIST\*.exe"
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Copy-Item .\license.txt -Destination $RELEASE_DIST
|
||||
Copy-Item .\README.md -Destination $RELEASE_DIST
|
||||
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
|
||||
7z a $MSVC_SEVENZIP $RELEASE_DIST
|
||||
|
||||
Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts"
|
||||
22
.ci/templates/build-msvc.yml
Normal file
22
.ci/templates/build-msvc.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
cache: 'false'
|
||||
version: ''
|
||||
|
||||
steps:
|
||||
- script: mkdir build && cd build && cmake -G "Visual Studio 15 2017 Win64" --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
inputs:
|
||||
solution: 'build/yuzu.sln'
|
||||
maximumCpuCount: true
|
||||
configuration: release
|
||||
- task: PowerShell@2
|
||||
displayName: 'Package Artifacts'
|
||||
inputs:
|
||||
targetType: 'filePath'
|
||||
filePath: './.ci/scripts/windows/upload.ps1'
|
||||
- publish: artifacts
|
||||
artifact: 'yuzu-$(BuildName)-windows-msvc'
|
||||
displayName: 'Upload Artifacts'
|
||||
@@ -1,10 +1,9 @@
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
cache: 'false'
|
||||
version: ''
|
||||
|
||||
steps:
|
||||
- script: export DATE=`date '+%Y.%m.%d'` && export CI=true && AZURE_REPO_NAME=yuzu-emu/yuzu-$(BuildName) && AZURE_REPO_TAG=$(BuildName)-$DATE
|
||||
displayName: 'Determine Build Name'
|
||||
- task: DockerInstaller@0
|
||||
displayName: 'Prepare Environment'
|
||||
inputs:
|
||||
@@ -15,7 +14,7 @@ steps:
|
||||
key: yuzu-v1-$(BuildName)-$(BuildSuffix)-$(CacheSuffix)
|
||||
path: $(System.DefaultWorkingDirectory)/ccache
|
||||
cacheHitVar: CACHE_RESTORED
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/exec.sh && ./.ci/scripts/$(ScriptFolder)/exec.sh
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/exec.sh && ./.ci/scripts/$(ScriptFolder)/exec.sh ${{ parameters['version'] }}
|
||||
displayName: 'Build'
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/upload.sh && RELEASE_NAME=$(BuildName) ./.ci/scripts/$(ScriptFolder)/upload.sh
|
||||
displayName: 'Package Artifacts'
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
parameters:
|
||||
version: ''
|
||||
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'standard'
|
||||
@@ -20,4 +23,5 @@ jobs:
|
||||
- template: ./build-single.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: $(parameters.cache)
|
||||
cache: $(parameters.cache)
|
||||
version: $(parameters.version)
|
||||
@@ -1,3 +1,6 @@
|
||||
parameters:
|
||||
version: ''
|
||||
|
||||
jobs:
|
||||
- job: build_test
|
||||
displayName: 'testing'
|
||||
@@ -31,3 +34,4 @@ jobs:
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: 'false'
|
||||
version: $(parameters.version)
|
||||
@@ -11,5 +11,5 @@ steps:
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '.ci/scripts/merge/apply-patches-by-label.py'
|
||||
arguments: '${{ parameters.matchLabel }} patches'
|
||||
arguments: '${{ parameters.matchLabel }} Tagged patches'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
@@ -2,7 +2,7 @@ steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Windows Release'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-windows-mingw'
|
||||
artifactName: 'yuzu-$(BuildName)-windows-msvc'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
variables:
|
||||
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
|
||||
|
||||
stages:
|
||||
- stage: format
|
||||
displayName: 'format'
|
||||
@@ -15,12 +18,49 @@ stages:
|
||||
dependsOn: format
|
||||
displayName: 'build'
|
||||
jobs:
|
||||
- template: ./templates/build-standard.yml
|
||||
parameters:
|
||||
cache: 'true'
|
||||
- job: build
|
||||
displayName: 'standard'
|
||||
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: 'true'
|
||||
version: $(DisplayVersion)
|
||||
- stage: build_win
|
||||
dependsOn: format
|
||||
displayName: 'build-windows'
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'msvc'
|
||||
pool:
|
||||
vmImage: vs2017-win2016
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./templates/build-msvc.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: 'true'
|
||||
version: $(DisplayVersion)
|
||||
- stage: release
|
||||
displayName: 'Release'
|
||||
dependsOn: build
|
||||
dependsOn:
|
||||
- build
|
||||
- build_win
|
||||
jobs:
|
||||
- job: github
|
||||
displayName: 'GitHub Release'
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
variables:
|
||||
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
|
||||
|
||||
stages:
|
||||
- stage: format
|
||||
displayName: 'format'
|
||||
@@ -15,14 +18,17 @@ stages:
|
||||
dependsOn: format
|
||||
displayName: 'build'
|
||||
jobs:
|
||||
- template: ./templates/build-standard.yml
|
||||
parameters:
|
||||
cache: 'true'
|
||||
- stage: release
|
||||
displayName: 'release'
|
||||
dependsOn: build
|
||||
jobs:
|
||||
- job: azure
|
||||
displayName: 'azure'
|
||||
- job: build
|
||||
displayName: 'windows-msvc'
|
||||
pool:
|
||||
vmImage: vs2017-win2016
|
||||
steps:
|
||||
- template: ./templates/release-universal.yml
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./templates/build-msvc.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: $(parameters.cache)
|
||||
version: $(DisplayVersion)
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -46,3 +46,9 @@
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/ReinUsesLisp/sirit
|
||||
[submodule "libzip"]
|
||||
path = externals/libzip
|
||||
url = https://github.com/DarkLordZach/libzip
|
||||
[submodule "zlib"]
|
||||
path = externals/zlib
|
||||
url = https://github.com/madler/zlib
|
||||
|
||||
@@ -21,6 +21,8 @@ option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||
|
||||
option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implementation of BCAT" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(ENABLE_VULKAN "Enables Vulkan backend" ON)
|
||||
|
||||
@@ -83,9 +83,15 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/warp.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.h"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.cpp"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.h"
|
||||
"${VIDEO_CORE}/shader/decode.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.h"
|
||||
"${VIDEO_CORE}/shader/node.h"
|
||||
"${VIDEO_CORE}/shader/node_helper.cpp"
|
||||
"${VIDEO_CORE}/shader/node_helper.h"
|
||||
|
||||
6
externals/CMakeLists.txt
vendored
6
externals/CMakeLists.txt
vendored
@@ -77,6 +77,12 @@ if (ENABLE_VULKAN)
|
||||
add_subdirectory(sirit)
|
||||
endif()
|
||||
|
||||
# zlib
|
||||
add_subdirectory(zlib EXCLUDE_FROM_ALL)
|
||||
|
||||
# libzip
|
||||
add_subdirectory(libzip EXCLUDE_FROM_ALL)
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
|
||||
1
externals/libzip
vendored
Submodule
1
externals/libzip
vendored
Submodule
Submodule externals/libzip added at bd7a8103e9
1
externals/zlib
vendored
Submodule
1
externals/zlib
vendored
Submodule
Submodule externals/zlib added at cacf7f1d4e
33
license.txt
33
license.txt
@@ -341,15 +341,24 @@ Public License instead of this License.
|
||||
|
||||
The icons used in this project have the following licenses:
|
||||
|
||||
Icon Name | License | Origin/Author
|
||||
--- | --- | ---
|
||||
checked.png | Free for non-commercial use
|
||||
failed.png | Free for non-commercial use
|
||||
lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
Icon Name | License | Origin/Author
|
||||
--- | --- | ---
|
||||
checked.png | Free for non-commercial use
|
||||
failed.png | Free for non-commercial use
|
||||
lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
plus_folder.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
bad_folder.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
chip.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
folder.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
sd_card.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
plus_folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
bad_folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
chip.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
sd_card.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
|
||||
Note:
|
||||
Some icons are different in different themes, and they are separately listed
|
||||
only when they have different licenses/origins.
|
||||
|
||||
@@ -15,11 +15,23 @@ if (DEFINED ENV{CI})
|
||||
set(BUILD_TAG $ENV{AZURE_REPO_TAG})
|
||||
endif()
|
||||
endif()
|
||||
if (DEFINED ENV{TITLEBARFORMATIDLE})
|
||||
set(TITLE_BAR_FORMAT_IDLE $ENV{TITLEBARFORMATIDLE})
|
||||
endif ()
|
||||
if (DEFINED ENV{TITLEBARFORMATRUNNING})
|
||||
set(TITLE_BAR_FORMAT_RUNNING $ENV{TITLEBARFORMATRUNNING})
|
||||
endif ()
|
||||
if (DEFINED ENV{DISPLAYVERSION})
|
||||
set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
|
||||
endif ()
|
||||
add_custom_command(OUTPUT scm_rev.cpp
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSRC_DIR="${CMAKE_SOURCE_DIR}"
|
||||
-DBUILD_REPOSITORY="${BUILD_REPOSITORY}"
|
||||
-DTITLE_BAR_FORMAT_IDLE="${TITLE_BAR_FORMAT_IDLE}"
|
||||
-DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}"
|
||||
-DBUILD_TAG="${BUILD_TAG}"
|
||||
-DBUILD_ID="${DISPLAY_VERSION}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
|
||||
DEPENDS
|
||||
# WARNING! It was too much work to try and make a common location for this list,
|
||||
@@ -60,9 +72,15 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/warp.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.h"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.cpp"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.h"
|
||||
"${VIDEO_CORE}/shader/decode.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.h"
|
||||
"${VIDEO_CORE}/shader/node.h"
|
||||
"${VIDEO_CORE}/shader/node_helper.cpp"
|
||||
"${VIDEO_CORE}/shader/node_helper.h"
|
||||
|
||||
@@ -713,7 +713,6 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
case UserPath::RootDir:
|
||||
user_path = paths[UserPath::RootDir] + DIR_SEP;
|
||||
break;
|
||||
|
||||
case UserPath::UserDir:
|
||||
user_path = paths[UserPath::RootDir] + DIR_SEP;
|
||||
paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP;
|
||||
@@ -721,6 +720,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP;
|
||||
paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#define BUILD_DATE "@BUILD_DATE@"
|
||||
#define BUILD_FULLNAME "@BUILD_FULLNAME@"
|
||||
#define BUILD_VERSION "@BUILD_VERSION@"
|
||||
#define BUILD_ID "@BUILD_ID@"
|
||||
#define TITLE_BAR_FORMAT_IDLE "@TITLE_BAR_FORMAT_IDLE@"
|
||||
#define TITLE_BAR_FORMAT_RUNNING "@TITLE_BAR_FORMAT_RUNNING@"
|
||||
#define SHADER_CACHE_VERSION "@SHADER_CACHE_VERSION@"
|
||||
|
||||
namespace Common {
|
||||
@@ -22,6 +25,9 @@ const char g_build_name[] = BUILD_NAME;
|
||||
const char g_build_date[] = BUILD_DATE;
|
||||
const char g_build_fullname[] = BUILD_FULLNAME;
|
||||
const char g_build_version[] = BUILD_VERSION;
|
||||
const char g_build_id[] = BUILD_ID;
|
||||
const char g_title_bar_format_idle[] = TITLE_BAR_FORMAT_IDLE;
|
||||
const char g_title_bar_format_running[] = TITLE_BAR_FORMAT_RUNNING;
|
||||
const char g_shader_cache_version[] = SHADER_CACHE_VERSION;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -13,6 +13,9 @@ extern const char g_build_name[];
|
||||
extern const char g_build_date[];
|
||||
extern const char g_build_fullname[];
|
||||
extern const char g_build_version[];
|
||||
extern const char g_build_id[];
|
||||
extern const char g_title_bar_format_idle[];
|
||||
extern const char g_title_bar_format_running[];
|
||||
extern const char g_shader_cache_version[];
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
if (YUZU_ENABLE_BOXCAT)
|
||||
set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h)
|
||||
else()
|
||||
set(BCAT_BOXCAT_ADDITIONAL_SOURCES)
|
||||
endif()
|
||||
|
||||
add_library(core STATIC
|
||||
arm/arm_interface.h
|
||||
arm/arm_interface.cpp
|
||||
@@ -82,6 +88,8 @@ add_library(core STATIC
|
||||
file_sys/vfs_concat.h
|
||||
file_sys/vfs_layered.cpp
|
||||
file_sys/vfs_layered.h
|
||||
file_sys/vfs_libzip.cpp
|
||||
file_sys/vfs_libzip.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
@@ -241,6 +249,9 @@ add_library(core STATIC
|
||||
hle/service/audio/errors.h
|
||||
hle/service/audio/hwopus.cpp
|
||||
hle/service/audio/hwopus.h
|
||||
hle/service/bcat/backend/backend.cpp
|
||||
hle/service/bcat/backend/backend.h
|
||||
${BCAT_BOXCAT_ADDITIONAL_SOURCES}
|
||||
hle/service/bcat/bcat.cpp
|
||||
hle/service/bcat/bcat.h
|
||||
hle/service/bcat/module.cpp
|
||||
@@ -499,6 +510,15 @@ create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
|
||||
|
||||
if (YUZU_ENABLE_BOXCAT)
|
||||
get_directory_property(OPENSSL_LIBS
|
||||
DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
|
||||
DEFINITION OPENSSL_LIBS)
|
||||
target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
|
||||
target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PRIVATE web_service)
|
||||
|
||||
@@ -339,6 +339,7 @@ struct System::Impl {
|
||||
|
||||
std::unique_ptr<Memory::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
std::array<u8, 0x20> build_id{};
|
||||
|
||||
/// Frontend applets
|
||||
Service::AM::Applets::AppletManager applet_manager;
|
||||
@@ -640,6 +641,14 @@ bool System::GetExitLock() const {
|
||||
return impl->exit_lock;
|
||||
}
|
||||
|
||||
void System::SetCurrentProcessBuildID(std::array<u8, 32> id) {
|
||||
impl->build_id = id;
|
||||
}
|
||||
|
||||
const std::array<u8, 32>& System::GetCurrentProcessBuildID() const {
|
||||
return impl->build_id;
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
||||
return impl->Init(*this, emu_window);
|
||||
}
|
||||
|
||||
@@ -330,6 +330,10 @@ public:
|
||||
|
||||
bool GetExitLock() const;
|
||||
|
||||
void SetCurrentProcessBuildID(std::array<u8, 0x20> id);
|
||||
|
||||
const std::array<u8, 0x20>& GetCurrentProcessBuildID() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
|
||||
@@ -136,4 +136,9 @@ u64 BISFactory::GetFullNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_total_size);
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const {
|
||||
return GetOrCreateDirectoryRelative(nand_root,
|
||||
fmt::format("/system/save/bcat/{:016X}", title_id));
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
u64 GetUserNANDTotalSpace() const;
|
||||
u64 GetFullNANDTotalSpace() const;
|
||||
|
||||
VirtualDir GetBCATDirectory(u64 title_id) const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
|
||||
79
src/core/file_sys/vfs_libzip.cpp
Normal file
79
src/core/file_sys/vfs_libzip.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string>
|
||||
#include <zip.h>
|
||||
#include "common/logging/backend.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_libzip.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualDir ExtractZIP(VirtualFile file) {
|
||||
zip_error_t error{};
|
||||
|
||||
const auto data = file->ReadAllBytes();
|
||||
std::unique_ptr<zip_source_t, decltype(&zip_source_close)> src{
|
||||
zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close};
|
||||
if (src == nullptr)
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error),
|
||||
zip_close};
|
||||
if (zip == nullptr)
|
||||
return nullptr;
|
||||
|
||||
std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>();
|
||||
|
||||
const auto num_entries = zip_get_num_entries(zip.get(), 0);
|
||||
|
||||
zip_stat_t stat{};
|
||||
zip_stat_init(&stat);
|
||||
|
||||
for (std::size_t i = 0; i < num_entries; ++i) {
|
||||
const auto stat_res = zip_stat_index(zip.get(), i, 0, &stat);
|
||||
if (stat_res == -1)
|
||||
return nullptr;
|
||||
|
||||
const std::string name(stat.name);
|
||||
if (name.empty())
|
||||
continue;
|
||||
|
||||
if (name.back() != '/') {
|
||||
std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file{
|
||||
zip_fopen_index(zip.get(), i, 0), zip_fclose};
|
||||
|
||||
std::vector<u8> buf(stat.size);
|
||||
if (zip_fread(file.get(), buf.data(), buf.size()) != buf.size())
|
||||
return nullptr;
|
||||
|
||||
const auto parts = FileUtil::SplitPathComponents(stat.name);
|
||||
const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
|
||||
|
||||
std::shared_ptr<VectorVfsDirectory> dtrv = out;
|
||||
for (std::size_t j = 0; j < parts.size() - 1; ++j) {
|
||||
if (dtrv == nullptr)
|
||||
return nullptr;
|
||||
const auto subdir = dtrv->GetSubdirectory(parts[j]);
|
||||
if (subdir == nullptr) {
|
||||
const auto temp = std::make_shared<VectorVfsDirectory>(
|
||||
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parts[j]);
|
||||
dtrv->AddDirectory(temp);
|
||||
dtrv = temp;
|
||||
} else {
|
||||
dtrv = std::dynamic_pointer_cast<VectorVfsDirectory>(subdir);
|
||||
}
|
||||
}
|
||||
|
||||
if (dtrv == nullptr)
|
||||
return nullptr;
|
||||
dtrv->AddFile(new_file);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
13
src/core/file_sys/vfs_libzip.h
Normal file
13
src/core/file_sys/vfs_libzip.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualDir ExtractZIP(VirtualFile zip);
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "core/hle/service/am/tcap.h"
|
||||
#include "core/hle/service/apm/controller.h"
|
||||
#include "core/hle/service/apm/interface.h"
|
||||
#include "core/hle/service/bcat/backend/backend.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/ns.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
@@ -46,15 +47,20 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
|
||||
constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3};
|
||||
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
|
||||
|
||||
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
|
||||
enum class LaunchParameterKind : u32 {
|
||||
ApplicationSpecific = 1,
|
||||
AccountPreselectedUser = 2,
|
||||
};
|
||||
|
||||
struct LaunchParameters {
|
||||
constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA;
|
||||
|
||||
struct LaunchParameterAccountPreselectedUser {
|
||||
u32_le magic;
|
||||
u32_le is_account_selected;
|
||||
u128 current_user;
|
||||
INSERT_PADDING_BYTES(0x70);
|
||||
};
|
||||
static_assert(sizeof(LaunchParameters) == 0x88);
|
||||
static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88);
|
||||
|
||||
IWindowController::IWindowController(Core::System& system_)
|
||||
: ServiceFramework("IWindowController"), system{system_} {
|
||||
@@ -1128,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
|
||||
}
|
||||
|
||||
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto kind = rp.PopEnum<LaunchParameterKind>();
|
||||
|
||||
LaunchParameters params{};
|
||||
LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
|
||||
|
||||
params.magic = POP_LAUNCH_PARAMETER_MAGIC;
|
||||
params.is_account_selected = 1;
|
||||
if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
|
||||
const auto backend = BCAT::CreateBackendFromSettings(
|
||||
[this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); });
|
||||
const auto build_id_full = Core::System::GetInstance().GetCurrentProcessBuildID();
|
||||
u64 build_id{};
|
||||
std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
|
||||
|
||||
Account::ProfileManager profile_manager{};
|
||||
const auto uuid = profile_manager.GetUser(Settings::values.current_user);
|
||||
ASSERT(uuid);
|
||||
params.current_user = uuid->uuid;
|
||||
const auto data =
|
||||
backend->GetLaunchParameter({Core::CurrentProcess()->GetTitleID(), build_id});
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
if (data.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorage>(*data);
|
||||
launch_popped_application_specific = true;
|
||||
return;
|
||||
}
|
||||
} else if (kind == LaunchParameterKind::AccountPreselectedUser &&
|
||||
!launch_popped_account_preselect) {
|
||||
LaunchParameterAccountPreselectedUser params{};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
|
||||
params.is_account_selected = 1;
|
||||
|
||||
std::vector<u8> buffer(sizeof(LaunchParameters));
|
||||
std::memcpy(buffer.data(), ¶ms, buffer.size());
|
||||
Account::ProfileManager profile_manager{};
|
||||
const auto uuid = profile_manager.GetUser(Settings::values.current_user);
|
||||
ASSERT(uuid);
|
||||
params.current_user = uuid->uuid;
|
||||
|
||||
rb.PushIpcInterface<AM::IStorage>(buffer);
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
|
||||
std::memcpy(buffer.data(), ¶ms, buffer.size());
|
||||
|
||||
rb.PushIpcInterface<AM::IStorage>(buffer);
|
||||
launch_popped_account_preselect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_NO_DATA_IN_CHANNEL);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
|
||||
|
||||
@@ -147,6 +147,7 @@ private:
|
||||
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
|
||||
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
Core::System& system;
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
Kernel::EventPair launchable_event;
|
||||
Kernel::EventPair accumulated_suspended_tick_changed_event;
|
||||
@@ -154,8 +155,6 @@ private:
|
||||
u32 idle_time_detection_extension = 0;
|
||||
u64 num_fatal_sections_entered = 0;
|
||||
bool is_auto_sleep_disabled = false;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
|
||||
@@ -255,6 +254,8 @@ private:
|
||||
void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
|
||||
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
bool launch_popped_application_specific = false;
|
||||
bool launch_popped_account_preselect = false;
|
||||
Kernel::EventPair gpu_error_detected_event;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -157,6 +157,10 @@ AppletManager::AppletManager(Core::System& system_) : system{system_} {}
|
||||
|
||||
AppletManager::~AppletManager() = default;
|
||||
|
||||
const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
|
||||
return frontend;
|
||||
}
|
||||
|
||||
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
||||
if (set.parental_controls != nullptr)
|
||||
frontend.parental_controls = std::move(set.parental_controls);
|
||||
|
||||
@@ -190,6 +190,8 @@ public:
|
||||
explicit AppletManager(Core::System& system_);
|
||||
~AppletManager();
|
||||
|
||||
const AppletFrontendSet& GetAppletFrontendSet() const;
|
||||
|
||||
void SetAppletFrontendSet(AppletFrontendSet set);
|
||||
void SetDefaultAppletFrontendSet();
|
||||
void SetDefaultAppletsIfMissing();
|
||||
|
||||
@@ -13,7 +13,7 @@ constexpr PerformanceConfiguration DEFAULT_PERFORMANCE_CONFIGURATION =
|
||||
PerformanceConfiguration::Config7;
|
||||
|
||||
Controller::Controller(Core::Timing::CoreTiming& core_timing)
|
||||
: core_timing(core_timing), configs{
|
||||
: core_timing{core_timing}, configs{
|
||||
{PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
|
||||
{PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
|
||||
} {}
|
||||
@@ -63,6 +63,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa
|
||||
void Controller::SetClockSpeed(u32 mhz) {
|
||||
LOG_INFO(Service_APM, "called, mhz={:08X}", mhz);
|
||||
// TODO(DarkLordZach): Actually signal core_timing to change clock speed.
|
||||
// TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used.
|
||||
}
|
||||
|
||||
} // namespace Service::APM
|
||||
|
||||
@@ -50,7 +50,7 @@ enum class PerformanceMode : u8 {
|
||||
// system during times of high load -- this simply maps to different PerformanceConfigs to use.
|
||||
class Controller {
|
||||
public:
|
||||
Controller(Core::Timing::CoreTiming& core_timing);
|
||||
explicit Controller(Core::Timing::CoreTiming& core_timing);
|
||||
~Controller();
|
||||
|
||||
void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config);
|
||||
@@ -62,9 +62,9 @@ public:
|
||||
private:
|
||||
void SetClockSpeed(u32 mhz);
|
||||
|
||||
std::map<PerformanceMode, PerformanceConfiguration> configs;
|
||||
[[maybe_unused]] Core::Timing::CoreTiming& core_timing;
|
||||
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
std::map<PerformanceMode, PerformanceConfiguration> configs;
|
||||
};
|
||||
|
||||
} // namespace Service::APM
|
||||
|
||||
@@ -205,7 +205,7 @@ private:
|
||||
AudioCore::StreamPtr stream;
|
||||
std::string device_name;
|
||||
|
||||
AudoutParams audio_params{};
|
||||
[[maybe_unused]] AudoutParams audio_params {};
|
||||
|
||||
/// This is the event handle used to check if the audio buffer was released
|
||||
Kernel::EventPair buffer_event;
|
||||
|
||||
136
src/core/hle/service/bcat/backend/backend.cpp
Normal file
136
src/core/hle/service/bcat/backend/backend.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/service/bcat/backend/backend.h"
|
||||
|
||||
namespace Service::BCAT {
|
||||
|
||||
ProgressServiceBackend::ProgressServiceBackend(std::string event_name) : impl{} {
|
||||
auto& kernel{Core::System::GetInstance().Kernel()};
|
||||
event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Automatic, "ProgressServiceBackend:UpdateEvent:" + event_name);
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
|
||||
return event.readable;
|
||||
}
|
||||
|
||||
DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::SetNeedHLELock(bool need) {
|
||||
need_hle_lock = need;
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::SetTotalSize(u64 size) {
|
||||
impl.total_bytes = size;
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::StartConnecting() {
|
||||
impl.status = DeliveryCacheProgressImpl::Status::Connecting;
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::StartProcessingDataList() {
|
||||
impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
|
||||
std::string_view file_name, u64 file_size) {
|
||||
impl.status = DeliveryCacheProgressImpl::Status::Downloading;
|
||||
impl.current_downloaded_bytes = 0;
|
||||
impl.current_total_bytes = file_size;
|
||||
std::memcpy(impl.current_directory.data(), dir_name.data(),
|
||||
std::min<u64>(dir_name.size(), 0x31ull));
|
||||
std::memcpy(impl.current_file.data(), file_name.data(),
|
||||
std::min<u64>(file_name.size(), 0x31ull));
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
|
||||
impl.current_downloaded_bytes = downloaded;
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::FinishDownloadingFile() {
|
||||
impl.total_downloaded_bytes += impl.current_total_bytes;
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
|
||||
impl.status = DeliveryCacheProgressImpl::Status::Committing;
|
||||
impl.current_file.fill(0);
|
||||
impl.current_downloaded_bytes = 0;
|
||||
impl.current_total_bytes = 0;
|
||||
std::memcpy(impl.current_directory.data(), dir_name.data(),
|
||||
std::min<u64>(dir_name.size(), 0x31ull));
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::FinishDownload(ResultCode result) {
|
||||
impl.total_downloaded_bytes = impl.total_bytes;
|
||||
impl.status = DeliveryCacheProgressImpl::Status::Done;
|
||||
impl.result = result;
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::SignalUpdate() const {
|
||||
if (need_hle_lock) {
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
event.writable->Signal();
|
||||
} else {
|
||||
event.writable->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
|
||||
|
||||
Backend::~Backend() = default;
|
||||
|
||||
NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(getter)) {}
|
||||
|
||||
NullBackend::~NullBackend() = default;
|
||||
|
||||
bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
|
||||
title.build_id);
|
||||
|
||||
progress.FinishDownload(RESULT_SUCCESS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
|
||||
ProgressServiceBackend& progress) {
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
|
||||
title.build_id, name);
|
||||
|
||||
progress.FinishDownload(RESULT_SUCCESS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullBackend::Clear(u64 title_id) {
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
|
||||
Common::HexToString(passphrase));
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
|
||||
title.build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Service::BCAT
|
||||
147
src/core/hle/service/bcat/backend/backend.h
Normal file
147
src/core/hle/service/bcat/backend/backend.h
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::BCAT {
|
||||
|
||||
struct DeliveryCacheProgressImpl;
|
||||
|
||||
using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
|
||||
using Passphrase = std::array<u8, 0x20>;
|
||||
|
||||
struct TitleIDVersion {
|
||||
u64 title_id;
|
||||
u64 build_id;
|
||||
};
|
||||
|
||||
using DirectoryName = std::array<char, 0x20>;
|
||||
using FileName = std::array<char, 0x20>;
|
||||
|
||||
struct DeliveryCacheProgressImpl {
|
||||
enum class Status : s32 {
|
||||
None = 0x0,
|
||||
Queued = 0x1,
|
||||
Connecting = 0x2,
|
||||
ProcessingDataList = 0x3,
|
||||
Downloading = 0x4,
|
||||
Committing = 0x5,
|
||||
Done = 0x9,
|
||||
};
|
||||
|
||||
Status status;
|
||||
ResultCode result = RESULT_SUCCESS;
|
||||
DirectoryName current_directory;
|
||||
FileName current_file;
|
||||
s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
|
||||
s64 current_total_bytes; ///< Bytes total on current file.
|
||||
s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
|
||||
s64 total_bytes; ///< Bytes total on overall download.
|
||||
INSERT_PADDING_BYTES(
|
||||
0x198); ///< Appears to be unused in official code, possibly reserved for future use.
|
||||
};
|
||||
static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
|
||||
"DeliveryCacheProgressImpl has incorrect size.");
|
||||
|
||||
// A class to manage the signalling to the game about BCAT download progress.
|
||||
// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
|
||||
class ProgressServiceBackend {
|
||||
friend class IBcatService;
|
||||
|
||||
public:
|
||||
// Clients should call this with true if any of the functions are going to be called from a
|
||||
// non-HLE thread and this class need to lock the hle mutex. (default is false)
|
||||
void SetNeedHLELock(bool need);
|
||||
|
||||
// Sets the number of bytes total in the entire download.
|
||||
void SetTotalSize(u64 size);
|
||||
|
||||
// Notifies the application that the backend has started connecting to the server.
|
||||
void StartConnecting();
|
||||
// Notifies the application that the backend has begun accumulating and processing metadata.
|
||||
void StartProcessingDataList();
|
||||
|
||||
// Notifies the application that a file is starting to be downloaded.
|
||||
void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
|
||||
// Updates the progress of the current file to the size passed.
|
||||
void UpdateFileProgress(u64 downloaded);
|
||||
// Notifies the application that the current file has completed download.
|
||||
void FinishDownloadingFile();
|
||||
|
||||
// Notifies the application that all files in this directory have completed and are being
|
||||
// finalized.
|
||||
void CommitDirectory(std::string_view dir_name);
|
||||
|
||||
// Notifies the application that the operation completed with result code result.
|
||||
void FinishDownload(ResultCode result);
|
||||
|
||||
private:
|
||||
explicit ProgressServiceBackend(std::string event_name);
|
||||
|
||||
Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
|
||||
DeliveryCacheProgressImpl& GetImpl();
|
||||
|
||||
void SignalUpdate() const;
|
||||
|
||||
DeliveryCacheProgressImpl impl;
|
||||
Kernel::EventPair event;
|
||||
bool need_hle_lock = false;
|
||||
};
|
||||
|
||||
// A class representing an abstract backend for BCAT functionality.
|
||||
class Backend {
|
||||
public:
|
||||
explicit Backend(DirectoryGetter getter);
|
||||
virtual ~Backend();
|
||||
|
||||
// Called when the backend is needed to synchronize the data for the game with title ID and
|
||||
// version in title. A ProgressServiceBackend object is provided to alert the application of
|
||||
// status.
|
||||
virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
|
||||
// Very similar to Synchronize, but only for the directory provided. Backends should not alter
|
||||
// the data for any other directories.
|
||||
virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
|
||||
ProgressServiceBackend& progress) = 0;
|
||||
|
||||
// Removes all cached data associated with title id provided.
|
||||
virtual bool Clear(u64 title_id) = 0;
|
||||
|
||||
// Sets the BCAT Passphrase to be used with the associated title ID.
|
||||
virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
|
||||
|
||||
// Gets the launch parameter used by AM associated with the title ID and version provided.
|
||||
virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
|
||||
|
||||
protected:
|
||||
DirectoryGetter dir_getter;
|
||||
};
|
||||
|
||||
// A backend of BCAT that provides no operation.
|
||||
class NullBackend : public Backend {
|
||||
public:
|
||||
explicit NullBackend(const DirectoryGetter& getter);
|
||||
~NullBackend() override;
|
||||
|
||||
bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
|
||||
bool SynchronizeDirectory(TitleIDVersion title, std::string name,
|
||||
ProgressServiceBackend& progress) override;
|
||||
|
||||
bool Clear(u64 title_id) override;
|
||||
|
||||
void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
|
||||
|
||||
std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
|
||||
|
||||
} // namespace Service::BCAT
|
||||
503
src/core/hle/service/bcat/backend/boxcat.cpp
Normal file
503
src/core/hle/service/bcat/backend/boxcat.cpp
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include <httplib.h>
|
||||
#include <json.hpp>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_libzip.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/frontend/applets/error.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/bcat/backend/boxcat.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Prevents conflicts with windows macro called CreateFile
|
||||
FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
|
||||
return dir->CreateFile(name);
|
||||
}
|
||||
|
||||
// Prevents conflicts with windows macro called DeleteFile
|
||||
bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
|
||||
return dir->DeleteFile(name);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
namespace Service::BCAT {
|
||||
|
||||
constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
|
||||
|
||||
constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
|
||||
|
||||
// Formatted using fmt with arg[0] = hex title id
|
||||
constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
|
||||
constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
|
||||
|
||||
constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
|
||||
|
||||
constexpr char BOXCAT_API_VERSION[] = "1";
|
||||
constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
|
||||
|
||||
// HTTP status codes for Boxcat
|
||||
enum class ResponseStatus {
|
||||
Ok = 200, ///< Operation completed successfully.
|
||||
BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
|
||||
NoUpdate = 304, ///< The digest provided would match the new data, no need to update.
|
||||
NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation.
|
||||
NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format
|
||||
///< issues or whatnot) and has no data.
|
||||
};
|
||||
|
||||
enum class DownloadResult {
|
||||
Success = 0,
|
||||
NoResponse,
|
||||
GeneralWebError,
|
||||
NoMatchTitleId,
|
||||
NoMatchBuildId,
|
||||
InvalidContentType,
|
||||
GeneralFSError,
|
||||
BadClientVersion,
|
||||
};
|
||||
|
||||
constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{
|
||||
"Success",
|
||||
"There was no response from the server.",
|
||||
"There was a general web error code returned from the server.",
|
||||
"The title ID of the current game doesn't have a boxcat implementation. If you believe an "
|
||||
"implementation should be added, contact yuzu support.",
|
||||
"The build ID of the current version of the game is marked as incompatible with the current "
|
||||
"BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.",
|
||||
"The content type of the web response was invalid.",
|
||||
"There was a general filesystem error while saving the zip file.",
|
||||
"The server is either too new or too old to serve the request. Try using the latest version of "
|
||||
"an official release of yuzu.",
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, DownloadResult result) {
|
||||
return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result));
|
||||
}
|
||||
|
||||
constexpr u32 PORT = 443;
|
||||
constexpr u32 TIMEOUT_SECONDS = 30;
|
||||
[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetBINFilePath(u64 title_id) {
|
||||
return fmt::format("{}bcat/{:016X}/launchparam.bin",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
|
||||
}
|
||||
|
||||
std::string GetZIPFilePath(u64 title_id) {
|
||||
return fmt::format("{}bcat/{:016X}/data.zip",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
|
||||
}
|
||||
|
||||
// If the error is something the user should know about (build ID mismatch, bad client version),
|
||||
// display an error.
|
||||
void HandleDownloadDisplayResult(DownloadResult res) {
|
||||
if (res == DownloadResult::Success || res == DownloadResult::NoResponse ||
|
||||
res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError ||
|
||||
res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()};
|
||||
frontend.error->ShowCustomErrorText(
|
||||
ResultCode(-1), "There was an error while attempting to use Boxcat.",
|
||||
DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
|
||||
}
|
||||
|
||||
bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
|
||||
std::string_view dir_name, ProgressServiceBackend& progress,
|
||||
std::size_t block_size = 0x1000) {
|
||||
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
return false;
|
||||
if (!dest->Resize(src->GetSize()))
|
||||
return false;
|
||||
|
||||
progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
|
||||
|
||||
std::vector<u8> temp(std::min(block_size, src->GetSize()));
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
|
||||
const auto read = std::min(block_size, src->GetSize() - i);
|
||||
|
||||
if (src->Read(temp.data(), read, i) != read) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest->Write(temp.data(), read, i) != read) {
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.UpdateFileProgress(i);
|
||||
}
|
||||
|
||||
progress.FinishDownloadingFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
|
||||
ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
|
||||
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
return false;
|
||||
|
||||
for (const auto& file : src->GetFiles()) {
|
||||
const auto out_file = VfsCreateFileWrap(dest, file->GetName());
|
||||
if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
progress.CommitDirectory(src->GetName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
|
||||
ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
|
||||
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
return false;
|
||||
|
||||
for (const auto& dir : src->GetSubdirectories()) {
|
||||
const auto out = dest->CreateSubdirectory(dir->GetName());
|
||||
if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
class Boxcat::Client {
|
||||
public:
|
||||
Client(std::string path, u64 title_id, u64 build_id)
|
||||
: path(std::move(path)), title_id(title_id), build_id(build_id) {}
|
||||
|
||||
DownloadResult DownloadDataZip() {
|
||||
return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
|
||||
"application/zip");
|
||||
}
|
||||
|
||||
DownloadResult DownloadLaunchParam() {
|
||||
return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
|
||||
TIMEOUT_SECONDS / 3, "application/octet-stream");
|
||||
}
|
||||
|
||||
private:
|
||||
DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
|
||||
const std::string& content_type_name) {
|
||||
if (client == nullptr) {
|
||||
client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
|
||||
}
|
||||
|
||||
httplib::Headers headers{
|
||||
{std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
|
||||
{std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
|
||||
{std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
|
||||
};
|
||||
|
||||
if (FileUtil::Exists(path)) {
|
||||
FileUtil::IOFile file{path, "rb"};
|
||||
if (file.IsOpen()) {
|
||||
std::vector<u8> bytes(file.GetSize());
|
||||
file.ReadBytes(bytes.data(), bytes.size());
|
||||
const auto digest = DigestFile(bytes);
|
||||
headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
|
||||
}
|
||||
}
|
||||
|
||||
const auto response = client->Get(resolved_path.c_str(), headers);
|
||||
if (response == nullptr)
|
||||
return DownloadResult::NoResponse;
|
||||
|
||||
if (response->status == static_cast<int>(ResponseStatus::NoUpdate))
|
||||
return DownloadResult::Success;
|
||||
if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
|
||||
return DownloadResult::BadClientVersion;
|
||||
if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId))
|
||||
return DownloadResult::NoMatchTitleId;
|
||||
if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
|
||||
return DownloadResult::NoMatchBuildId;
|
||||
if (response->status != static_cast<int>(ResponseStatus::Ok))
|
||||
return DownloadResult::GeneralWebError;
|
||||
|
||||
const auto content_type = response->headers.find("content-type");
|
||||
if (content_type == response->headers.end() ||
|
||||
content_type->second.find(content_type_name) == std::string::npos) {
|
||||
return DownloadResult::InvalidContentType;
|
||||
}
|
||||
|
||||
FileUtil::CreateFullPath(path);
|
||||
FileUtil::IOFile file{path, "wb"};
|
||||
if (!file.IsOpen())
|
||||
return DownloadResult::GeneralFSError;
|
||||
if (!file.Resize(response->body.size()))
|
||||
return DownloadResult::GeneralFSError;
|
||||
if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size())
|
||||
return DownloadResult::GeneralFSError;
|
||||
|
||||
return DownloadResult::Success;
|
||||
}
|
||||
|
||||
using Digest = std::array<u8, 0x20>;
|
||||
static Digest DigestFile(std::vector<u8> bytes) {
|
||||
Digest out{};
|
||||
mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::unique_ptr<httplib::Client> client;
|
||||
std::string path;
|
||||
u64 title_id;
|
||||
u64 build_id;
|
||||
};
|
||||
|
||||
Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
|
||||
|
||||
Boxcat::~Boxcat() = default;
|
||||
|
||||
void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
|
||||
ProgressServiceBackend& progress,
|
||||
std::optional<std::string> dir_name = {}) {
|
||||
progress.SetNeedHLELock(true);
|
||||
|
||||
if (Settings::values.bcat_boxcat_local) {
|
||||
LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
|
||||
const auto dir = dir_getter(title.title_id);
|
||||
if (dir)
|
||||
progress.SetTotalSize(dir->GetSize());
|
||||
progress.FinishDownload(RESULT_SUCCESS);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto zip_path{GetZIPFilePath(title.title_id)};
|
||||
Boxcat::Client client{zip_path, title.title_id, title.build_id};
|
||||
|
||||
progress.StartConnecting();
|
||||
|
||||
const auto res = client.DownloadDataZip();
|
||||
if (res != DownloadResult::Success) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
|
||||
|
||||
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
|
||||
FileUtil::Delete(zip_path);
|
||||
}
|
||||
|
||||
HandleDownloadDisplayResult(res);
|
||||
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
progress.StartProcessingDataList();
|
||||
|
||||
FileUtil::IOFile zip{zip_path, "rb"};
|
||||
const auto size = zip.GetSize();
|
||||
std::vector<u8> bytes(size);
|
||||
if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
|
||||
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
|
||||
if (extracted == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
|
||||
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir_name == std::nullopt) {
|
||||
progress.SetTotalSize(extracted->GetSize());
|
||||
|
||||
const auto target_dir = dir_getter(title.title_id);
|
||||
if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
|
||||
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const auto target_dir = dir_getter(title.title_id);
|
||||
if (target_dir == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
|
||||
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto target_sub = target_dir->GetSubdirectory(*dir_name);
|
||||
const auto source_sub = extracted->GetSubdirectory(*dir_name);
|
||||
|
||||
progress.SetTotalSize(source_sub->GetSize());
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
{
|
||||
const auto files = target_sub->GetFiles();
|
||||
std::transform(files.begin(), files.end(), std::back_inserter(filenames),
|
||||
[](const auto& vfile) { return vfile->GetName(); });
|
||||
}
|
||||
|
||||
for (const auto& filename : filenames) {
|
||||
VfsDeleteFileWrap(target_sub, filename);
|
||||
}
|
||||
|
||||
if (target_sub == nullptr || source_sub == nullptr ||
|
||||
!VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
|
||||
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
progress.FinishDownload(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
|
||||
is_syncing.exchange(true);
|
||||
std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
|
||||
.detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
|
||||
ProgressServiceBackend& progress) {
|
||||
is_syncing.exchange(true);
|
||||
std::thread(
|
||||
[this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
|
||||
.detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Boxcat::Clear(u64 title_id) {
|
||||
if (Settings::values.bcat_boxcat_local) {
|
||||
LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear.");
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto dir = dir_getter(title_id);
|
||||
|
||||
std::vector<std::string> dirnames;
|
||||
|
||||
for (const auto& subdir : dir->GetSubdirectories())
|
||||
dirnames.push_back(subdir->GetName());
|
||||
|
||||
for (const auto& subdir : dirnames) {
|
||||
if (!dir->DeleteSubdirectoryRecursive(subdir))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
|
||||
Common::HexToString(passphrase));
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
|
||||
const auto path{GetBINFilePath(title.title_id)};
|
||||
|
||||
if (Settings::values.bcat_boxcat_local) {
|
||||
LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
|
||||
} else {
|
||||
Boxcat::Client client{path, title.title_id, title.build_id};
|
||||
|
||||
const auto res = client.DownloadLaunchParam();
|
||||
if (res != DownloadResult::Success) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
|
||||
|
||||
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
|
||||
FileUtil::Delete(path);
|
||||
}
|
||||
|
||||
HandleDownloadDisplayResult(res);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
FileUtil::IOFile bin{path, "rb"};
|
||||
const auto size = bin.GetSize();
|
||||
std::vector<u8> bytes(size);
|
||||
if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
|
||||
LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
|
||||
path);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
|
||||
std::map<std::string, EventStatus>& games) {
|
||||
httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT),
|
||||
static_cast<int>(TIMEOUT_SECONDS)};
|
||||
|
||||
httplib::Headers headers{
|
||||
{std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
|
||||
{std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
|
||||
};
|
||||
|
||||
const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
|
||||
if (response == nullptr)
|
||||
return StatusResult::Offline;
|
||||
|
||||
if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
|
||||
return StatusResult::BadClientVersion;
|
||||
|
||||
try {
|
||||
nlohmann::json json = nlohmann::json::parse(response->body);
|
||||
|
||||
if (!json["online"].get<bool>())
|
||||
return StatusResult::Offline;
|
||||
|
||||
if (json["global"].is_null())
|
||||
global = std::nullopt;
|
||||
else
|
||||
global = json["global"].get<std::string>();
|
||||
|
||||
if (json["games"].is_array()) {
|
||||
for (const auto object : json["games"]) {
|
||||
if (object.is_object() && object.find("name") != object.end()) {
|
||||
EventStatus detail{};
|
||||
if (object["header"].is_string()) {
|
||||
detail.header = object["header"].get<std::string>();
|
||||
} else {
|
||||
detail.header = std::nullopt;
|
||||
}
|
||||
|
||||
if (object["footer"].is_string()) {
|
||||
detail.footer = object["footer"].get<std::string>();
|
||||
} else {
|
||||
detail.footer = std::nullopt;
|
||||
}
|
||||
|
||||
if (object["events"].is_array()) {
|
||||
for (const auto& event : object["events"]) {
|
||||
if (!event.is_string())
|
||||
continue;
|
||||
detail.events.push_back(event.get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
games.insert_or_assign(object["name"], std::move(detail));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StatusResult::Success;
|
||||
} catch (const nlohmann::json::parse_error& e) {
|
||||
return StatusResult::ParseError;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::BCAT
|
||||
58
src/core/hle/service/bcat/backend/boxcat.h
Normal file
58
src/core/hle/service/bcat/backend/boxcat.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include "core/hle/service/bcat/backend/backend.h"
|
||||
|
||||
namespace Service::BCAT {
|
||||
|
||||
struct EventStatus {
|
||||
std::optional<std::string> header;
|
||||
std::optional<std::string> footer;
|
||||
std::vector<std::string> events;
|
||||
};
|
||||
|
||||
/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and
|
||||
/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team.
|
||||
class Boxcat final : public Backend {
|
||||
friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
|
||||
ProgressServiceBackend& progress,
|
||||
std::optional<std::string> dir_name);
|
||||
|
||||
public:
|
||||
explicit Boxcat(DirectoryGetter getter);
|
||||
~Boxcat() override;
|
||||
|
||||
bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
|
||||
bool SynchronizeDirectory(TitleIDVersion title, std::string name,
|
||||
ProgressServiceBackend& progress) override;
|
||||
|
||||
bool Clear(u64 title_id) override;
|
||||
|
||||
void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
|
||||
|
||||
std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
|
||||
|
||||
enum class StatusResult {
|
||||
Success,
|
||||
Offline,
|
||||
ParseError,
|
||||
BadClientVersion,
|
||||
};
|
||||
|
||||
static StatusResult GetStatus(std::optional<std::string>& global,
|
||||
std::map<std::string, EventStatus>& games);
|
||||
|
||||
private:
|
||||
std::atomic_bool is_syncing{false};
|
||||
|
||||
class Client;
|
||||
std::unique_ptr<Client> client;
|
||||
};
|
||||
|
||||
} // namespace Service::BCAT
|
||||
@@ -6,11 +6,15 @@
|
||||
|
||||
namespace Service::BCAT {
|
||||
|
||||
BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
|
||||
: Module::Interface(std::move(module), name) {
|
||||
BCAT::BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, const char* name)
|
||||
: Module::Interface(std::move(module), fsc, name) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &BCAT::CreateBcatService, "CreateBcatService"},
|
||||
{1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
|
||||
{2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Service::BCAT {
|
||||
|
||||
class BCAT final : public Module::Interface {
|
||||
public:
|
||||
explicit BCAT(std::shared_ptr<Module> module, const char* name);
|
||||
explicit BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
|
||||
const char* name);
|
||||
~BCAT() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,34 +2,254 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cctype>
|
||||
#include <mbedtls/md5.h>
|
||||
#include "backend/boxcat.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/service/bcat/backend/backend.h"
|
||||
#include "core/hle/service/bcat/bcat.h"
|
||||
#include "core/hle/service/bcat/module.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::BCAT {
|
||||
|
||||
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
|
||||
constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
|
||||
constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
|
||||
constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
|
||||
|
||||
// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
|
||||
// and if any of them have a non-zero result it just forwards that result. This is the FS error code
|
||||
// for permission denied, which is the closest approximation of this scenario.
|
||||
constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
|
||||
|
||||
using BCATDigest = std::array<u8, 0x10>;
|
||||
|
||||
namespace {
|
||||
|
||||
u64 GetCurrentBuildID() {
|
||||
const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID();
|
||||
u64 out{};
|
||||
std::memcpy(&out, id.data(), sizeof(u64));
|
||||
return out;
|
||||
}
|
||||
|
||||
// The digest is only used to determine if a file is unique compared to others of the same name.
|
||||
// Since the algorithm isn't ever checked in game, MD5 is safe.
|
||||
BCATDigest DigestFile(const FileSys::VirtualFile& file) {
|
||||
BCATDigest out{};
|
||||
const auto bytes = file->ReadAllBytes();
|
||||
mbedtls_md5(bytes.data(), bytes.size(), out.data());
|
||||
return out;
|
||||
}
|
||||
|
||||
// For a name to be valid it must be non-empty, must have a null terminating character as the final
|
||||
// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
|
||||
// file.
|
||||
bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
|
||||
char match_char) {
|
||||
const auto null_chars = std::count(name.begin(), name.end(), 0);
|
||||
const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
|
||||
return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
|
||||
});
|
||||
if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
|
||||
LOG_ERROR(Service_BCAT, "Name passed was invalid!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
|
||||
return VerifyNameValidInternal(ctx, name, '-');
|
||||
}
|
||||
|
||||
bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
|
||||
return VerifyNameValidInternal(ctx, name, '.');
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
struct DeliveryCacheDirectoryEntry {
|
||||
FileName name;
|
||||
u64 size;
|
||||
BCATDigest digest;
|
||||
};
|
||||
|
||||
class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
|
||||
public:
|
||||
IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event,
|
||||
const DeliveryCacheProgressImpl& impl)
|
||||
: ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
|
||||
{1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetEvent(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(event);
|
||||
}
|
||||
|
||||
void GetImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::ReadableEvent> event;
|
||||
const DeliveryCacheProgressImpl& impl;
|
||||
};
|
||||
|
||||
class IBcatService final : public ServiceFramework<IBcatService> {
|
||||
public:
|
||||
IBcatService() : ServiceFramework("IBcatService") {
|
||||
IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{10100, nullptr, "RequestSyncDeliveryCache"},
|
||||
{10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"},
|
||||
{10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
|
||||
{10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
|
||||
{10200, nullptr, "CancelSyncDeliveryCacheRequest"},
|
||||
{20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
|
||||
{20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
|
||||
{30100, nullptr, "SetPassphrase"},
|
||||
{30100, &IBcatService::SetPassphrase, "SetPassphrase"},
|
||||
{30200, nullptr, "RegisterBackgroundDeliveryTask"},
|
||||
{30201, nullptr, "UnregisterBackgroundDeliveryTask"},
|
||||
{30202, nullptr, "BlockDeliveryTask"},
|
||||
{30203, nullptr, "UnblockDeliveryTask"},
|
||||
{90100, nullptr, "EnumerateBackgroundDeliveryTask"},
|
||||
{90200, nullptr, "GetDeliveryList"},
|
||||
{90201, nullptr, "ClearDeliveryCacheStorage"},
|
||||
{90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
|
||||
{90300, nullptr, "GetPushNotificationLog"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class SyncType {
|
||||
Normal,
|
||||
Directory,
|
||||
Count,
|
||||
};
|
||||
|
||||
std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
|
||||
auto& backend{progress.at(static_cast<std::size_t>(type))};
|
||||
return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
|
||||
backend.GetImpl());
|
||||
}
|
||||
|
||||
void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
|
||||
progress.at(static_cast<std::size_t>(SyncType::Normal)));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
|
||||
}
|
||||
|
||||
void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto name_raw = rp.PopRaw<DirectoryName>();
|
||||
const auto name =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, name={}", name);
|
||||
|
||||
backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
|
||||
name,
|
||||
progress.at(static_cast<std::size_t>(SyncType::Directory)));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
|
||||
}
|
||||
|
||||
void SetPassphrase(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
const auto passphrase_raw = ctx.ReadBuffer();
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
|
||||
Common::HexToString(passphrase_raw));
|
||||
|
||||
if (title_id == 0) {
|
||||
LOG_ERROR(Service_BCAT, "Invalid title ID!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (passphrase_raw.size() > 0x40) {
|
||||
LOG_ERROR(Service_BCAT, "Passphrase too large!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
Passphrase passphrase{};
|
||||
std::memcpy(passphrase.data(), passphrase_raw.data(),
|
||||
std::min(passphrase.size(), passphrase_raw.size()));
|
||||
|
||||
backend.SetPassphrase(title_id, passphrase);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
|
||||
|
||||
if (title_id == 0) {
|
||||
LOG_ERROR(Service_BCAT, "Invalid title ID!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!backend.Clear(title_id)) {
|
||||
LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_FAILED_CLEAR_CACHE);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
Backend& backend;
|
||||
|
||||
std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
|
||||
ProgressServiceBackend{"Normal"},
|
||||
ProgressServiceBackend{"Directory"},
|
||||
};
|
||||
};
|
||||
|
||||
void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
|
||||
@@ -37,20 +257,331 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IBcatService>();
|
||||
rb.PushIpcInterface<IBcatService>(*backend);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
|
||||
: ServiceFramework(name), module(std::move(module)) {}
|
||||
class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
|
||||
public:
|
||||
IDeliveryCacheFileService(FileSys::VirtualDir root_)
|
||||
: ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDeliveryCacheFileService::Open, "Open"},
|
||||
{1, &IDeliveryCacheFileService::Read, "Read"},
|
||||
{2, &IDeliveryCacheFileService::GetSize, "GetSize"},
|
||||
{3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void Open(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto dir_name_raw = rp.PopRaw<DirectoryName>();
|
||||
const auto file_name_raw = rp.PopRaw<FileName>();
|
||||
|
||||
const auto dir_name =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
|
||||
const auto file_name =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
|
||||
|
||||
if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
|
||||
return;
|
||||
|
||||
if (current_file != nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_ENTITY_ALREADY_OPEN);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dir = root->GetSubdirectory(dir_name);
|
||||
|
||||
if (dir == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_FAILED_OPEN_ENTITY);
|
||||
return;
|
||||
}
|
||||
|
||||
current_file = dir->GetFile(file_name);
|
||||
|
||||
if (current_file == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_FAILED_OPEN_ENTITY);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto offset{rp.PopRaw<u64>()};
|
||||
|
||||
auto size = ctx.GetWriteBufferSize();
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
|
||||
|
||||
if (current_file == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "There is no file currently open!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NO_OPEN_ENTITY);
|
||||
}
|
||||
|
||||
size = std::min<u64>(current_file->GetSize() - offset, size);
|
||||
const auto buffer = current_file->ReadBytes(size, offset);
|
||||
ctx.WriteBuffer(buffer);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(buffer.size());
|
||||
}
|
||||
|
||||
void GetSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
if (current_file == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "There is no file currently open!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NO_OPEN_ENTITY);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(current_file->GetSize());
|
||||
}
|
||||
|
||||
void GetDigest(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
if (current_file == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "There is no file currently open!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NO_OPEN_ENTITY);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(DigestFile(current_file));
|
||||
}
|
||||
|
||||
FileSys::VirtualDir root;
|
||||
FileSys::VirtualFile current_file;
|
||||
};
|
||||
|
||||
class IDeliveryCacheDirectoryService final
|
||||
: public ServiceFramework<IDeliveryCacheDirectoryService> {
|
||||
public:
|
||||
IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
|
||||
: ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDeliveryCacheDirectoryService::Open, "Open"},
|
||||
{1, &IDeliveryCacheDirectoryService::Read, "Read"},
|
||||
{2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void Open(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto name_raw = rp.PopRaw<DirectoryName>();
|
||||
const auto name =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, name={}", name);
|
||||
|
||||
if (!VerifyNameValidDir(ctx, name_raw))
|
||||
return;
|
||||
|
||||
if (current_dir != nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_ENTITY_ALREADY_OPEN);
|
||||
return;
|
||||
}
|
||||
|
||||
current_dir = root->GetSubdirectory(name);
|
||||
|
||||
if (current_dir == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_FAILED_OPEN_ENTITY);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
|
||||
|
||||
if (current_dir == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "There is no open directory!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NO_OPEN_ENTITY);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto files = current_dir->GetFiles();
|
||||
write_size = std::min<u64>(write_size, files.size());
|
||||
std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
|
||||
std::transform(
|
||||
files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
|
||||
FileName name{};
|
||||
std::memcpy(name.data(), file->GetName().data(),
|
||||
std::min(file->GetName().size(), name.size()));
|
||||
return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
|
||||
});
|
||||
|
||||
ctx.WriteBuffer(entries);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry));
|
||||
}
|
||||
|
||||
void GetCount(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
if (current_dir == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "There is no open directory!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NO_OPEN_ENTITY);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto files = current_dir->GetFiles();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(files.size());
|
||||
}
|
||||
|
||||
FileSys::VirtualDir root;
|
||||
FileSys::VirtualDir current_dir;
|
||||
};
|
||||
|
||||
class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
|
||||
public:
|
||||
IDeliveryCacheStorageService(FileSys::VirtualDir root_)
|
||||
: ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
|
||||
{1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
|
||||
{10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
for (const auto& subdir : root->GetSubdirectories()) {
|
||||
DirectoryName name{};
|
||||
std::memcpy(name.data(), subdir->GetName().data(),
|
||||
std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
|
||||
entries.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void CreateFileService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDeliveryCacheFileService>(root);
|
||||
}
|
||||
|
||||
void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
|
||||
}
|
||||
|
||||
void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
|
||||
auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
|
||||
|
||||
size = std::min<u64>(size, entries.size() - next_read_index);
|
||||
ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
|
||||
next_read_index += size;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(size);
|
||||
}
|
||||
|
||||
FileSys::VirtualDir root;
|
||||
std::vector<DirectoryName> entries;
|
||||
u64 next_read_index = 0;
|
||||
};
|
||||
|
||||
void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_BCAT, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDeliveryCacheStorageService>(
|
||||
fsc.GetBCATDirectory(Core::CurrentProcess()->GetTitleID()));
|
||||
}
|
||||
|
||||
void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
|
||||
Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
|
||||
}
|
||||
|
||||
std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
|
||||
const auto backend = Settings::values.bcat_backend;
|
||||
|
||||
#ifdef YUZU_ENABLE_BOXCAT
|
||||
if (backend == "boxcat")
|
||||
return std::make_unique<Boxcat>(std::move(getter));
|
||||
#endif
|
||||
|
||||
return std::make_unique<NullBackend>(std::move(getter));
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
|
||||
const char* name)
|
||||
: ServiceFramework(name), fsc(fsc), module(std::move(module)),
|
||||
backend(CreateBackendFromSettings([&fsc](u64 tid) { return fsc.GetBCATDirectory(tid); })) {}
|
||||
|
||||
Module::Interface::~Interface() = default;
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
auto module = std::make_shared<Module>();
|
||||
std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager);
|
||||
std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager);
|
||||
std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager);
|
||||
std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager);
|
||||
std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:a")
|
||||
->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:m")
|
||||
->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:u")
|
||||
->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:s")
|
||||
->InstallAsService(system.ServiceManager());
|
||||
}
|
||||
|
||||
} // namespace Service::BCAT
|
||||
|
||||
@@ -6,23 +6,39 @@
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::BCAT {
|
||||
namespace Service {
|
||||
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
} // namespace FileSystem
|
||||
|
||||
namespace BCAT {
|
||||
|
||||
class Backend;
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
public:
|
||||
explicit Interface(std::shared_ptr<Module> module, const char* name);
|
||||
explicit Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
|
||||
const char* name);
|
||||
~Interface() override;
|
||||
|
||||
void CreateBcatService(Kernel::HLERequestContext& ctx);
|
||||
void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
|
||||
void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
FileSystem::FileSystemController& fsc;
|
||||
|
||||
std::shared_ptr<Module> module;
|
||||
std::unique_ptr<Backend> backend;
|
||||
};
|
||||
};
|
||||
|
||||
/// Registers all BCAT services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
void InstallInterfaces(Core::System& system);
|
||||
|
||||
} // namespace Service::BCAT
|
||||
} // namespace BCAT
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id)
|
||||
return bis_factory->GetModificationDumpRoot(title_id);
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
|
||||
LOG_TRACE(Service_FS, "Opening BCAT root for tid={:016X}", title_id);
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetBCATDirectory(title_id);
|
||||
}
|
||||
|
||||
void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (overwrite) {
|
||||
bis_factory = nullptr;
|
||||
|
||||
@@ -110,6 +110,8 @@ public:
|
||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
|
||||
|
||||
FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
|
||||
|
||||
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
|
||||
// above is called.
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||
|
||||
@@ -803,7 +803,7 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
|
||||
auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
|
||||
[[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
|
||||
u128 uid = rp.PopRaw<u128>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
|
||||
|
||||
@@ -237,7 +237,6 @@ private:
|
||||
};
|
||||
|
||||
Common::UUID uuid;
|
||||
bool is_event_created = false;
|
||||
Kernel::EventPair notification_event;
|
||||
std::queue<SizedNotificationInfo> notifications;
|
||||
States states{};
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
namespace Service::HID {
|
||||
|
||||
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
|
||||
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
|
||||
[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
|
||||
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
|
||||
|
||||
Controller_DebugPad::Controller_DebugPad(Core::System& system)
|
||||
: ControllerBase(system), system(system) {}
|
||||
Controller_DebugPad::Controller_DebugPad(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_DebugPad::~Controller_DebugPad() = default;
|
||||
|
||||
void Controller_DebugPad::OnInit() {}
|
||||
|
||||
@@ -89,6 +89,5 @@ private:
|
||||
buttons;
|
||||
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>
|
||||
analogs;
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
|
||||
|
||||
Controller_Gesture::Controller_Gesture(Core::System& system)
|
||||
: ControllerBase(system), system(system) {}
|
||||
Controller_Gesture::Controller_Gesture(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_Gesture::~Controller_Gesture() = default;
|
||||
|
||||
void Controller_Gesture::OnInit() {}
|
||||
|
||||
@@ -59,6 +59,5 @@ private:
|
||||
std::array<GestureState, 17> gesture_states;
|
||||
};
|
||||
SharedMemory shared_memory{};
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -12,8 +12,7 @@ namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
|
||||
constexpr u8 KEYS_PER_BYTE = 8;
|
||||
|
||||
Controller_Keyboard::Controller_Keyboard(Core::System& system)
|
||||
: ControllerBase(system), system(system) {}
|
||||
Controller_Keyboard::Controller_Keyboard(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_Keyboard::~Controller_Keyboard() = default;
|
||||
|
||||
void Controller_Keyboard::OnInit() {}
|
||||
|
||||
@@ -53,6 +53,5 @@ private:
|
||||
keyboard_keys;
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods>
|
||||
keyboard_mods;
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
|
||||
|
||||
Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system), system(system) {}
|
||||
Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_Mouse::~Controller_Mouse() = default;
|
||||
|
||||
void Controller_Mouse::OnInit() {}
|
||||
|
||||
@@ -53,6 +53,5 @@ private:
|
||||
std::unique_ptr<Input::MouseDevice> mouse_device;
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons>
|
||||
mouse_button_devices;
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
|
||||
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
|
||||
[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
|
||||
constexpr std::size_t NPAD_OFFSET = 0x9A00;
|
||||
constexpr u32 BATTERY_FULL = 2;
|
||||
constexpr u32 MAX_NPAD_ID = 7;
|
||||
@@ -105,6 +105,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
|
||||
controller.joy_styles.raw = 0; // Zero out
|
||||
controller.device_type.raw = 0;
|
||||
switch (controller_type) {
|
||||
case NPadControllerType::None:
|
||||
UNREACHABLE();
|
||||
case NPadControllerType::Handheld:
|
||||
controller.joy_styles.handheld.Assign(1);
|
||||
controller.device_type.handheld.Assign(1);
|
||||
@@ -239,7 +241,7 @@ void Controller_NPad::OnRelease() {}
|
||||
|
||||
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
|
||||
const auto controller_idx = NPadIdToIndex(npad_id);
|
||||
const auto controller_type = connected_controllers[controller_idx].type;
|
||||
[[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type;
|
||||
if (!connected_controllers[controller_idx].is_connected) {
|
||||
return;
|
||||
}
|
||||
@@ -346,6 +348,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
|
||||
libnx_entry.connection_status.raw = 0;
|
||||
|
||||
switch (controller_type) {
|
||||
case NPadControllerType::None:
|
||||
UNREACHABLE();
|
||||
case NPadControllerType::Handheld:
|
||||
handheld_entry.connection_status.raw = 0;
|
||||
handheld_entry.connection_status.IsWired.Assign(1);
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
Controller_Stubbed::Controller_Stubbed(Core::System& system)
|
||||
: ControllerBase(system), system(system) {}
|
||||
Controller_Stubbed::Controller_Stubbed(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_Stubbed::~Controller_Stubbed() = default;
|
||||
|
||||
void Controller_Stubbed::OnInit() {}
|
||||
|
||||
@@ -30,6 +30,5 @@ public:
|
||||
private:
|
||||
bool smart_update{};
|
||||
std::size_t common_offset{};
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
|
||||
|
||||
Controller_Touchscreen::Controller_Touchscreen(Core::System& system)
|
||||
: ControllerBase(system), system(system) {}
|
||||
Controller_Touchscreen::Controller_Touchscreen(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_Touchscreen::~Controller_Touchscreen() = default;
|
||||
|
||||
void Controller_Touchscreen::OnInit() {}
|
||||
|
||||
@@ -69,6 +69,5 @@ private:
|
||||
TouchScreenSharedMemory shared_memory{};
|
||||
std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
s64_le last_touch{};
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
|
||||
|
||||
Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system), system(system) {}
|
||||
Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system) {}
|
||||
Controller_XPad::~Controller_XPad() = default;
|
||||
|
||||
void Controller_XPad::OnInit() {}
|
||||
|
||||
@@ -56,6 +56,5 @@ private:
|
||||
};
|
||||
static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
|
||||
SharedMemory shared_memory{};
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
||||
@@ -38,8 +38,10 @@ namespace Service::HID {
|
||||
// Updating period for each HID device.
|
||||
// TODO(ogniK): Find actual polling rate of hid
|
||||
constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66);
|
||||
constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
|
||||
constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
|
||||
[[maybe_unused]] constexpr s64 accelerometer_update_ticks =
|
||||
static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
|
||||
[[maybe_unused]] constexpr s64 gyroscope_update_ticks =
|
||||
static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
|
||||
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
|
||||
|
||||
IAppletResource::IAppletResource(Core::System& system)
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
namespace Service::NFP {
|
||||
|
||||
namespace ErrCodes {
|
||||
constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
|
||||
-1); // TODO(ogniK): Find the actual error code
|
||||
[[maybe_unused]] constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
|
||||
-1); // TODO(ogniK): Find the actual error code
|
||||
constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
|
||||
} // namespace ErrCodes
|
||||
|
||||
@@ -35,7 +35,7 @@ Module::Interface::~Interface() = default;
|
||||
class IUser final : public ServiceFramework<IUser> {
|
||||
public:
|
||||
IUser(Module::Interface& nfp_interface, Core::System& system)
|
||||
: ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface), system(system) {
|
||||
: ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IUser::Initialize, "Initialize"},
|
||||
{1, &IUser::Finalize, "Finalize"},
|
||||
@@ -183,6 +183,8 @@ private:
|
||||
case DeviceState::TagRemoved:
|
||||
device_state = DeviceState::Initialized;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -324,7 +326,6 @@ private:
|
||||
Kernel::EventPair deactivate_event;
|
||||
Kernel::EventPair availability_change_event;
|
||||
const Module::Interface& nfp_interface;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -12,6 +12,13 @@
|
||||
|
||||
namespace Service::NIFM {
|
||||
|
||||
enum class RequestState : u32 {
|
||||
NotSubmitted = 1,
|
||||
Error = 1, ///< The duplicate 1 is intentional; it means both not submitted and error on HW.
|
||||
Pending = 2,
|
||||
Connected = 3,
|
||||
};
|
||||
|
||||
class IScanRequest final : public ServiceFramework<IScanRequest> {
|
||||
public:
|
||||
explicit IScanRequest() : ServiceFramework("IScanRequest") {
|
||||
@@ -81,7 +88,7 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0);
|
||||
rb.PushEnum(RequestState::Connected);
|
||||
}
|
||||
|
||||
void GetResult(Kernel::HLERequestContext& ctx) {
|
||||
@@ -189,14 +196,14 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
rb.Push<u8>(1);
|
||||
}
|
||||
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
rb.Push<u8>(1);
|
||||
}
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -45,6 +45,8 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std:
|
||||
return GetVARegions(input, output);
|
||||
case IoctlCommand::IocUnmapBufferCommand:
|
||||
return UnmapBuffer(input, output);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand)
|
||||
|
||||
@@ -38,9 +38,10 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::v
|
||||
return IocCtrlEventUnregister(input, output);
|
||||
case IoctlCommand::IocCtrlEventSignalCommand:
|
||||
return IocCtrlEventSignal(input, output);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
return 0;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
|
||||
@@ -40,9 +40,10 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
|
||||
return FlushL2(input, output);
|
||||
case IoctlCommand::IocGetGpuTime:
|
||||
return GetGpuTime(input, output);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
return 0;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
|
||||
|
||||
@@ -44,6 +44,8 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
|
||||
return GetWaitbase(input, output);
|
||||
case IoctlCommand::IocChannelSetTimeoutCommand:
|
||||
return ChannelSetTimeout(input, output);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (command.group == NVGPU_IOCTL_MAGIC) {
|
||||
|
||||
@@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
|
||||
AOC::InstallInterfaces(*sm, system);
|
||||
APM::InstallInterfaces(system);
|
||||
Audio::InstallInterfaces(*sm, system);
|
||||
BCAT::InstallInterfaces(*sm);
|
||||
BCAT::InstallInterfaces(system);
|
||||
BPC::InstallInterfaces(*sm);
|
||||
BtDrv::InstallInterfaces(*sm, system);
|
||||
BTM::InstallInterfaces(*sm, system);
|
||||
|
||||
@@ -150,6 +150,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
// Apply cheats if they exist and the program has a valid title ID
|
||||
if (pm) {
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.SetCurrentProcessBuildID(nso_header.build_id);
|
||||
const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
|
||||
if (!cheats.empty()) {
|
||||
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
|
||||
|
||||
@@ -103,6 +103,8 @@ void LogSettings() {
|
||||
LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub);
|
||||
LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port);
|
||||
LogSetting("Debugging_ProgramArgs", Settings::values.program_args);
|
||||
LogSetting("Services_BCATBackend", Settings::values.bcat_backend);
|
||||
LogSetting("Services_BCATBoxcatLocal", Settings::values.bcat_boxcat_local);
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -448,6 +448,10 @@ struct Values {
|
||||
bool reporting_services;
|
||||
bool quest_flag;
|
||||
|
||||
// BCAT
|
||||
std::string bcat_backend;
|
||||
bool bcat_boxcat_local;
|
||||
|
||||
// WebService
|
||||
bool enable_telemetry;
|
||||
std::string web_api_url;
|
||||
|
||||
@@ -105,9 +105,15 @@ add_library(video_core STATIC
|
||||
shader/decode/warp.cpp
|
||||
shader/decode/xmad.cpp
|
||||
shader/decode/other.cpp
|
||||
shader/ast.cpp
|
||||
shader/ast.h
|
||||
shader/control_flow.cpp
|
||||
shader/control_flow.h
|
||||
shader/compiler_settings.cpp
|
||||
shader/compiler_settings.h
|
||||
shader/decode.cpp
|
||||
shader/expr.cpp
|
||||
shader/expr.h
|
||||
shader/node_helper.cpp
|
||||
shader/node_helper.h
|
||||
shader/node.h
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "video_core/renderer_opengl/gl_device.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/node.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
@@ -334,39 +335,24 @@ constexpr bool IsVertexShader(ProgramType stage) {
|
||||
return stage == ProgramType::VertexA || stage == ProgramType::VertexB;
|
||||
}
|
||||
|
||||
class ASTDecompiler;
|
||||
class ExprDecompiler;
|
||||
|
||||
class GLSLDecompiler final {
|
||||
public:
|
||||
explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage,
|
||||
std::string suffix)
|
||||
: device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {}
|
||||
|
||||
void Decompile() {
|
||||
DeclareVertex();
|
||||
DeclareGeometry();
|
||||
DeclareRegisters();
|
||||
DeclarePredicates();
|
||||
DeclareLocalMemory();
|
||||
DeclareSharedMemory();
|
||||
DeclareInternalFlags();
|
||||
DeclareInputAttributes();
|
||||
DeclareOutputAttributes();
|
||||
DeclareConstantBuffers();
|
||||
DeclareGlobalMemory();
|
||||
DeclareSamplers();
|
||||
DeclarePhysicalAttributeReader();
|
||||
DeclareImages();
|
||||
|
||||
code.AddLine("void execute_{}() {{", suffix);
|
||||
++code.scope;
|
||||
|
||||
void DecompileBranchMode() {
|
||||
// VM's program counter
|
||||
const auto first_address = ir.GetBasicBlocks().begin()->first;
|
||||
code.AddLine("uint jmp_to = {}U;", first_address);
|
||||
|
||||
// TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
|
||||
// unlikely that shaders will use 20 nested SSYs and PBKs.
|
||||
constexpr u32 FLOW_STACK_SIZE = 20;
|
||||
if (!ir.IsFlowStackDisabled()) {
|
||||
constexpr u32 FLOW_STACK_SIZE = 20;
|
||||
for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
|
||||
code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
|
||||
code.AddLine("uint {} = 0U;", FlowStackTopName(stack));
|
||||
@@ -392,10 +378,37 @@ public:
|
||||
code.AddLine("default: return;");
|
||||
code.AddLine("}}");
|
||||
|
||||
for (std::size_t i = 0; i < 2; ++i) {
|
||||
--code.scope;
|
||||
code.AddLine("}}");
|
||||
--code.scope;
|
||||
code.AddLine("}}");
|
||||
}
|
||||
|
||||
void DecompileAST();
|
||||
|
||||
void Decompile() {
|
||||
DeclareVertex();
|
||||
DeclareGeometry();
|
||||
DeclareRegisters();
|
||||
DeclarePredicates();
|
||||
DeclareLocalMemory();
|
||||
DeclareInternalFlags();
|
||||
DeclareInputAttributes();
|
||||
DeclareOutputAttributes();
|
||||
DeclareConstantBuffers();
|
||||
DeclareGlobalMemory();
|
||||
DeclareSamplers();
|
||||
DeclarePhysicalAttributeReader();
|
||||
|
||||
code.AddLine("void execute_{}() {{", suffix);
|
||||
++code.scope;
|
||||
|
||||
if (ir.IsDecompiled()) {
|
||||
DecompileAST();
|
||||
} else {
|
||||
DecompileBranchMode();
|
||||
}
|
||||
|
||||
--code.scope;
|
||||
code.AddLine("}}");
|
||||
}
|
||||
|
||||
std::string GetResult() {
|
||||
@@ -424,6 +437,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ASTDecompiler;
|
||||
friend class ExprDecompiler;
|
||||
|
||||
void DeclareVertex() {
|
||||
if (!IsVertexShader(stage))
|
||||
return;
|
||||
@@ -1821,10 +1837,9 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
Expression Exit(Operation operation) {
|
||||
void PreExit() {
|
||||
if (stage != ProgramType::Fragment) {
|
||||
code.AddLine("return;");
|
||||
return {};
|
||||
return;
|
||||
}
|
||||
const auto& used_registers = ir.GetRegisters();
|
||||
const auto SafeGetRegister = [&](u32 reg) -> Expression {
|
||||
@@ -1856,7 +1871,10 @@ private:
|
||||
// already contains one past the last color register.
|
||||
code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
|
||||
}
|
||||
}
|
||||
|
||||
Expression Exit(Operation operation) {
|
||||
PreExit();
|
||||
code.AddLine("return;");
|
||||
return {};
|
||||
}
|
||||
@@ -2253,6 +2271,208 @@ private:
|
||||
ShaderWriter code;
|
||||
};
|
||||
|
||||
static constexpr std::string_view flow_var = "flow_var_";
|
||||
|
||||
std::string GetFlowVariable(u32 i) {
|
||||
return fmt::format("{}{}", flow_var, i);
|
||||
}
|
||||
|
||||
class ExprDecompiler {
|
||||
public:
|
||||
explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprAnd& expr) {
|
||||
inner += "( ";
|
||||
std::visit(*this, *expr.operand1);
|
||||
inner += " && ";
|
||||
std::visit(*this, *expr.operand2);
|
||||
inner += ')';
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprOr& expr) {
|
||||
inner += "( ";
|
||||
std::visit(*this, *expr.operand1);
|
||||
inner += " || ";
|
||||
std::visit(*this, *expr.operand2);
|
||||
inner += ')';
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprNot& expr) {
|
||||
inner += '!';
|
||||
std::visit(*this, *expr.operand1);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprPredicate& expr) {
|
||||
const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
|
||||
inner += decomp.GetPredicate(pred);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprCondCode& expr) {
|
||||
const Node cc = decomp.ir.GetConditionCode(expr.cc);
|
||||
std::string target;
|
||||
|
||||
if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
|
||||
const auto index = pred->GetIndex();
|
||||
switch (index) {
|
||||
case Tegra::Shader::Pred::NeverExecute:
|
||||
target = "false";
|
||||
case Tegra::Shader::Pred::UnusedIndex:
|
||||
target = "true";
|
||||
default:
|
||||
target = decomp.GetPredicate(index);
|
||||
}
|
||||
} else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
|
||||
target = decomp.GetInternalFlag(flag->GetFlag());
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
inner += target;
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprVar& expr) {
|
||||
inner += GetFlowVariable(expr.var_index);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprBoolean& expr) {
|
||||
inner += expr.value ? "true" : "false";
|
||||
}
|
||||
|
||||
std::string& GetResult() {
|
||||
return inner;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string inner;
|
||||
GLSLDecompiler& decomp;
|
||||
};
|
||||
|
||||
class ASTDecompiler {
|
||||
public:
|
||||
explicit ASTDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTProgram& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTIfThen& ast) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
|
||||
decomp.code.scope++;
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
decomp.code.scope--;
|
||||
decomp.code.AddLine("}}");
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTIfElse& ast) {
|
||||
decomp.code.AddLine("else {{");
|
||||
decomp.code.scope++;
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
decomp.code.scope--;
|
||||
decomp.code.AddLine("}}");
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
|
||||
decomp.VisitBlock(ast.nodes);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTVarSet& ast) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
decomp.code.AddLine("{} = {};", GetFlowVariable(ast.index), expr_parser.GetResult());
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTLabel& ast) {
|
||||
decomp.code.AddLine("// Label_{}:", ast.index);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTGoto& ast) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
decomp.code.AddLine("do {{");
|
||||
decomp.code.scope++;
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
decomp.code.scope--;
|
||||
decomp.code.AddLine("}} while({});", expr_parser.GetResult());
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTReturn& ast) {
|
||||
const bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition);
|
||||
if (!is_true) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
|
||||
decomp.code.scope++;
|
||||
}
|
||||
if (ast.kills) {
|
||||
decomp.code.AddLine("discard;");
|
||||
} else {
|
||||
decomp.PreExit();
|
||||
decomp.code.AddLine("return;");
|
||||
}
|
||||
if (!is_true) {
|
||||
decomp.code.scope--;
|
||||
decomp.code.AddLine("}}");
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTBreak& ast) {
|
||||
const bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition);
|
||||
if (!is_true) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
|
||||
decomp.code.scope++;
|
||||
}
|
||||
decomp.code.AddLine("break;");
|
||||
if (!is_true) {
|
||||
decomp.code.scope--;
|
||||
decomp.code.AddLine("}}");
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(VideoCommon::Shader::ASTNode& node) {
|
||||
std::visit(*this, *node->GetInnerData());
|
||||
}
|
||||
|
||||
private:
|
||||
GLSLDecompiler& decomp;
|
||||
};
|
||||
|
||||
void GLSLDecompiler::DecompileAST() {
|
||||
const u32 num_flow_variables = ir.GetASTNumVariables();
|
||||
for (u32 i = 0; i < num_flow_variables; i++) {
|
||||
code.AddLine("bool {} = false;", GetFlowVariable(i));
|
||||
}
|
||||
ASTDecompiler decompiler{*this};
|
||||
VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
|
||||
decompiler.Visit(program);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::string GetCommonDeclarations() {
|
||||
|
||||
@@ -11,12 +11,16 @@
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
using Tegra::Engines::Maxwell3D;
|
||||
using VideoCommon::Shader::CompileDepth;
|
||||
using VideoCommon::Shader::CompilerSettings;
|
||||
using VideoCommon::Shader::ProgramCode;
|
||||
using VideoCommon::Shader::ShaderIR;
|
||||
|
||||
static constexpr u32 PROGRAM_OFFSET = 10;
|
||||
static constexpr u32 COMPUTE_OFFSET = 0;
|
||||
|
||||
static constexpr CompilerSettings settings{CompileDepth::NoFlowStack, true};
|
||||
|
||||
ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
|
||||
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
|
||||
|
||||
@@ -31,13 +35,14 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
|
||||
|
||||
)";
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
|
||||
const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB;
|
||||
ProgramResult program = Decompile(device, program_ir, stage, "vertex");
|
||||
out += program.first;
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b);
|
||||
const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b,
|
||||
settings);
|
||||
ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b");
|
||||
out += program_b.first;
|
||||
}
|
||||
@@ -80,7 +85,7 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
|
||||
|
||||
)";
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
|
||||
ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry");
|
||||
out += program.first;
|
||||
|
||||
@@ -114,7 +119,8 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
|
||||
};
|
||||
|
||||
)";
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
|
||||
ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment");
|
||||
out += program.first;
|
||||
|
||||
@@ -133,7 +139,7 @@ ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& set
|
||||
std::string out = "// Shader Unique Id: CS" + id + "\n\n";
|
||||
out += GetCommonDeclarations();
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a);
|
||||
const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a, settings);
|
||||
ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute");
|
||||
out += program.first;
|
||||
|
||||
|
||||
@@ -88,6 +88,9 @@ bool IsPrecise(Operation operand) {
|
||||
|
||||
} // namespace
|
||||
|
||||
class ASTDecompiler;
|
||||
class ExprDecompiler;
|
||||
|
||||
class SPIRVDecompiler : public Sirit::Module {
|
||||
public:
|
||||
explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage)
|
||||
@@ -97,27 +100,7 @@ public:
|
||||
AddExtension("SPV_KHR_variable_pointers");
|
||||
}
|
||||
|
||||
void Decompile() {
|
||||
AllocateBindings();
|
||||
AllocateLabels();
|
||||
|
||||
DeclareVertex();
|
||||
DeclareGeometry();
|
||||
DeclareFragment();
|
||||
DeclareRegisters();
|
||||
DeclarePredicates();
|
||||
DeclareLocalMemory();
|
||||
DeclareInternalFlags();
|
||||
DeclareInputAttributes();
|
||||
DeclareOutputAttributes();
|
||||
DeclareConstantBuffers();
|
||||
DeclareGlobalBuffers();
|
||||
DeclareSamplers();
|
||||
|
||||
execute_function =
|
||||
Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
|
||||
Emit(OpLabel());
|
||||
|
||||
void DecompileBranchMode() {
|
||||
const u32 first_address = ir.GetBasicBlocks().begin()->first;
|
||||
const Id loop_label = OpLabel("loop");
|
||||
const Id merge_label = OpLabel("merge");
|
||||
@@ -174,6 +157,43 @@ public:
|
||||
Emit(continue_label);
|
||||
Emit(OpBranch(loop_label));
|
||||
Emit(merge_label);
|
||||
}
|
||||
|
||||
void DecompileAST();
|
||||
|
||||
void Decompile() {
|
||||
const bool is_fully_decompiled = ir.IsDecompiled();
|
||||
AllocateBindings();
|
||||
if (!is_fully_decompiled) {
|
||||
AllocateLabels();
|
||||
}
|
||||
|
||||
DeclareVertex();
|
||||
DeclareGeometry();
|
||||
DeclareFragment();
|
||||
DeclareRegisters();
|
||||
DeclarePredicates();
|
||||
if (is_fully_decompiled) {
|
||||
DeclareFlowVariables();
|
||||
}
|
||||
DeclareLocalMemory();
|
||||
DeclareInternalFlags();
|
||||
DeclareInputAttributes();
|
||||
DeclareOutputAttributes();
|
||||
DeclareConstantBuffers();
|
||||
DeclareGlobalBuffers();
|
||||
DeclareSamplers();
|
||||
|
||||
execute_function =
|
||||
Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
|
||||
Emit(OpLabel());
|
||||
|
||||
if (is_fully_decompiled) {
|
||||
DecompileAST();
|
||||
} else {
|
||||
DecompileBranchMode();
|
||||
}
|
||||
|
||||
Emit(OpReturn());
|
||||
Emit(OpFunctionEnd());
|
||||
}
|
||||
@@ -206,6 +226,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ASTDecompiler;
|
||||
friend class ExprDecompiler;
|
||||
|
||||
static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount);
|
||||
|
||||
void AllocateBindings() {
|
||||
@@ -294,6 +317,14 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void DeclareFlowVariables() {
|
||||
for (u32 i = 0; i < ir.GetASTNumVariables(); i++) {
|
||||
const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
|
||||
Name(id, fmt::format("flow_var_{}", static_cast<u32>(i)));
|
||||
flow_variables.emplace(i, AddGlobalVariable(id));
|
||||
}
|
||||
}
|
||||
|
||||
void DeclareLocalMemory() {
|
||||
if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
|
||||
const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4);
|
||||
@@ -615,9 +646,15 @@ private:
|
||||
Emit(OpBranchConditional(condition, true_label, skip_label));
|
||||
Emit(true_label);
|
||||
|
||||
++conditional_nest_count;
|
||||
VisitBasicBlock(conditional->GetCode());
|
||||
--conditional_nest_count;
|
||||
|
||||
Emit(OpBranch(skip_label));
|
||||
if (inside_branch == 0) {
|
||||
Emit(OpBranch(skip_label));
|
||||
} else {
|
||||
inside_branch--;
|
||||
}
|
||||
Emit(skip_label);
|
||||
return {};
|
||||
|
||||
@@ -980,7 +1017,11 @@ private:
|
||||
UNIMPLEMENTED_IF(!target);
|
||||
|
||||
Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue())));
|
||||
BranchingOp([&]() { Emit(OpBranch(continue_label)); });
|
||||
Emit(OpBranch(continue_label));
|
||||
inside_branch = conditional_nest_count;
|
||||
if (conditional_nest_count == 0) {
|
||||
Emit(OpLabel());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -988,7 +1029,11 @@ private:
|
||||
const Id op_a = VisitOperand<Type::Uint>(operation, 0);
|
||||
|
||||
Emit(OpStore(jmp_to, op_a));
|
||||
BranchingOp([&]() { Emit(OpBranch(continue_label)); });
|
||||
Emit(OpBranch(continue_label));
|
||||
inside_branch = conditional_nest_count;
|
||||
if (conditional_nest_count == 0) {
|
||||
Emit(OpLabel());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1015,11 +1060,15 @@ private:
|
||||
|
||||
Emit(OpStore(flow_stack_top, previous));
|
||||
Emit(OpStore(jmp_to, target));
|
||||
BranchingOp([&]() { Emit(OpBranch(continue_label)); });
|
||||
Emit(OpBranch(continue_label));
|
||||
inside_branch = conditional_nest_count;
|
||||
if (conditional_nest_count == 0) {
|
||||
Emit(OpLabel());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Id Exit(Operation operation) {
|
||||
Id PreExit() {
|
||||
switch (stage) {
|
||||
case ShaderStage::Vertex: {
|
||||
// TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't
|
||||
@@ -1067,12 +1116,35 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
BranchingOp([&]() { Emit(OpReturn()); });
|
||||
return {};
|
||||
}
|
||||
|
||||
Id Exit(Operation operation) {
|
||||
PreExit();
|
||||
inside_branch = conditional_nest_count;
|
||||
if (conditional_nest_count > 0) {
|
||||
Emit(OpReturn());
|
||||
} else {
|
||||
const Id dummy = OpLabel();
|
||||
Emit(OpBranch(dummy));
|
||||
Emit(dummy);
|
||||
Emit(OpReturn());
|
||||
Emit(OpLabel());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Id Discard(Operation operation) {
|
||||
BranchingOp([&]() { Emit(OpKill()); });
|
||||
inside_branch = conditional_nest_count;
|
||||
if (conditional_nest_count > 0) {
|
||||
Emit(OpKill());
|
||||
} else {
|
||||
const Id dummy = OpLabel();
|
||||
Emit(OpBranch(dummy));
|
||||
Emit(dummy);
|
||||
Emit(OpKill());
|
||||
Emit(OpLabel());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1267,17 +1339,6 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
void BranchingOp(std::function<void()> call) {
|
||||
const Id true_label = OpLabel();
|
||||
const Id skip_label = OpLabel();
|
||||
Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::Flatten));
|
||||
Emit(OpBranchConditional(v_true, true_label, skip_label, 1, 0));
|
||||
Emit(true_label);
|
||||
call();
|
||||
|
||||
Emit(skip_label);
|
||||
}
|
||||
|
||||
std::tuple<Id, Id> CreateFlowStack() {
|
||||
// TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely
|
||||
// that shaders will use 20 nested SSYs and PBKs.
|
||||
@@ -1483,6 +1544,8 @@ private:
|
||||
const ShaderIR& ir;
|
||||
const ShaderStage stage;
|
||||
const Tegra::Shader::Header header;
|
||||
u64 conditional_nest_count{};
|
||||
u64 inside_branch{};
|
||||
|
||||
const Id t_void = Name(TypeVoid(), "void");
|
||||
|
||||
@@ -1545,6 +1608,7 @@ private:
|
||||
Id per_vertex{};
|
||||
std::map<u32, Id> registers;
|
||||
std::map<Tegra::Shader::Pred, Id> predicates;
|
||||
std::map<u32, Id> flow_variables;
|
||||
Id local_memory{};
|
||||
std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{};
|
||||
std::map<Attribute::Index, Id> input_attributes;
|
||||
@@ -1580,6 +1644,223 @@ private:
|
||||
std::map<u32, Id> labels;
|
||||
};
|
||||
|
||||
class ExprDecompiler {
|
||||
public:
|
||||
explicit ExprDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprAnd& expr) {
|
||||
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
|
||||
const Id op1 = Visit(expr.operand1);
|
||||
const Id op2 = Visit(expr.operand2);
|
||||
return decomp.Emit(decomp.OpLogicalAnd(type_def, op1, op2));
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprOr& expr) {
|
||||
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
|
||||
const Id op1 = Visit(expr.operand1);
|
||||
const Id op2 = Visit(expr.operand2);
|
||||
return decomp.Emit(decomp.OpLogicalOr(type_def, op1, op2));
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprNot& expr) {
|
||||
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
|
||||
const Id op1 = Visit(expr.operand1);
|
||||
return decomp.Emit(decomp.OpLogicalNot(type_def, op1));
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprPredicate& expr) {
|
||||
const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
|
||||
return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.predicates.at(pred)));
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprCondCode& expr) {
|
||||
const Node cc = decomp.ir.GetConditionCode(expr.cc);
|
||||
Id target;
|
||||
|
||||
if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
|
||||
const auto index = pred->GetIndex();
|
||||
switch (index) {
|
||||
case Tegra::Shader::Pred::NeverExecute:
|
||||
target = decomp.v_false;
|
||||
case Tegra::Shader::Pred::UnusedIndex:
|
||||
target = decomp.v_true;
|
||||
default:
|
||||
target = decomp.predicates.at(index);
|
||||
}
|
||||
} else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
|
||||
target = decomp.internal_flags.at(static_cast<u32>(flag->GetFlag()));
|
||||
}
|
||||
return decomp.Emit(decomp.OpLoad(decomp.t_bool, target));
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprVar& expr) {
|
||||
return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.flow_variables.at(expr.var_index)));
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprBoolean& expr) {
|
||||
return expr.value ? decomp.v_true : decomp.v_false;
|
||||
}
|
||||
|
||||
Id Visit(VideoCommon::Shader::Expr& node) {
|
||||
return std::visit(*this, *node);
|
||||
}
|
||||
|
||||
private:
|
||||
SPIRVDecompiler& decomp;
|
||||
};
|
||||
|
||||
class ASTDecompiler {
|
||||
public:
|
||||
explicit ASTDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTProgram& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTIfThen& ast) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
const Id condition = expr_parser.Visit(ast.condition);
|
||||
const Id then_label = decomp.OpLabel();
|
||||
const Id endif_label = decomp.OpLabel();
|
||||
decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
|
||||
decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
|
||||
decomp.Emit(then_label);
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
decomp.Emit(decomp.OpBranch(endif_label));
|
||||
decomp.Emit(endif_label);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTIfElse& ast) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
|
||||
decomp.VisitBasicBlock(ast.nodes);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTVarSet& ast) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
const Id condition = expr_parser.Visit(ast.condition);
|
||||
decomp.Emit(decomp.OpStore(decomp.flow_variables.at(ast.index), condition));
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTLabel& ast) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTGoto& ast) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
|
||||
const Id loop_label = decomp.OpLabel();
|
||||
const Id endloop_label = decomp.OpLabel();
|
||||
const Id loop_start_block = decomp.OpLabel();
|
||||
const Id loop_end_block = decomp.OpLabel();
|
||||
current_loop_exit = endloop_label;
|
||||
decomp.Emit(decomp.OpBranch(loop_label));
|
||||
decomp.Emit(loop_label);
|
||||
decomp.Emit(
|
||||
decomp.OpLoopMerge(endloop_label, loop_end_block, spv::LoopControlMask::MaskNone));
|
||||
decomp.Emit(decomp.OpBranch(loop_start_block));
|
||||
decomp.Emit(loop_start_block);
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
const Id condition = expr_parser.Visit(ast.condition);
|
||||
decomp.Emit(decomp.OpBranchConditional(condition, loop_label, endloop_label));
|
||||
decomp.Emit(endloop_label);
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTReturn& ast) {
|
||||
if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
const Id condition = expr_parser.Visit(ast.condition);
|
||||
const Id then_label = decomp.OpLabel();
|
||||
const Id endif_label = decomp.OpLabel();
|
||||
decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
|
||||
decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
|
||||
decomp.Emit(then_label);
|
||||
if (ast.kills) {
|
||||
decomp.Emit(decomp.OpKill());
|
||||
} else {
|
||||
decomp.PreExit();
|
||||
decomp.Emit(decomp.OpReturn());
|
||||
}
|
||||
decomp.Emit(endif_label);
|
||||
} else {
|
||||
const Id next_block = decomp.OpLabel();
|
||||
decomp.Emit(decomp.OpBranch(next_block));
|
||||
decomp.Emit(next_block);
|
||||
if (ast.kills) {
|
||||
decomp.Emit(decomp.OpKill());
|
||||
} else {
|
||||
decomp.PreExit();
|
||||
decomp.Emit(decomp.OpReturn());
|
||||
}
|
||||
decomp.Emit(decomp.OpLabel());
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ASTBreak& ast) {
|
||||
if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
|
||||
ExprDecompiler expr_parser{decomp};
|
||||
const Id condition = expr_parser.Visit(ast.condition);
|
||||
const Id then_label = decomp.OpLabel();
|
||||
const Id endif_label = decomp.OpLabel();
|
||||
decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
|
||||
decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
|
||||
decomp.Emit(then_label);
|
||||
decomp.Emit(decomp.OpBranch(current_loop_exit));
|
||||
decomp.Emit(endif_label);
|
||||
} else {
|
||||
const Id next_block = decomp.OpLabel();
|
||||
decomp.Emit(decomp.OpBranch(next_block));
|
||||
decomp.Emit(next_block);
|
||||
decomp.Emit(decomp.OpBranch(current_loop_exit));
|
||||
decomp.Emit(decomp.OpLabel());
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(VideoCommon::Shader::ASTNode& node) {
|
||||
std::visit(*this, *node->GetInnerData());
|
||||
}
|
||||
|
||||
private:
|
||||
SPIRVDecompiler& decomp;
|
||||
Id current_loop_exit{};
|
||||
};
|
||||
|
||||
void SPIRVDecompiler::DecompileAST() {
|
||||
const u32 num_flow_variables = ir.GetASTNumVariables();
|
||||
for (u32 i = 0; i < num_flow_variables; i++) {
|
||||
const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
|
||||
Name(id, fmt::format("flow_var_{}", i));
|
||||
flow_variables.emplace(i, AddGlobalVariable(id));
|
||||
}
|
||||
ASTDecompiler decompiler{*this};
|
||||
VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
|
||||
decompiler.Visit(program);
|
||||
const Id next_block = OpLabel();
|
||||
Emit(OpBranch(next_block));
|
||||
Emit(next_block);
|
||||
}
|
||||
|
||||
DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
|
||||
Maxwell::ShaderStage stage) {
|
||||
auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage);
|
||||
|
||||
738
src/video_core/shader/ast.cpp
Normal file
738
src/video_core/shader/ast.cpp
Normal file
@@ -0,0 +1,738 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/expr.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
ASTZipper::ASTZipper() = default;
|
||||
|
||||
void ASTZipper::Init(const ASTNode new_first, const ASTNode parent) {
|
||||
ASSERT(new_first->manager == nullptr);
|
||||
first = new_first;
|
||||
last = new_first;
|
||||
|
||||
ASTNode current = first;
|
||||
while (current) {
|
||||
current->manager = this;
|
||||
current->parent = parent;
|
||||
last = current;
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
void ASTZipper::PushBack(const ASTNode new_node) {
|
||||
ASSERT(new_node->manager == nullptr);
|
||||
new_node->previous = last;
|
||||
if (last) {
|
||||
last->next = new_node;
|
||||
}
|
||||
new_node->next.reset();
|
||||
last = new_node;
|
||||
if (!first) {
|
||||
first = new_node;
|
||||
}
|
||||
new_node->manager = this;
|
||||
}
|
||||
|
||||
void ASTZipper::PushFront(const ASTNode new_node) {
|
||||
ASSERT(new_node->manager == nullptr);
|
||||
new_node->previous.reset();
|
||||
new_node->next = first;
|
||||
if (first) {
|
||||
first->previous = new_node;
|
||||
}
|
||||
if (last == first) {
|
||||
last = new_node;
|
||||
}
|
||||
first = new_node;
|
||||
new_node->manager = this;
|
||||
}
|
||||
|
||||
void ASTZipper::InsertAfter(const ASTNode new_node, const ASTNode at_node) {
|
||||
ASSERT(new_node->manager == nullptr);
|
||||
if (!at_node) {
|
||||
PushFront(new_node);
|
||||
return;
|
||||
}
|
||||
const ASTNode next = at_node->next;
|
||||
if (next) {
|
||||
next->previous = new_node;
|
||||
}
|
||||
new_node->previous = at_node;
|
||||
if (at_node == last) {
|
||||
last = new_node;
|
||||
}
|
||||
new_node->next = next;
|
||||
at_node->next = new_node;
|
||||
new_node->manager = this;
|
||||
}
|
||||
|
||||
void ASTZipper::InsertBefore(const ASTNode new_node, const ASTNode at_node) {
|
||||
ASSERT(new_node->manager == nullptr);
|
||||
if (!at_node) {
|
||||
PushBack(new_node);
|
||||
return;
|
||||
}
|
||||
const ASTNode previous = at_node->previous;
|
||||
if (previous) {
|
||||
previous->next = new_node;
|
||||
}
|
||||
new_node->next = at_node;
|
||||
if (at_node == first) {
|
||||
first = new_node;
|
||||
}
|
||||
new_node->previous = previous;
|
||||
at_node->previous = new_node;
|
||||
new_node->manager = this;
|
||||
}
|
||||
|
||||
void ASTZipper::DetachTail(ASTNode node) {
|
||||
ASSERT(node->manager == this);
|
||||
if (node == first) {
|
||||
first.reset();
|
||||
last.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
last = node->previous;
|
||||
last->next.reset();
|
||||
node->previous.reset();
|
||||
|
||||
ASTNode current = std::move(node);
|
||||
while (current) {
|
||||
current->manager = nullptr;
|
||||
current->parent.reset();
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
void ASTZipper::DetachSegment(const ASTNode start, const ASTNode end) {
|
||||
ASSERT(start->manager == this && end->manager == this);
|
||||
if (start == end) {
|
||||
DetachSingle(start);
|
||||
return;
|
||||
}
|
||||
const ASTNode prev = start->previous;
|
||||
const ASTNode post = end->next;
|
||||
if (!prev) {
|
||||
first = post;
|
||||
} else {
|
||||
prev->next = post;
|
||||
}
|
||||
if (!post) {
|
||||
last = prev;
|
||||
} else {
|
||||
post->previous = prev;
|
||||
}
|
||||
start->previous.reset();
|
||||
end->next.reset();
|
||||
ASTNode current = start;
|
||||
bool found = false;
|
||||
while (current) {
|
||||
current->manager = nullptr;
|
||||
current->parent.reset();
|
||||
found |= current == end;
|
||||
current = current->next;
|
||||
}
|
||||
ASSERT(found);
|
||||
}
|
||||
|
||||
void ASTZipper::DetachSingle(const ASTNode node) {
|
||||
ASSERT(node->manager == this);
|
||||
const ASTNode prev = node->previous;
|
||||
const ASTNode post = node->next;
|
||||
node->previous.reset();
|
||||
node->next.reset();
|
||||
if (!prev) {
|
||||
first = post;
|
||||
} else {
|
||||
prev->next = post;
|
||||
}
|
||||
if (!post) {
|
||||
last = prev;
|
||||
} else {
|
||||
post->previous = prev;
|
||||
}
|
||||
|
||||
node->manager = nullptr;
|
||||
node->parent.reset();
|
||||
}
|
||||
|
||||
void ASTZipper::Remove(const ASTNode node) {
|
||||
ASSERT(node->manager == this);
|
||||
const ASTNode next = node->next;
|
||||
const ASTNode previous = node->previous;
|
||||
if (previous) {
|
||||
previous->next = next;
|
||||
}
|
||||
if (next) {
|
||||
next->previous = previous;
|
||||
}
|
||||
node->parent.reset();
|
||||
node->manager = nullptr;
|
||||
if (node == last) {
|
||||
last = previous;
|
||||
}
|
||||
if (node == first) {
|
||||
first = next;
|
||||
}
|
||||
}
|
||||
|
||||
class ExprPrinter final {
|
||||
public:
|
||||
void operator()(const ExprAnd& expr) {
|
||||
inner += "( ";
|
||||
std::visit(*this, *expr.operand1);
|
||||
inner += " && ";
|
||||
std::visit(*this, *expr.operand2);
|
||||
inner += ')';
|
||||
}
|
||||
|
||||
void operator()(const ExprOr& expr) {
|
||||
inner += "( ";
|
||||
std::visit(*this, *expr.operand1);
|
||||
inner += " || ";
|
||||
std::visit(*this, *expr.operand2);
|
||||
inner += ')';
|
||||
}
|
||||
|
||||
void operator()(const ExprNot& expr) {
|
||||
inner += "!";
|
||||
std::visit(*this, *expr.operand1);
|
||||
}
|
||||
|
||||
void operator()(const ExprPredicate& expr) {
|
||||
inner += "P" + std::to_string(expr.predicate);
|
||||
}
|
||||
|
||||
void operator()(const ExprCondCode& expr) {
|
||||
u32 cc = static_cast<u32>(expr.cc);
|
||||
inner += "CC" + std::to_string(cc);
|
||||
}
|
||||
|
||||
void operator()(const ExprVar& expr) {
|
||||
inner += "V" + std::to_string(expr.var_index);
|
||||
}
|
||||
|
||||
void operator()(const ExprBoolean& expr) {
|
||||
inner += expr.value ? "true" : "false";
|
||||
}
|
||||
|
||||
const std::string& GetResult() const {
|
||||
return inner;
|
||||
}
|
||||
|
||||
std::string inner{};
|
||||
};
|
||||
|
||||
class ASTPrinter {
|
||||
public:
|
||||
void operator()(const ASTProgram& ast) {
|
||||
scope++;
|
||||
inner += "program {\n";
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
inner += "}\n";
|
||||
scope--;
|
||||
}
|
||||
|
||||
void operator()(const ASTIfThen& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
|
||||
scope++;
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
scope--;
|
||||
inner += Ident() + "}\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTIfElse& ast) {
|
||||
inner += Ident() + "else {\n";
|
||||
scope++;
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
scope--;
|
||||
inner += Ident() + "}\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTBlockEncoded& ast) {
|
||||
inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
|
||||
");\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTBlockDecoded& ast) {
|
||||
inner += Ident() + "Block;\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTVarSet& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner +=
|
||||
Ident() + "V" + std::to_string(ast.index) + " := " + expr_parser.GetResult() + ";\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTLabel& ast) {
|
||||
inner += "Label_" + std::to_string(ast.index) + ":\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTGoto& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner += Ident() + "(" + expr_parser.GetResult() + ") -> goto Label_" +
|
||||
std::to_string(ast.label) + ";\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTDoWhile& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner += Ident() + "do {\n";
|
||||
scope++;
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
scope--;
|
||||
inner += Ident() + "} while (" + expr_parser.GetResult() + ");\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTReturn& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner += Ident() + "(" + expr_parser.GetResult() + ") -> " +
|
||||
(ast.kills ? "discard" : "exit") + ";\n";
|
||||
}
|
||||
|
||||
void operator()(const ASTBreak& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner += Ident() + "(" + expr_parser.GetResult() + ") -> break;\n";
|
||||
}
|
||||
|
||||
std::string& Ident() {
|
||||
if (memo_scope == scope) {
|
||||
return tabs_memo;
|
||||
}
|
||||
tabs_memo = tabs.substr(0, scope * 2);
|
||||
memo_scope = scope;
|
||||
return tabs_memo;
|
||||
}
|
||||
|
||||
void Visit(ASTNode& node) {
|
||||
std::visit(*this, *node->GetInnerData());
|
||||
}
|
||||
|
||||
const std::string& GetResult() const {
|
||||
return inner;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string inner{};
|
||||
u32 scope{};
|
||||
|
||||
std::string tabs_memo{};
|
||||
u32 memo_scope{};
|
||||
|
||||
static constexpr std::string_view tabs{" "};
|
||||
};
|
||||
|
||||
std::string ASTManager::Print() {
|
||||
ASTPrinter printer{};
|
||||
printer.Visit(main_node);
|
||||
return printer.GetResult();
|
||||
}
|
||||
|
||||
ASTManager::ASTManager(bool full_decompile, bool disable_else_derivation)
|
||||
: full_decompile{full_decompile}, disable_else_derivation{disable_else_derivation} {};
|
||||
|
||||
ASTManager::~ASTManager() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void ASTManager::Init() {
|
||||
main_node = ASTBase::Make<ASTProgram>(ASTNode{});
|
||||
program = std::get_if<ASTProgram>(main_node->GetInnerData());
|
||||
false_condition = MakeExpr<ExprBoolean>(false);
|
||||
}
|
||||
|
||||
void ASTManager::DeclareLabel(u32 address) {
|
||||
const auto pair = labels_map.emplace(address, labels_count);
|
||||
if (pair.second) {
|
||||
labels_count++;
|
||||
labels.resize(labels_count);
|
||||
}
|
||||
}
|
||||
|
||||
void ASTManager::InsertLabel(u32 address) {
|
||||
const u32 index = labels_map[address];
|
||||
const ASTNode label = ASTBase::Make<ASTLabel>(main_node, index);
|
||||
labels[index] = label;
|
||||
program->nodes.PushBack(label);
|
||||
}
|
||||
|
||||
void ASTManager::InsertGoto(Expr condition, u32 address) {
|
||||
const u32 index = labels_map[address];
|
||||
const ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, std::move(condition), index);
|
||||
gotos.push_back(goto_node);
|
||||
program->nodes.PushBack(goto_node);
|
||||
}
|
||||
|
||||
void ASTManager::InsertBlock(u32 start_address, u32 end_address) {
|
||||
ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
|
||||
program->nodes.PushBack(std::move(block));
|
||||
}
|
||||
|
||||
void ASTManager::InsertReturn(Expr condition, bool kills) {
|
||||
ASTNode node = ASTBase::Make<ASTReturn>(main_node, std::move(condition), kills);
|
||||
program->nodes.PushBack(std::move(node));
|
||||
}
|
||||
|
||||
// The decompile algorithm is based on
|
||||
// "Taming control flow: A structured approach to eliminating goto statements"
|
||||
// by AM Erosa, LJ Hendren 1994. In general, the idea is to get gotos to be
|
||||
// on the same structured level as the label which they jump to. This is done,
|
||||
// through outward/inward movements and lifting. Once they are at the same
|
||||
// level, you can enclose them in an "if" structure or a "do-while" structure.
|
||||
void ASTManager::Decompile() {
|
||||
auto it = gotos.begin();
|
||||
while (it != gotos.end()) {
|
||||
const ASTNode goto_node = *it;
|
||||
const auto label_index = goto_node->GetGotoLabel();
|
||||
if (!label_index) {
|
||||
return;
|
||||
}
|
||||
const ASTNode label = labels[*label_index];
|
||||
if (!full_decompile) {
|
||||
// We only decompile backward jumps
|
||||
if (!IsBackwardsJump(goto_node, label)) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (IndirectlyRelated(goto_node, label)) {
|
||||
while (!DirectlyRelated(goto_node, label)) {
|
||||
MoveOutward(goto_node);
|
||||
}
|
||||
}
|
||||
if (DirectlyRelated(goto_node, label)) {
|
||||
u32 goto_level = goto_node->GetLevel();
|
||||
const u32 label_level = label->GetLevel();
|
||||
while (label_level < goto_level) {
|
||||
MoveOutward(goto_node);
|
||||
goto_level--;
|
||||
}
|
||||
// TODO(Blinkhawk): Implement Lifting and Inward Movements
|
||||
}
|
||||
if (label->GetParent() == goto_node->GetParent()) {
|
||||
bool is_loop = false;
|
||||
ASTNode current = goto_node->GetPrevious();
|
||||
while (current) {
|
||||
if (current == label) {
|
||||
is_loop = true;
|
||||
break;
|
||||
}
|
||||
current = current->GetPrevious();
|
||||
}
|
||||
|
||||
if (is_loop) {
|
||||
EncloseDoWhile(goto_node, label);
|
||||
} else {
|
||||
EncloseIfThen(goto_node, label);
|
||||
}
|
||||
it = gotos.erase(it);
|
||||
continue;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
if (full_decompile) {
|
||||
for (const ASTNode& label : labels) {
|
||||
auto& manager = label->GetManager();
|
||||
manager.Remove(label);
|
||||
}
|
||||
labels.clear();
|
||||
} else {
|
||||
auto label_it = labels.begin();
|
||||
while (label_it != labels.end()) {
|
||||
bool can_remove = true;
|
||||
ASTNode label = *label_it;
|
||||
for (const ASTNode& goto_node : gotos) {
|
||||
const auto label_index = goto_node->GetGotoLabel();
|
||||
if (!label_index) {
|
||||
return;
|
||||
}
|
||||
ASTNode& glabel = labels[*label_index];
|
||||
if (glabel == label) {
|
||||
can_remove = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (can_remove) {
|
||||
label->MarkLabelUnused();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ASTManager::IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const {
|
||||
u32 goto_level = goto_node->GetLevel();
|
||||
u32 label_level = label_node->GetLevel();
|
||||
while (goto_level > label_level) {
|
||||
goto_level--;
|
||||
goto_node = goto_node->GetParent();
|
||||
}
|
||||
while (label_level > goto_level) {
|
||||
label_level--;
|
||||
label_node = label_node->GetParent();
|
||||
}
|
||||
while (goto_node->GetParent() != label_node->GetParent()) {
|
||||
goto_node = goto_node->GetParent();
|
||||
label_node = label_node->GetParent();
|
||||
}
|
||||
ASTNode current = goto_node->GetPrevious();
|
||||
while (current) {
|
||||
if (current == label_node) {
|
||||
return true;
|
||||
}
|
||||
current = current->GetPrevious();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASTManager::IndirectlyRelated(const ASTNode& first, const ASTNode& second) const {
|
||||
return !(first->GetParent() == second->GetParent() || DirectlyRelated(first, second));
|
||||
}
|
||||
|
||||
bool ASTManager::DirectlyRelated(const ASTNode& first, const ASTNode& second) const {
|
||||
if (first->GetParent() == second->GetParent()) {
|
||||
return false;
|
||||
}
|
||||
const u32 first_level = first->GetLevel();
|
||||
const u32 second_level = second->GetLevel();
|
||||
u32 min_level;
|
||||
u32 max_level;
|
||||
ASTNode max;
|
||||
ASTNode min;
|
||||
if (first_level > second_level) {
|
||||
min_level = second_level;
|
||||
min = second;
|
||||
max_level = first_level;
|
||||
max = first;
|
||||
} else {
|
||||
min_level = first_level;
|
||||
min = first;
|
||||
max_level = second_level;
|
||||
max = second;
|
||||
}
|
||||
|
||||
while (max_level > min_level) {
|
||||
max_level--;
|
||||
max = max->GetParent();
|
||||
}
|
||||
|
||||
return min->GetParent() == max->GetParent();
|
||||
}
|
||||
|
||||
void ASTManager::ShowCurrentState(std::string_view state) {
|
||||
LOG_CRITICAL(HW_GPU, "\nState {}:\n\n{}\n", state, Print());
|
||||
SanityCheck();
|
||||
}
|
||||
|
||||
void ASTManager::SanityCheck() {
|
||||
for (auto& label : labels) {
|
||||
if (!label->GetParent()) {
|
||||
LOG_CRITICAL(HW_GPU, "Sanity Check Failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASTManager::EncloseDoWhile(ASTNode goto_node, ASTNode label) {
|
||||
ASTZipper& zipper = goto_node->GetManager();
|
||||
const ASTNode loop_start = label->GetNext();
|
||||
if (loop_start == goto_node) {
|
||||
zipper.Remove(goto_node);
|
||||
return;
|
||||
}
|
||||
const ASTNode parent = label->GetParent();
|
||||
const Expr condition = goto_node->GetGotoCondition();
|
||||
zipper.DetachSegment(loop_start, goto_node);
|
||||
const ASTNode do_while_node = ASTBase::Make<ASTDoWhile>(parent, condition);
|
||||
ASTZipper* sub_zipper = do_while_node->GetSubNodes();
|
||||
sub_zipper->Init(loop_start, do_while_node);
|
||||
zipper.InsertAfter(do_while_node, label);
|
||||
sub_zipper->Remove(goto_node);
|
||||
}
|
||||
|
||||
void ASTManager::EncloseIfThen(ASTNode goto_node, ASTNode label) {
|
||||
ASTZipper& zipper = goto_node->GetManager();
|
||||
const ASTNode if_end = label->GetPrevious();
|
||||
if (if_end == goto_node) {
|
||||
zipper.Remove(goto_node);
|
||||
return;
|
||||
}
|
||||
const ASTNode prev = goto_node->GetPrevious();
|
||||
const Expr condition = goto_node->GetGotoCondition();
|
||||
bool do_else = false;
|
||||
if (!disable_else_derivation && prev->IsIfThen()) {
|
||||
const Expr if_condition = prev->GetIfCondition();
|
||||
do_else = ExprAreEqual(if_condition, condition);
|
||||
}
|
||||
const ASTNode parent = label->GetParent();
|
||||
zipper.DetachSegment(goto_node, if_end);
|
||||
ASTNode if_node;
|
||||
if (do_else) {
|
||||
if_node = ASTBase::Make<ASTIfElse>(parent);
|
||||
} else {
|
||||
Expr neg_condition = MakeExprNot(condition);
|
||||
if_node = ASTBase::Make<ASTIfThen>(parent, neg_condition);
|
||||
}
|
||||
ASTZipper* sub_zipper = if_node->GetSubNodes();
|
||||
sub_zipper->Init(goto_node, if_node);
|
||||
zipper.InsertAfter(if_node, prev);
|
||||
sub_zipper->Remove(goto_node);
|
||||
}
|
||||
|
||||
void ASTManager::MoveOutward(ASTNode goto_node) {
|
||||
ASTZipper& zipper = goto_node->GetManager();
|
||||
const ASTNode parent = goto_node->GetParent();
|
||||
ASTZipper& zipper2 = parent->GetManager();
|
||||
const ASTNode grandpa = parent->GetParent();
|
||||
const bool is_loop = parent->IsLoop();
|
||||
const bool is_else = parent->IsIfElse();
|
||||
const bool is_if = parent->IsIfThen();
|
||||
|
||||
const ASTNode prev = goto_node->GetPrevious();
|
||||
const ASTNode post = goto_node->GetNext();
|
||||
|
||||
const Expr condition = goto_node->GetGotoCondition();
|
||||
zipper.DetachSingle(goto_node);
|
||||
if (is_loop) {
|
||||
const u32 var_index = NewVariable();
|
||||
const Expr var_condition = MakeExpr<ExprVar>(var_index);
|
||||
const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
|
||||
const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
|
||||
zipper2.InsertBefore(var_node_init, parent);
|
||||
zipper.InsertAfter(var_node, prev);
|
||||
goto_node->SetGotoCondition(var_condition);
|
||||
const ASTNode break_node = ASTBase::Make<ASTBreak>(parent, var_condition);
|
||||
zipper.InsertAfter(break_node, var_node);
|
||||
} else if (is_if || is_else) {
|
||||
const u32 var_index = NewVariable();
|
||||
const Expr var_condition = MakeExpr<ExprVar>(var_index);
|
||||
const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
|
||||
const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
|
||||
if (is_if) {
|
||||
zipper2.InsertBefore(var_node_init, parent);
|
||||
} else {
|
||||
zipper2.InsertBefore(var_node_init, parent->GetPrevious());
|
||||
}
|
||||
zipper.InsertAfter(var_node, prev);
|
||||
goto_node->SetGotoCondition(var_condition);
|
||||
if (post) {
|
||||
zipper.DetachTail(post);
|
||||
const ASTNode if_node = ASTBase::Make<ASTIfThen>(parent, MakeExprNot(var_condition));
|
||||
ASTZipper* sub_zipper = if_node->GetSubNodes();
|
||||
sub_zipper->Init(post, if_node);
|
||||
zipper.InsertAfter(if_node, var_node);
|
||||
}
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
const ASTNode next = parent->GetNext();
|
||||
if (is_if && next && next->IsIfElse()) {
|
||||
zipper2.InsertAfter(goto_node, next);
|
||||
goto_node->SetParent(grandpa);
|
||||
return;
|
||||
}
|
||||
zipper2.InsertAfter(goto_node, parent);
|
||||
goto_node->SetParent(grandpa);
|
||||
}
|
||||
|
||||
class ASTClearer {
|
||||
public:
|
||||
ASTClearer() = default;
|
||||
|
||||
void operator()(const ASTProgram& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const ASTIfThen& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const ASTIfElse& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()([[maybe_unused]] const ASTBlockEncoded& ast) {}
|
||||
|
||||
void operator()(ASTBlockDecoded& ast) {
|
||||
ast.nodes.clear();
|
||||
}
|
||||
|
||||
void operator()([[maybe_unused]] const ASTVarSet& ast) {}
|
||||
|
||||
void operator()([[maybe_unused]] const ASTLabel& ast) {}
|
||||
|
||||
void operator()([[maybe_unused]] const ASTGoto& ast) {}
|
||||
|
||||
void operator()(const ASTDoWhile& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()([[maybe_unused]] const ASTReturn& ast) {}
|
||||
|
||||
void operator()([[maybe_unused]] const ASTBreak& ast) {}
|
||||
|
||||
void Visit(const ASTNode& node) {
|
||||
std::visit(*this, *node->GetInnerData());
|
||||
node->Clear();
|
||||
}
|
||||
};
|
||||
|
||||
void ASTManager::Clear() {
|
||||
if (!main_node) {
|
||||
return;
|
||||
}
|
||||
ASTClearer clearer{};
|
||||
clearer.Visit(main_node);
|
||||
main_node.reset();
|
||||
program = nullptr;
|
||||
labels_map.clear();
|
||||
labels.clear();
|
||||
gotos.clear();
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
400
src/video_core/shader/ast.h
Normal file
400
src/video_core/shader/ast.h
Normal file
@@ -0,0 +1,400 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/shader/expr.h"
|
||||
#include "video_core/shader/node.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
class ASTBase;
|
||||
class ASTBlockDecoded;
|
||||
class ASTBlockEncoded;
|
||||
class ASTBreak;
|
||||
class ASTDoWhile;
|
||||
class ASTGoto;
|
||||
class ASTIfElse;
|
||||
class ASTIfThen;
|
||||
class ASTLabel;
|
||||
class ASTProgram;
|
||||
class ASTReturn;
|
||||
class ASTVarSet;
|
||||
|
||||
using ASTData = std::variant<ASTProgram, ASTIfThen, ASTIfElse, ASTBlockEncoded, ASTBlockDecoded,
|
||||
ASTVarSet, ASTGoto, ASTLabel, ASTDoWhile, ASTReturn, ASTBreak>;
|
||||
|
||||
using ASTNode = std::shared_ptr<ASTBase>;
|
||||
|
||||
enum class ASTZipperType : u32 {
|
||||
Program,
|
||||
IfThen,
|
||||
IfElse,
|
||||
Loop,
|
||||
};
|
||||
|
||||
class ASTZipper final {
|
||||
public:
|
||||
explicit ASTZipper();
|
||||
|
||||
void Init(ASTNode first, ASTNode parent);
|
||||
|
||||
ASTNode GetFirst() const {
|
||||
return first;
|
||||
}
|
||||
|
||||
ASTNode GetLast() const {
|
||||
return last;
|
||||
}
|
||||
|
||||
void PushBack(ASTNode new_node);
|
||||
void PushFront(ASTNode new_node);
|
||||
void InsertAfter(ASTNode new_node, ASTNode at_node);
|
||||
void InsertBefore(ASTNode new_node, ASTNode at_node);
|
||||
void DetachTail(ASTNode node);
|
||||
void DetachSingle(ASTNode node);
|
||||
void DetachSegment(ASTNode start, ASTNode end);
|
||||
void Remove(ASTNode node);
|
||||
|
||||
ASTNode first{};
|
||||
ASTNode last{};
|
||||
};
|
||||
|
||||
class ASTProgram {
|
||||
public:
|
||||
ASTZipper nodes{};
|
||||
};
|
||||
|
||||
class ASTIfThen {
|
||||
public:
|
||||
explicit ASTIfThen(Expr condition) : condition{std::move(condition)} {}
|
||||
Expr condition;
|
||||
ASTZipper nodes{};
|
||||
};
|
||||
|
||||
class ASTIfElse {
|
||||
public:
|
||||
ASTZipper nodes{};
|
||||
};
|
||||
|
||||
class ASTBlockEncoded {
|
||||
public:
|
||||
explicit ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {}
|
||||
u32 start;
|
||||
u32 end;
|
||||
};
|
||||
|
||||
class ASTBlockDecoded {
|
||||
public:
|
||||
explicit ASTBlockDecoded(NodeBlock&& new_nodes) : nodes(std::move(new_nodes)) {}
|
||||
NodeBlock nodes;
|
||||
};
|
||||
|
||||
class ASTVarSet {
|
||||
public:
|
||||
explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{std::move(condition)} {}
|
||||
u32 index;
|
||||
Expr condition;
|
||||
};
|
||||
|
||||
class ASTLabel {
|
||||
public:
|
||||
explicit ASTLabel(u32 index) : index{index} {}
|
||||
u32 index;
|
||||
bool unused{};
|
||||
};
|
||||
|
||||
class ASTGoto {
|
||||
public:
|
||||
explicit ASTGoto(Expr condition, u32 label) : condition{std::move(condition)}, label{label} {}
|
||||
Expr condition;
|
||||
u32 label;
|
||||
};
|
||||
|
||||
class ASTDoWhile {
|
||||
public:
|
||||
explicit ASTDoWhile(Expr condition) : condition{std::move(condition)} {}
|
||||
Expr condition;
|
||||
ASTZipper nodes{};
|
||||
};
|
||||
|
||||
class ASTReturn {
|
||||
public:
|
||||
explicit ASTReturn(Expr condition, bool kills)
|
||||
: condition{std::move(condition)}, kills{kills} {}
|
||||
Expr condition;
|
||||
bool kills;
|
||||
};
|
||||
|
||||
class ASTBreak {
|
||||
public:
|
||||
explicit ASTBreak(Expr condition) : condition{std::move(condition)} {}
|
||||
Expr condition;
|
||||
};
|
||||
|
||||
class ASTBase {
|
||||
public:
|
||||
explicit ASTBase(ASTNode parent, ASTData data)
|
||||
: data{std::move(data)}, parent{std::move(parent)} {}
|
||||
|
||||
template <class U, class... Args>
|
||||
static ASTNode Make(ASTNode parent, Args&&... args) {
|
||||
return std::make_shared<ASTBase>(std::move(parent),
|
||||
ASTData(U(std::forward<Args>(args)...)));
|
||||
}
|
||||
|
||||
void SetParent(ASTNode new_parent) {
|
||||
parent = std::move(new_parent);
|
||||
}
|
||||
|
||||
ASTNode& GetParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
const ASTNode& GetParent() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
u32 GetLevel() const {
|
||||
u32 level = 0;
|
||||
auto next_parent = parent;
|
||||
while (next_parent) {
|
||||
next_parent = next_parent->GetParent();
|
||||
level++;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
ASTData* GetInnerData() {
|
||||
return &data;
|
||||
}
|
||||
|
||||
const ASTData* GetInnerData() const {
|
||||
return &data;
|
||||
}
|
||||
|
||||
ASTNode GetNext() const {
|
||||
return next;
|
||||
}
|
||||
|
||||
ASTNode GetPrevious() const {
|
||||
return previous;
|
||||
}
|
||||
|
||||
ASTZipper& GetManager() {
|
||||
return *manager;
|
||||
}
|
||||
|
||||
const ASTZipper& GetManager() const {
|
||||
return *manager;
|
||||
}
|
||||
|
||||
std::optional<u32> GetGotoLabel() const {
|
||||
auto inner = std::get_if<ASTGoto>(&data);
|
||||
if (inner) {
|
||||
return {inner->label};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Expr GetGotoCondition() const {
|
||||
auto inner = std::get_if<ASTGoto>(&data);
|
||||
if (inner) {
|
||||
return inner->condition;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MarkLabelUnused() {
|
||||
auto inner = std::get_if<ASTLabel>(&data);
|
||||
if (inner) {
|
||||
inner->unused = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsLabelUnused() const {
|
||||
auto inner = std::get_if<ASTLabel>(&data);
|
||||
if (inner) {
|
||||
return inner->unused;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<u32> GetLabelIndex() const {
|
||||
auto inner = std::get_if<ASTLabel>(&data);
|
||||
if (inner) {
|
||||
return {inner->index};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Expr GetIfCondition() const {
|
||||
auto inner = std::get_if<ASTIfThen>(&data);
|
||||
if (inner) {
|
||||
return inner->condition;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SetGotoCondition(Expr new_condition) {
|
||||
auto inner = std::get_if<ASTGoto>(&data);
|
||||
if (inner) {
|
||||
inner->condition = std::move(new_condition);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsIfThen() const {
|
||||
return std::holds_alternative<ASTIfThen>(data);
|
||||
}
|
||||
|
||||
bool IsIfElse() const {
|
||||
return std::holds_alternative<ASTIfElse>(data);
|
||||
}
|
||||
|
||||
bool IsBlockEncoded() const {
|
||||
return std::holds_alternative<ASTBlockEncoded>(data);
|
||||
}
|
||||
|
||||
void TransformBlockEncoded(NodeBlock&& nodes) {
|
||||
data = ASTBlockDecoded(std::move(nodes));
|
||||
}
|
||||
|
||||
bool IsLoop() const {
|
||||
return std::holds_alternative<ASTDoWhile>(data);
|
||||
}
|
||||
|
||||
ASTZipper* GetSubNodes() {
|
||||
if (std::holds_alternative<ASTProgram>(data)) {
|
||||
return &std::get_if<ASTProgram>(&data)->nodes;
|
||||
}
|
||||
if (std::holds_alternative<ASTIfThen>(data)) {
|
||||
return &std::get_if<ASTIfThen>(&data)->nodes;
|
||||
}
|
||||
if (std::holds_alternative<ASTIfElse>(data)) {
|
||||
return &std::get_if<ASTIfElse>(&data)->nodes;
|
||||
}
|
||||
if (std::holds_alternative<ASTDoWhile>(data)) {
|
||||
return &std::get_if<ASTDoWhile>(&data)->nodes;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
next.reset();
|
||||
previous.reset();
|
||||
parent.reset();
|
||||
manager = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ASTZipper;
|
||||
|
||||
ASTData data;
|
||||
ASTNode parent{};
|
||||
ASTNode next{};
|
||||
ASTNode previous{};
|
||||
ASTZipper* manager{};
|
||||
};
|
||||
|
||||
class ASTManager final {
|
||||
public:
|
||||
ASTManager(bool full_decompile, bool disable_else_derivation);
|
||||
~ASTManager();
|
||||
|
||||
ASTManager(const ASTManager& o) = delete;
|
||||
ASTManager& operator=(const ASTManager& other) = delete;
|
||||
|
||||
ASTManager(ASTManager&& other) noexcept = default;
|
||||
ASTManager& operator=(ASTManager&& other) noexcept = default;
|
||||
|
||||
void Init();
|
||||
|
||||
void DeclareLabel(u32 address);
|
||||
|
||||
void InsertLabel(u32 address);
|
||||
|
||||
void InsertGoto(Expr condition, u32 address);
|
||||
|
||||
void InsertBlock(u32 start_address, u32 end_address);
|
||||
|
||||
void InsertReturn(Expr condition, bool kills);
|
||||
|
||||
std::string Print();
|
||||
|
||||
void Decompile();
|
||||
|
||||
void ShowCurrentState(std::string_view state);
|
||||
|
||||
void SanityCheck();
|
||||
|
||||
void Clear();
|
||||
|
||||
bool IsFullyDecompiled() const {
|
||||
if (full_decompile) {
|
||||
return gotos.empty();
|
||||
}
|
||||
|
||||
for (ASTNode goto_node : gotos) {
|
||||
auto label_index = goto_node->GetGotoLabel();
|
||||
if (!label_index) {
|
||||
return false;
|
||||
}
|
||||
ASTNode glabel = labels[*label_index];
|
||||
if (IsBackwardsJump(goto_node, glabel)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ASTNode GetProgram() const {
|
||||
return main_node;
|
||||
}
|
||||
|
||||
u32 GetVariables() const {
|
||||
return variables;
|
||||
}
|
||||
|
||||
const std::vector<ASTNode>& GetLabels() const {
|
||||
return labels;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const;
|
||||
|
||||
bool IndirectlyRelated(const ASTNode& first, const ASTNode& second) const;
|
||||
|
||||
bool DirectlyRelated(const ASTNode& first, const ASTNode& second) const;
|
||||
|
||||
void EncloseDoWhile(ASTNode goto_node, ASTNode label);
|
||||
|
||||
void EncloseIfThen(ASTNode goto_node, ASTNode label);
|
||||
|
||||
void MoveOutward(ASTNode goto_node);
|
||||
|
||||
u32 NewVariable() {
|
||||
return variables++;
|
||||
}
|
||||
|
||||
bool full_decompile{};
|
||||
bool disable_else_derivation{};
|
||||
std::unordered_map<u32, u32> labels_map{};
|
||||
u32 labels_count{};
|
||||
std::vector<ASTNode> labels{};
|
||||
std::list<ASTNode> gotos{};
|
||||
u32 variables{};
|
||||
ASTProgram* program{};
|
||||
ASTNode main_node{};
|
||||
Expr false_condition{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
26
src/video_core/shader/compiler_settings.cpp
Normal file
26
src/video_core/shader/compiler_settings.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/shader/compiler_settings.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
std::string CompileDepthAsString(const CompileDepth cd) {
|
||||
switch (cd) {
|
||||
case CompileDepth::BruteForce:
|
||||
return "Brute Force Compile";
|
||||
case CompileDepth::FlowStack:
|
||||
return "Simple Flow Stack Mode";
|
||||
case CompileDepth::NoFlowStack:
|
||||
return "Remove Flow Stack";
|
||||
case CompileDepth::DecompileBackwards:
|
||||
return "Decompile Backward Jumps";
|
||||
case CompileDepth::FullDecompile:
|
||||
return "Full Decompilation";
|
||||
default:
|
||||
return "Unknown Compiler Process";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
26
src/video_core/shader/compiler_settings.h
Normal file
26
src/video_core/shader/compiler_settings.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
enum class CompileDepth : u32 {
|
||||
BruteForce = 0,
|
||||
FlowStack = 1,
|
||||
NoFlowStack = 2,
|
||||
DecompileBackwards = 3,
|
||||
FullDecompile = 4,
|
||||
};
|
||||
|
||||
std::string CompileDepthAsString(CompileDepth cd);
|
||||
|
||||
struct CompilerSettings {
|
||||
CompileDepth depth{CompileDepth::NoFlowStack};
|
||||
bool disable_else_derivation{true};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -4,13 +4,14 @@
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/control_flow.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
@@ -64,12 +65,13 @@ struct CFGRebuildState {
|
||||
std::list<u32> inspect_queries{};
|
||||
std::list<Query> queries{};
|
||||
std::unordered_map<u32, u32> registered{};
|
||||
std::unordered_set<u32> labels{};
|
||||
std::set<u32> labels{};
|
||||
std::map<u32, u32> ssy_labels{};
|
||||
std::map<u32, u32> pbk_labels{};
|
||||
std::unordered_map<u32, BlockStack> stacks{};
|
||||
const ProgramCode& program_code;
|
||||
const std::size_t program_size;
|
||||
ASTManager* manager;
|
||||
};
|
||||
|
||||
enum class BlockCollision : u32 { None, Found, Inside };
|
||||
@@ -415,38 +417,133 @@ bool TryQuery(CFGRebuildState& state) {
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
||||
std::size_t program_size, u32 start_address) {
|
||||
CFGRebuildState state{program_code, program_size, start_address};
|
||||
void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
|
||||
const auto get_expr = ([&](const Condition& cond) -> Expr {
|
||||
Expr result{};
|
||||
if (cond.cc != ConditionCode::T) {
|
||||
result = MakeExpr<ExprCondCode>(cond.cc);
|
||||
}
|
||||
if (cond.predicate != Pred::UnusedIndex) {
|
||||
u32 pred = static_cast<u32>(cond.predicate);
|
||||
bool negate = false;
|
||||
if (pred > 7) {
|
||||
negate = true;
|
||||
pred -= 8;
|
||||
}
|
||||
Expr extra = MakeExpr<ExprPredicate>(pred);
|
||||
if (negate) {
|
||||
extra = MakeExpr<ExprNot>(extra);
|
||||
}
|
||||
if (result) {
|
||||
return MakeExpr<ExprAnd>(extra, result);
|
||||
}
|
||||
return extra;
|
||||
}
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return MakeExpr<ExprBoolean>(true);
|
||||
});
|
||||
if (branch.address < 0) {
|
||||
if (branch.kill) {
|
||||
mm.InsertReturn(get_expr(branch.condition), true);
|
||||
return;
|
||||
}
|
||||
mm.InsertReturn(get_expr(branch.condition), false);
|
||||
return;
|
||||
}
|
||||
mm.InsertGoto(get_expr(branch.condition), branch.address);
|
||||
}
|
||||
|
||||
void DecompileShader(CFGRebuildState& state) {
|
||||
state.manager->Init();
|
||||
for (auto label : state.labels) {
|
||||
state.manager->DeclareLabel(label);
|
||||
}
|
||||
for (auto& block : state.block_info) {
|
||||
if (state.labels.count(block.start) != 0) {
|
||||
state.manager->InsertLabel(block.start);
|
||||
}
|
||||
u32 end = block.branch.ignore ? block.end + 1 : block.end;
|
||||
state.manager->InsertBlock(block.start, end);
|
||||
if (!block.branch.ignore) {
|
||||
InsertBranch(*state.manager, block.branch);
|
||||
}
|
||||
}
|
||||
state.manager->Decompile();
|
||||
}
|
||||
|
||||
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
|
||||
u32 start_address,
|
||||
const CompilerSettings& settings) {
|
||||
auto result_out = std::make_unique<ShaderCharacteristics>();
|
||||
if (settings.depth == CompileDepth::BruteForce) {
|
||||
result_out->settings.depth = CompileDepth::BruteForce;
|
||||
return result_out;
|
||||
}
|
||||
|
||||
CFGRebuildState state{program_code, program_size, start_address};
|
||||
// Inspect Code and generate blocks
|
||||
state.labels.clear();
|
||||
state.labels.emplace(start_address);
|
||||
state.inspect_queries.push_back(state.start);
|
||||
while (!state.inspect_queries.empty()) {
|
||||
if (!TryInspectAddress(state)) {
|
||||
return {};
|
||||
result_out->settings.depth = CompileDepth::BruteForce;
|
||||
return result_out;
|
||||
}
|
||||
}
|
||||
|
||||
// Decompile Stacks
|
||||
state.queries.push_back(Query{state.start, {}, {}});
|
||||
bool decompiled = true;
|
||||
while (!state.queries.empty()) {
|
||||
if (!TryQuery(state)) {
|
||||
decompiled = false;
|
||||
break;
|
||||
bool use_flow_stack = true;
|
||||
|
||||
bool decompiled = false;
|
||||
|
||||
if (settings.depth != CompileDepth::FlowStack) {
|
||||
// Decompile Stacks
|
||||
state.queries.push_back(Query{state.start, {}, {}});
|
||||
decompiled = true;
|
||||
while (!state.queries.empty()) {
|
||||
if (!TryQuery(state)) {
|
||||
decompiled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use_flow_stack = !decompiled;
|
||||
|
||||
// Sort and organize results
|
||||
std::sort(state.block_info.begin(), state.block_info.end(),
|
||||
[](const BlockInfo& a, const BlockInfo& b) { return a.start < b.start; });
|
||||
ShaderCharacteristics result_out{};
|
||||
result_out.decompilable = decompiled;
|
||||
result_out.start = start_address;
|
||||
result_out.end = start_address;
|
||||
for (const auto& block : state.block_info) {
|
||||
[](const BlockInfo& a, const BlockInfo& b) -> bool { return a.start < b.start; });
|
||||
if (decompiled && settings.depth != CompileDepth::NoFlowStack) {
|
||||
ASTManager manager{settings.depth != CompileDepth::DecompileBackwards,
|
||||
settings.disable_else_derivation};
|
||||
state.manager = &manager;
|
||||
DecompileShader(state);
|
||||
decompiled = state.manager->IsFullyDecompiled();
|
||||
if (!decompiled) {
|
||||
if (settings.depth == CompileDepth::FullDecompile) {
|
||||
LOG_CRITICAL(HW_GPU, "Failed to remove all the gotos!:");
|
||||
} else {
|
||||
LOG_CRITICAL(HW_GPU, "Failed to remove all backward gotos!:");
|
||||
}
|
||||
state.manager->ShowCurrentState("Of Shader");
|
||||
state.manager->Clear();
|
||||
} else {
|
||||
auto characteristics = std::make_unique<ShaderCharacteristics>();
|
||||
characteristics->start = start_address;
|
||||
characteristics->settings.depth = settings.depth;
|
||||
characteristics->manager = std::move(manager);
|
||||
characteristics->end = state.block_info.back().end + 1;
|
||||
return characteristics;
|
||||
}
|
||||
}
|
||||
|
||||
result_out->start = start_address;
|
||||
result_out->settings.depth =
|
||||
use_flow_stack ? CompileDepth::FlowStack : CompileDepth::NoFlowStack;
|
||||
result_out->blocks.clear();
|
||||
for (auto& block : state.block_info) {
|
||||
ShaderBlock new_block{};
|
||||
new_block.start = block.start;
|
||||
new_block.end = block.end;
|
||||
@@ -456,26 +553,26 @@ std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
||||
new_block.branch.kills = block.branch.kill;
|
||||
new_block.branch.address = block.branch.address;
|
||||
}
|
||||
result_out.end = std::max(result_out.end, block.end);
|
||||
result_out.blocks.push_back(new_block);
|
||||
result_out->end = std::max(result_out->end, block.end);
|
||||
result_out->blocks.push_back(new_block);
|
||||
}
|
||||
if (result_out.decompilable) {
|
||||
result_out.labels = std::move(state.labels);
|
||||
return {std::move(result_out)};
|
||||
if (!use_flow_stack) {
|
||||
result_out->labels = std::move(state.labels);
|
||||
return result_out;
|
||||
}
|
||||
|
||||
// If it's not decompilable, merge the unlabelled blocks together
|
||||
auto back = result_out.blocks.begin();
|
||||
auto back = result_out->blocks.begin();
|
||||
auto next = std::next(back);
|
||||
while (next != result_out.blocks.end()) {
|
||||
while (next != result_out->blocks.end()) {
|
||||
if (state.labels.count(next->start) == 0 && next->start == back->end + 1) {
|
||||
back->end = next->end;
|
||||
next = result_out.blocks.erase(next);
|
||||
next = result_out->blocks.erase(next);
|
||||
continue;
|
||||
}
|
||||
back = next;
|
||||
++next;
|
||||
}
|
||||
return {std::move(result_out)};
|
||||
|
||||
return result_out;
|
||||
}
|
||||
} // namespace VideoCommon::Shader
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <set>
|
||||
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/compiler_settings.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
@@ -67,13 +69,15 @@ struct ShaderBlock {
|
||||
|
||||
struct ShaderCharacteristics {
|
||||
std::list<ShaderBlock> blocks{};
|
||||
bool decompilable{};
|
||||
std::set<u32> labels{};
|
||||
u32 start{};
|
||||
u32 end{};
|
||||
std::unordered_set<u32> labels{};
|
||||
ASTManager manager{true, true};
|
||||
CompilerSettings settings{};
|
||||
};
|
||||
|
||||
std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
||||
std::size_t program_size, u32 start_address);
|
||||
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
|
||||
u32 start_address,
|
||||
const CompilerSettings& settings);
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
|
||||
@@ -35,58 +35,138 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
|
||||
|
||||
} // namespace
|
||||
|
||||
class ASTDecoder {
|
||||
public:
|
||||
ASTDecoder(ShaderIR& ir) : ir(ir) {}
|
||||
|
||||
void operator()(ASTProgram& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(ASTIfThen& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(ASTIfElse& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(ASTBlockEncoded& ast) {}
|
||||
|
||||
void operator()(ASTBlockDecoded& ast) {}
|
||||
|
||||
void operator()(ASTVarSet& ast) {}
|
||||
|
||||
void operator()(ASTLabel& ast) {}
|
||||
|
||||
void operator()(ASTGoto& ast) {}
|
||||
|
||||
void operator()(ASTDoWhile& ast) {
|
||||
ASTNode current = ast.nodes.GetFirst();
|
||||
while (current) {
|
||||
Visit(current);
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(ASTReturn& ast) {}
|
||||
|
||||
void operator()(ASTBreak& ast) {}
|
||||
|
||||
void Visit(ASTNode& node) {
|
||||
std::visit(*this, *node->GetInnerData());
|
||||
if (node->IsBlockEncoded()) {
|
||||
auto block = std::get_if<ASTBlockEncoded>(node->GetInnerData());
|
||||
NodeBlock bb = ir.DecodeRange(block->start, block->end);
|
||||
node->TransformBlockEncoded(std::move(bb));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ShaderIR& ir;
|
||||
};
|
||||
|
||||
void ShaderIR::Decode() {
|
||||
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
|
||||
|
||||
disable_flow_stack = false;
|
||||
const auto info = ScanFlow(program_code, program_size, main_offset);
|
||||
if (info) {
|
||||
const auto& shader_info = *info;
|
||||
coverage_begin = shader_info.start;
|
||||
coverage_end = shader_info.end;
|
||||
if (shader_info.decompilable) {
|
||||
disable_flow_stack = true;
|
||||
const auto insert_block = [this](NodeBlock& nodes, u32 label) {
|
||||
if (label == static_cast<u32>(exit_branch)) {
|
||||
return;
|
||||
}
|
||||
basic_blocks.insert({label, nodes});
|
||||
};
|
||||
const auto& blocks = shader_info.blocks;
|
||||
NodeBlock current_block;
|
||||
u32 current_label = static_cast<u32>(exit_branch);
|
||||
for (auto& block : blocks) {
|
||||
if (shader_info.labels.count(block.start) != 0) {
|
||||
insert_block(current_block, current_label);
|
||||
current_block.clear();
|
||||
current_label = block.start;
|
||||
}
|
||||
if (!block.ignore_branch) {
|
||||
DecodeRangeInner(current_block, block.start, block.end);
|
||||
InsertControlFlow(current_block, block);
|
||||
} else {
|
||||
DecodeRangeInner(current_block, block.start, block.end + 1);
|
||||
}
|
||||
}
|
||||
insert_block(current_block, current_label);
|
||||
return;
|
||||
}
|
||||
LOG_WARNING(HW_GPU, "Flow Stack Removing Failed! Falling back to old method");
|
||||
// we can't decompile it, fallback to standard method
|
||||
decompiled = false;
|
||||
auto info = ScanFlow(program_code, program_size, main_offset, settings);
|
||||
auto& shader_info = *info;
|
||||
coverage_begin = shader_info.start;
|
||||
coverage_end = shader_info.end;
|
||||
switch (shader_info.settings.depth) {
|
||||
case CompileDepth::FlowStack: {
|
||||
for (const auto& block : shader_info.blocks) {
|
||||
basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)});
|
||||
}
|
||||
return;
|
||||
break;
|
||||
}
|
||||
LOG_WARNING(HW_GPU, "Flow Analysis Failed! Falling back to brute force compiling");
|
||||
|
||||
// Now we need to deal with an undecompilable shader. We need to brute force
|
||||
// a shader that captures every position.
|
||||
coverage_begin = main_offset;
|
||||
const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
|
||||
coverage_end = shader_end;
|
||||
for (u32 label = main_offset; label < shader_end; label++) {
|
||||
basic_blocks.insert({label, DecodeRange(label, label + 1)});
|
||||
case CompileDepth::NoFlowStack: {
|
||||
disable_flow_stack = true;
|
||||
const auto insert_block = [this](NodeBlock& nodes, u32 label) {
|
||||
if (label == static_cast<u32>(exit_branch)) {
|
||||
return;
|
||||
}
|
||||
basic_blocks.insert({label, nodes});
|
||||
};
|
||||
const auto& blocks = shader_info.blocks;
|
||||
NodeBlock current_block;
|
||||
u32 current_label = static_cast<u32>(exit_branch);
|
||||
for (auto& block : blocks) {
|
||||
if (shader_info.labels.count(block.start) != 0) {
|
||||
insert_block(current_block, current_label);
|
||||
current_block.clear();
|
||||
current_label = block.start;
|
||||
}
|
||||
if (!block.ignore_branch) {
|
||||
DecodeRangeInner(current_block, block.start, block.end);
|
||||
InsertControlFlow(current_block, block);
|
||||
} else {
|
||||
DecodeRangeInner(current_block, block.start, block.end + 1);
|
||||
}
|
||||
}
|
||||
insert_block(current_block, current_label);
|
||||
break;
|
||||
}
|
||||
case CompileDepth::DecompileBackwards:
|
||||
case CompileDepth::FullDecompile: {
|
||||
program_manager = std::move(shader_info.manager);
|
||||
disable_flow_stack = true;
|
||||
decompiled = true;
|
||||
ASTDecoder decoder{*this};
|
||||
ASTNode program = GetASTProgram();
|
||||
decoder.Visit(program);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unknown decompilation mode!");
|
||||
[[fallthrough]];
|
||||
case CompileDepth::BruteForce: {
|
||||
coverage_begin = main_offset;
|
||||
const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
|
||||
coverage_end = shader_end;
|
||||
for (u32 label = main_offset; label < shader_end; label++) {
|
||||
basic_blocks.insert({label, DecodeRange(label, label + 1)});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (settings.depth != shader_info.settings.depth) {
|
||||
LOG_WARNING(
|
||||
HW_GPU, "Decompiling to this setting \"{}\" failed, downgrading to this setting \"{}\"",
|
||||
CompileDepthAsString(settings.depth), CompileDepthAsString(shader_info.settings.depth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
93
src/video_core/shader/expr.cpp
Normal file
93
src/video_core/shader/expr.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "video_core/shader/expr.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
namespace {
|
||||
bool ExprIsBoolean(const Expr& expr) {
|
||||
return std::holds_alternative<ExprBoolean>(*expr);
|
||||
}
|
||||
|
||||
bool ExprBooleanGet(const Expr& expr) {
|
||||
return std::get_if<ExprBoolean>(expr.get())->value;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
bool ExprAnd::operator==(const ExprAnd& b) const {
|
||||
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
|
||||
}
|
||||
|
||||
bool ExprAnd::operator!=(const ExprAnd& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
bool ExprOr::operator==(const ExprOr& b) const {
|
||||
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
|
||||
}
|
||||
|
||||
bool ExprOr::operator!=(const ExprOr& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
bool ExprNot::operator==(const ExprNot& b) const {
|
||||
return *operand1 == *b.operand1;
|
||||
}
|
||||
|
||||
bool ExprNot::operator!=(const ExprNot& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
Expr MakeExprNot(Expr first) {
|
||||
if (std::holds_alternative<ExprNot>(*first)) {
|
||||
return std::get_if<ExprNot>(first.get())->operand1;
|
||||
}
|
||||
return MakeExpr<ExprNot>(std::move(first));
|
||||
}
|
||||
|
||||
Expr MakeExprAnd(Expr first, Expr second) {
|
||||
if (ExprIsBoolean(first)) {
|
||||
return ExprBooleanGet(first) ? second : first;
|
||||
}
|
||||
if (ExprIsBoolean(second)) {
|
||||
return ExprBooleanGet(second) ? first : second;
|
||||
}
|
||||
return MakeExpr<ExprAnd>(std::move(first), std::move(second));
|
||||
}
|
||||
|
||||
Expr MakeExprOr(Expr first, Expr second) {
|
||||
if (ExprIsBoolean(first)) {
|
||||
return ExprBooleanGet(first) ? first : second;
|
||||
}
|
||||
if (ExprIsBoolean(second)) {
|
||||
return ExprBooleanGet(second) ? second : first;
|
||||
}
|
||||
return MakeExpr<ExprOr>(std::move(first), std::move(second));
|
||||
}
|
||||
|
||||
bool ExprAreEqual(const Expr& first, const Expr& second) {
|
||||
return (*first) == (*second);
|
||||
}
|
||||
|
||||
bool ExprAreOpposite(const Expr& first, const Expr& second) {
|
||||
if (std::holds_alternative<ExprNot>(*first)) {
|
||||
return ExprAreEqual(std::get_if<ExprNot>(first.get())->operand1, second);
|
||||
}
|
||||
if (std::holds_alternative<ExprNot>(*second)) {
|
||||
return ExprAreEqual(std::get_if<ExprNot>(second.get())->operand1, first);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExprIsTrue(const Expr& first) {
|
||||
if (ExprIsBoolean(first)) {
|
||||
return ExprBooleanGet(first);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
139
src/video_core/shader/expr.h
Normal file
139
src/video_core/shader/expr.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
using Tegra::Shader::ConditionCode;
|
||||
using Tegra::Shader::Pred;
|
||||
|
||||
class ExprAnd;
|
||||
class ExprBoolean;
|
||||
class ExprCondCode;
|
||||
class ExprNot;
|
||||
class ExprOr;
|
||||
class ExprPredicate;
|
||||
class ExprVar;
|
||||
|
||||
using ExprData =
|
||||
std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
|
||||
using Expr = std::shared_ptr<ExprData>;
|
||||
|
||||
class ExprAnd final {
|
||||
public:
|
||||
explicit ExprAnd(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
|
||||
|
||||
bool operator==(const ExprAnd& b) const;
|
||||
bool operator!=(const ExprAnd& b) const;
|
||||
|
||||
Expr operand1;
|
||||
Expr operand2;
|
||||
};
|
||||
|
||||
class ExprOr final {
|
||||
public:
|
||||
explicit ExprOr(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
|
||||
|
||||
bool operator==(const ExprOr& b) const;
|
||||
bool operator!=(const ExprOr& b) const;
|
||||
|
||||
Expr operand1;
|
||||
Expr operand2;
|
||||
};
|
||||
|
||||
class ExprNot final {
|
||||
public:
|
||||
explicit ExprNot(Expr a) : operand1{std::move(a)} {}
|
||||
|
||||
bool operator==(const ExprNot& b) const;
|
||||
bool operator!=(const ExprNot& b) const;
|
||||
|
||||
Expr operand1;
|
||||
};
|
||||
|
||||
class ExprVar final {
|
||||
public:
|
||||
explicit ExprVar(u32 index) : var_index{index} {}
|
||||
|
||||
bool operator==(const ExprVar& b) const {
|
||||
return var_index == b.var_index;
|
||||
}
|
||||
|
||||
bool operator!=(const ExprVar& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
u32 var_index;
|
||||
};
|
||||
|
||||
class ExprPredicate final {
|
||||
public:
|
||||
explicit ExprPredicate(u32 predicate) : predicate{predicate} {}
|
||||
|
||||
bool operator==(const ExprPredicate& b) const {
|
||||
return predicate == b.predicate;
|
||||
}
|
||||
|
||||
bool operator!=(const ExprPredicate& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
u32 predicate;
|
||||
};
|
||||
|
||||
class ExprCondCode final {
|
||||
public:
|
||||
explicit ExprCondCode(ConditionCode cc) : cc{cc} {}
|
||||
|
||||
bool operator==(const ExprCondCode& b) const {
|
||||
return cc == b.cc;
|
||||
}
|
||||
|
||||
bool operator!=(const ExprCondCode& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
ConditionCode cc;
|
||||
};
|
||||
|
||||
class ExprBoolean final {
|
||||
public:
|
||||
explicit ExprBoolean(bool val) : value{val} {}
|
||||
|
||||
bool operator==(const ExprBoolean& b) const {
|
||||
return value == b.value;
|
||||
}
|
||||
|
||||
bool operator!=(const ExprBoolean& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
|
||||
bool value;
|
||||
};
|
||||
|
||||
template <typename T, typename... Args>
|
||||
Expr MakeExpr(Args&&... args) {
|
||||
static_assert(std::is_convertible_v<T, ExprData>);
|
||||
return std::make_shared<ExprData>(T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
bool ExprAreEqual(const Expr& first, const Expr& second);
|
||||
|
||||
bool ExprAreOpposite(const Expr& first, const Expr& second);
|
||||
|
||||
Expr MakeExprNot(Expr first);
|
||||
|
||||
Expr MakeExprAnd(Expr first, Expr second);
|
||||
|
||||
Expr MakeExprOr(Expr first, Expr second);
|
||||
|
||||
bool ExprIsTrue(const Expr& first);
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -22,8 +22,10 @@ using Tegra::Shader::PredCondition;
|
||||
using Tegra::Shader::PredOperation;
|
||||
using Tegra::Shader::Register;
|
||||
|
||||
ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size)
|
||||
: program_code{program_code}, main_offset{main_offset}, program_size{size} {
|
||||
ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size,
|
||||
CompilerSettings settings)
|
||||
: program_code{program_code}, main_offset{main_offset}, program_size{size}, basic_blocks{},
|
||||
program_manager{true, true}, settings{settings} {
|
||||
Decode();
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
|
||||
return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer));
|
||||
}
|
||||
|
||||
Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) {
|
||||
Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const {
|
||||
const Node node = MakeNode<InternalFlagNode>(flag);
|
||||
if (negated) {
|
||||
return Operation(OperationCode::LogicalNegate, node);
|
||||
@@ -367,13 +369,13 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
|
||||
return op->second;
|
||||
}
|
||||
|
||||
Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) {
|
||||
Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const {
|
||||
switch (cc) {
|
||||
case Tegra::Shader::ConditionCode::NEU:
|
||||
return GetInternalFlag(InternalFlag::Zero, true);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc));
|
||||
return GetPredicate(static_cast<u64>(Pred::NeverExecute));
|
||||
return MakeNode<PredicateNode>(Pred::NeverExecute, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/engines/shader_header.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/compiler_settings.h"
|
||||
#include "video_core/shader/node.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
@@ -64,7 +66,8 @@ struct GlobalMemoryUsage {
|
||||
|
||||
class ShaderIR final {
|
||||
public:
|
||||
explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size);
|
||||
explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size,
|
||||
CompilerSettings settings);
|
||||
~ShaderIR();
|
||||
|
||||
const std::map<u32, NodeBlock>& GetBasicBlocks() const {
|
||||
@@ -144,11 +147,31 @@ public:
|
||||
return disable_flow_stack;
|
||||
}
|
||||
|
||||
bool IsDecompiled() const {
|
||||
return decompiled;
|
||||
}
|
||||
|
||||
const ASTManager& GetASTManager() const {
|
||||
return program_manager;
|
||||
}
|
||||
|
||||
ASTNode GetASTProgram() const {
|
||||
return program_manager.GetProgram();
|
||||
}
|
||||
|
||||
u32 GetASTNumVariables() const {
|
||||
return program_manager.GetVariables();
|
||||
}
|
||||
|
||||
u32 ConvertAddressToNvidiaSpace(const u32 address) const {
|
||||
return (address - main_offset) * sizeof(Tegra::Shader::Instruction);
|
||||
}
|
||||
|
||||
/// Returns a condition code evaluated from internal flags
|
||||
Node GetConditionCode(Tegra::Shader::ConditionCode cc) const;
|
||||
|
||||
private:
|
||||
friend class ASTDecoder;
|
||||
void Decode();
|
||||
|
||||
NodeBlock DecodeRange(u32 begin, u32 end);
|
||||
@@ -213,7 +236,7 @@ private:
|
||||
/// Generates a node representing an output attribute. Keeps track of used attributes.
|
||||
Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
|
||||
/// Generates a node representing an internal flag
|
||||
Node GetInternalFlag(InternalFlag flag, bool negated = false);
|
||||
Node GetInternalFlag(InternalFlag flag, bool negated = false) const;
|
||||
/// Generates a node representing a local memory address
|
||||
Node GetLocalMemory(Node address);
|
||||
/// Generates a node representing a shared memory address
|
||||
@@ -271,9 +294,6 @@ private:
|
||||
/// Returns a predicate combiner operation
|
||||
OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation);
|
||||
|
||||
/// Returns a condition code evaluated from internal flags
|
||||
Node GetConditionCode(Tegra::Shader::ConditionCode cc);
|
||||
|
||||
/// Accesses a texture sampler
|
||||
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
|
||||
Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
|
||||
@@ -357,6 +377,7 @@ private:
|
||||
const ProgramCode& program_code;
|
||||
const u32 main_offset;
|
||||
const std::size_t program_size;
|
||||
bool decompiled{};
|
||||
bool disable_flow_stack{};
|
||||
|
||||
u32 coverage_begin{};
|
||||
@@ -364,6 +385,8 @@ private:
|
||||
|
||||
std::map<u32, NodeBlock> basic_blocks;
|
||||
NodeBlock global_code;
|
||||
ASTManager program_manager;
|
||||
CompilerSettings settings{};
|
||||
|
||||
std::set<u32> used_registers;
|
||||
std::set<Tegra::Shader::Pred> used_predicates;
|
||||
|
||||
@@ -224,8 +224,13 @@ public:
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config,
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) {
|
||||
std::lock_guard lock{mutex};
|
||||
std::pair<TSurface, TView> dst_surface = GetFermiSurface(dst_config);
|
||||
std::pair<TSurface, TView> src_surface = GetFermiSurface(src_config);
|
||||
SurfaceParams src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
|
||||
SurfaceParams dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
|
||||
const GPUVAddr src_gpu_addr = src_config.Address();
|
||||
const GPUVAddr dst_gpu_addr = dst_config.Address();
|
||||
DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
|
||||
std::pair<TSurface, TView> dst_surface = GetSurface(dst_gpu_addr, dst_params, true, false);
|
||||
std::pair<TSurface, TView> src_surface = GetSurface(src_gpu_addr, src_params, true, false);
|
||||
ImageBlit(src_surface.second, dst_surface.second, copy_config);
|
||||
dst_surface.first->MarkAsModified(true, Tick());
|
||||
}
|
||||
@@ -357,6 +362,29 @@ private:
|
||||
BufferCopy = 3,
|
||||
};
|
||||
|
||||
enum class DeductionType : u32 {
|
||||
DeductionComplete,
|
||||
DeductionIncomplete,
|
||||
DeductionFailed,
|
||||
};
|
||||
|
||||
struct Deduction {
|
||||
DeductionType type{DeductionType::DeductionFailed};
|
||||
TSurface surface{};
|
||||
|
||||
bool Failed() const {
|
||||
return type == DeductionType::DeductionFailed;
|
||||
}
|
||||
|
||||
bool Incomplete() const {
|
||||
return type == DeductionType::DeductionIncomplete;
|
||||
}
|
||||
|
||||
bool IsDepth() const {
|
||||
return surface->GetSurfaceParams().IsPixelFormatZeta();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* `PickStrategy` takes care of selecting a proper strategy to deal with a texture recycle.
|
||||
* @param overlaps, the overlapping surfaces registered in the cache.
|
||||
@@ -691,6 +719,120 @@ private:
|
||||
MatchTopologyResult::FullMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* `DeduceSurface` gets the starting address and parameters of a candidate surface and tries
|
||||
* to find a matching surface within the cache that's similar to it. If there are many textures
|
||||
* or the texture found if entirely incompatible, it will fail. If no texture is found, the
|
||||
* blit will be unsuccessful.
|
||||
* @param gpu_addr, the starting address of the candidate surface.
|
||||
* @param params, the paremeters on the candidate surface.
|
||||
**/
|
||||
Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
|
||||
const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
|
||||
if (!cache_addr) {
|
||||
Deduction result{};
|
||||
result.type = DeductionType::DeductionFailed;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const auto iter = l1_cache.find(cache_addr); iter != l1_cache.end()) {
|
||||
TSurface& current_surface = iter->second;
|
||||
const auto topological_result = current_surface->MatchesTopology(params);
|
||||
if (topological_result != MatchTopologyResult::FullMatch) {
|
||||
Deduction result{};
|
||||
result.type = DeductionType::DeductionFailed;
|
||||
return result;
|
||||
}
|
||||
const auto struct_result = current_surface->MatchesStructure(params);
|
||||
if (struct_result != MatchStructureResult::None &&
|
||||
current_surface->MatchTarget(params.target)) {
|
||||
Deduction result{};
|
||||
result.type = DeductionType::DeductionComplete;
|
||||
result.surface = current_surface;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t candidate_size = params.GetGuestSizeInBytes();
|
||||
auto overlaps{GetSurfacesInRegion(cache_addr, candidate_size)};
|
||||
|
||||
if (overlaps.empty()) {
|
||||
Deduction result{};
|
||||
result.type = DeductionType::DeductionIncomplete;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (overlaps.size() > 1) {
|
||||
Deduction result{};
|
||||
result.type = DeductionType::DeductionFailed;
|
||||
return result;
|
||||
} else {
|
||||
Deduction result{};
|
||||
result.type = DeductionType::DeductionComplete;
|
||||
result.surface = overlaps[0];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `DeduceBestBlit` gets the a source and destination starting address and parameters,
|
||||
* and tries to deduce if they are supposed to be depth textures. If so, their
|
||||
* parameters are modified and fixed into so.
|
||||
* @param gpu_addr, the starting address of the candidate surface.
|
||||
* @param params, the parameters on the candidate surface.
|
||||
**/
|
||||
void DeduceBestBlit(SurfaceParams& src_params, SurfaceParams& dst_params,
|
||||
const GPUVAddr src_gpu_addr, const GPUVAddr dst_gpu_addr) {
|
||||
auto deduced_src = DeduceSurface(src_gpu_addr, src_params);
|
||||
auto deduced_dst = DeduceSurface(src_gpu_addr, src_params);
|
||||
if (deduced_src.Failed() || deduced_dst.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool incomplete_src = deduced_src.Incomplete();
|
||||
const bool incomplete_dst = deduced_dst.Incomplete();
|
||||
|
||||
if (incomplete_src && incomplete_dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool any_incomplete = incomplete_src || incomplete_dst;
|
||||
|
||||
if (!any_incomplete) {
|
||||
if (!(deduced_src.IsDepth() && deduced_dst.IsDepth())) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (incomplete_src && !(deduced_dst.IsDepth())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (incomplete_dst && !(deduced_src.IsDepth())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto inherit_format = ([](SurfaceParams& to, TSurface from) {
|
||||
const SurfaceParams& params = from->GetSurfaceParams();
|
||||
to.pixel_format = params.pixel_format;
|
||||
to.component_type = params.component_type;
|
||||
to.type = params.type;
|
||||
});
|
||||
// Now we got the cases where one or both is Depth and the other is not known
|
||||
if (!incomplete_src) {
|
||||
inherit_format(src_params, deduced_src.surface);
|
||||
} else {
|
||||
inherit_format(src_params, deduced_dst.surface);
|
||||
}
|
||||
if (!incomplete_dst) {
|
||||
inherit_format(dst_params, deduced_dst.surface);
|
||||
} else {
|
||||
inherit_format(dst_params, deduced_src.surface);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params,
|
||||
bool preserve_contents) {
|
||||
auto new_surface{GetUncachedSurface(gpu_addr, params)};
|
||||
|
||||
@@ -66,6 +66,9 @@ add_executable(yuzu
|
||||
configuration/configure_profile_manager.cpp
|
||||
configuration/configure_profile_manager.h
|
||||
configuration/configure_profile_manager.ui
|
||||
configuration/configure_service.cpp
|
||||
configuration/configure_service.h
|
||||
configuration/configure_service.ui
|
||||
configuration/configure_system.cpp
|
||||
configuration/configure_system.h
|
||||
configuration/configure_system.ui
|
||||
@@ -186,6 +189,10 @@ if (YUZU_USE_QT_WEB_ENGINE)
|
||||
target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
|
||||
endif ()
|
||||
|
||||
if (YUZU_ENABLE_BOXCAT)
|
||||
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT)
|
||||
endif ()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
|
||||
endif()
|
||||
|
||||
@@ -525,6 +525,17 @@ void Config::ReadDebuggingValues() {
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::ReadServiceValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Services"));
|
||||
Settings::values.bcat_backend =
|
||||
ReadSetting(QStringLiteral("bcat_backend"), QStringLiteral("boxcat"))
|
||||
.toString()
|
||||
.toStdString();
|
||||
Settings::values.bcat_boxcat_local =
|
||||
ReadSetting(QStringLiteral("bcat_boxcat_local"), false).toBool();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::ReadDisabledAddOnValues() {
|
||||
const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
|
||||
|
||||
@@ -769,6 +780,7 @@ void Config::ReadValues() {
|
||||
ReadMiscellaneousValues();
|
||||
ReadDebuggingValues();
|
||||
ReadWebServiceValues();
|
||||
ReadServiceValues();
|
||||
ReadDisabledAddOnValues();
|
||||
ReadUIValues();
|
||||
}
|
||||
@@ -866,6 +878,7 @@ void Config::SaveValues() {
|
||||
SaveMiscellaneousValues();
|
||||
SaveDebuggingValues();
|
||||
SaveWebServiceValues();
|
||||
SaveServiceValues();
|
||||
SaveDisabledAddOnValues();
|
||||
SaveUIValues();
|
||||
}
|
||||
@@ -963,6 +976,14 @@ void Config::SaveDebuggingValues() {
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::SaveServiceValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Services"));
|
||||
WriteSetting(QStringLiteral("bcat_backend"),
|
||||
QString::fromStdString(Settings::values.bcat_backend), QStringLiteral("null"));
|
||||
WriteSetting(QStringLiteral("bcat_boxcat_local"), Settings::values.bcat_boxcat_local, false);
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::SaveDisabledAddOnValues() {
|
||||
qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ private:
|
||||
void ReadCoreValues();
|
||||
void ReadDataStorageValues();
|
||||
void ReadDebuggingValues();
|
||||
void ReadServiceValues();
|
||||
void ReadDisabledAddOnValues();
|
||||
void ReadMiscellaneousValues();
|
||||
void ReadPathValues();
|
||||
@@ -65,6 +66,7 @@ private:
|
||||
void SaveCoreValues();
|
||||
void SaveDataStorageValues();
|
||||
void SaveDebuggingValues();
|
||||
void SaveServiceValues();
|
||||
void SaveDisabledAddOnValues();
|
||||
void SaveMiscellaneousValues();
|
||||
void SavePathValues();
|
||||
|
||||
@@ -98,6 +98,11 @@
|
||||
<string>Web</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureService" name="serviceTab">
|
||||
<attribute name="title">
|
||||
<string>Services</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -178,6 +183,12 @@
|
||||
<header>configuration/configure_hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureService</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_service.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
||||
@@ -44,6 +44,7 @@ void ConfigureDialog::ApplyConfiguration() {
|
||||
ui->audioTab->ApplyConfiguration();
|
||||
ui->debugTab->ApplyConfiguration();
|
||||
ui->webTab->ApplyConfiguration();
|
||||
ui->serviceTab->ApplyConfiguration();
|
||||
Settings::Apply();
|
||||
Settings::LogSettings();
|
||||
}
|
||||
@@ -74,7 +75,8 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
|
||||
void ConfigureDialog::PopulateSelectionList() {
|
||||
const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
|
||||
{{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
|
||||
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}},
|
||||
{tr("System"),
|
||||
{ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}},
|
||||
{tr("Graphics"), {ui->graphicsTab}},
|
||||
{tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
|
||||
};
|
||||
@@ -108,6 +110,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{ui->webTab, tr("Web")},
|
||||
{ui->gameListTab, tr("Game List")},
|
||||
{ui->filesystemTab, tr("Filesystem")},
|
||||
{ui->serviceTab, tr("Services")},
|
||||
};
|
||||
|
||||
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
|
||||
|
||||
138
src/yuzu/configuration/configure_service.cpp
Normal file
138
src/yuzu/configuration/configure_service.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include "core/hle/service/bcat/backend/boxcat.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_service.h"
|
||||
#include "yuzu/configuration/configure_service.h"
|
||||
|
||||
namespace {
|
||||
QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
|
||||
QString out;
|
||||
|
||||
if (status.header.has_value()) {
|
||||
out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.header));
|
||||
}
|
||||
|
||||
if (status.events.size() == 1) {
|
||||
out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front()));
|
||||
} else {
|
||||
for (const auto& event : status.events) {
|
||||
out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event));
|
||||
}
|
||||
}
|
||||
|
||||
if (status.footer.has_value()) {
|
||||
out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.footer));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigureService::ConfigureService(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->bcat_source->addItem(QStringLiteral("None"));
|
||||
ui->bcat_empty_label->setHidden(true);
|
||||
ui->bcat_empty_header->setHidden(true);
|
||||
|
||||
#ifdef YUZU_ENABLE_BOXCAT
|
||||
ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
|
||||
#endif
|
||||
|
||||
connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureService::OnBCATImplChanged);
|
||||
|
||||
this->SetConfiguration();
|
||||
}
|
||||
|
||||
ConfigureService::~ConfigureService() = default;
|
||||
|
||||
void ConfigureService::ApplyConfiguration() {
|
||||
Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
|
||||
}
|
||||
|
||||
void ConfigureService::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureService::SetConfiguration() {
|
||||
const int index =
|
||||
ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
|
||||
ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
|
||||
}
|
||||
|
||||
std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
|
||||
std::optional<std::string> global;
|
||||
std::map<std::string, Service::BCAT::EventStatus> map;
|
||||
const auto res = Service::BCAT::Boxcat::GetStatus(global, map);
|
||||
|
||||
switch (res) {
|
||||
case Service::BCAT::Boxcat::StatusResult::Success:
|
||||
break;
|
||||
case Service::BCAT::Boxcat::StatusResult::Offline:
|
||||
return {QString{},
|
||||
tr("The boxcat service is offline or you are not connected to the internet.")};
|
||||
case Service::BCAT::Boxcat::StatusResult::ParseError:
|
||||
return {QString{},
|
||||
tr("There was an error while processing the boxcat event data. Contact the yuzu "
|
||||
"developers.")};
|
||||
case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
|
||||
return {QString{},
|
||||
tr("The version of yuzu you are using is either too new or too old for the server. "
|
||||
"Try updating to the latest official release of yuzu.")};
|
||||
}
|
||||
|
||||
if (map.empty()) {
|
||||
return {QStringLiteral("Current Boxcat Events"),
|
||||
tr("There are currently no events on boxcat.")};
|
||||
}
|
||||
|
||||
QString out;
|
||||
|
||||
if (global.has_value()) {
|
||||
out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global));
|
||||
}
|
||||
|
||||
for (const auto& [key, value] : map) {
|
||||
out += QStringLiteral("%1<b>%2</b><br>%3")
|
||||
.arg(out.isEmpty() ? QString{} : QStringLiteral("<br>"))
|
||||
.arg(QString::fromStdString(key))
|
||||
.arg(FormatEventStatusString(value));
|
||||
}
|
||||
return {QStringLiteral("Current Boxcat Events"), std::move(out)};
|
||||
}
|
||||
|
||||
void ConfigureService::OnBCATImplChanged() {
|
||||
#ifdef YUZU_ENABLE_BOXCAT
|
||||
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
|
||||
ui->bcat_empty_header->setHidden(!boxcat);
|
||||
ui->bcat_empty_label->setHidden(!boxcat);
|
||||
ui->bcat_empty_header->setText(QString{});
|
||||
ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
|
||||
|
||||
if (!boxcat)
|
||||
return;
|
||||
|
||||
const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); });
|
||||
|
||||
watcher.setFuture(future);
|
||||
connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this,
|
||||
[this] { OnUpdateBCATEmptyLabel(watcher.result()); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
|
||||
#ifdef YUZU_ENABLE_BOXCAT
|
||||
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
|
||||
if (boxcat) {
|
||||
ui->bcat_empty_header->setText(string.first);
|
||||
ui->bcat_empty_label->setText(string.second);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
34
src/yuzu/configuration/configure_service.h
Normal file
34
src/yuzu/configuration/configure_service.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QFutureWatcher>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureService;
|
||||
}
|
||||
|
||||
class ConfigureService : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureService(QWidget* parent = nullptr);
|
||||
~ConfigureService() override;
|
||||
|
||||
void ApplyConfiguration();
|
||||
void RetranslateUi();
|
||||
|
||||
private:
|
||||
void SetConfiguration();
|
||||
|
||||
std::pair<QString, QString> BCATDownloadEvents();
|
||||
void OnBCATImplChanged();
|
||||
void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
|
||||
|
||||
std::unique_ptr<Ui::ConfigureService> ui;
|
||||
QFutureWatcher<std::pair<QString, QString>> watcher{this};
|
||||
};
|
||||
124
src/yuzu/configuration/configure_service.ui
Normal file
124
src/yuzu/configuration/configure_service.ui
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureService</class>
|
||||
<widget class="QWidget" name="ConfigureService">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>433</width>
|
||||
<height>561</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>BCAT</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>260</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>BCAT Backend</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QLabel" name="bcat_empty_label">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>260</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="bcat_source"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="bcat_empty_header">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user