diff options
150 files changed, 5311 insertions, 662 deletions
| diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh index f538a4081..090ca75f1 100644 --- a/.ci/scripts/linux/docker.sh +++ b/.ci/scripts/linux/docker.sh @@ -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 diff --git a/.ci/scripts/linux/exec.sh b/.ci/scripts/linux/exec.sh index a5a6c34b9..9fafa9208 100644 --- a/.ci/scripts/linux/exec.sh +++ b/.ci/scripts/linux/exec.sh @@ -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 diff --git a/.ci/scripts/merge/apply-patches-by-label-private.py b/.ci/scripts/merge/apply-patches-by-label-private.py new file mode 100644 index 000000000..fe0acd510 --- /dev/null +++ b/.ci/scripts/merge/apply-patches-by-label-private.py @@ -0,0 +1,45 @@ +# Download all pull requests as patches that match a specific label +# Usage: python download-patches-by-label.py <Label to Match> <Root Path Folder to DL to> + +import requests, sys, json, shutil, subprocess, os, traceback + +org = os.getenv("PRIVATEMERGEORG", "yuzu-emu") +repo = os.getenv("PRIVATEMERGEREPO", "yuzu-private") +tagline = sys.argv[3] +user = sys.argv[1] + +dl_list = {} + +TAG_NAME = sys.argv[2] + +def check_individual(repo_id, pr_id): +    url = 'https://%sdev.azure.com/%s/%s/_apis/git/repositories/%s/pullRequests/%s/labels?api-version=5.1-preview.1' % (user, org, repo, repo_id, pr_id) +    response = requests.get(url) +    if (response.ok): +        try: +            js = response.json() +            return any(tag.get('name') == TAG_NAME for tag in js['value']) +        except: +            return False +    return False + +def merge_pr(pn, ref): +    print("Matched PR# %s" % pn) +    print(subprocess.check_output(["git", "fetch", "https://%sdev.azure.com/%s/_git/%s" % (user, org, repo), ref, "-f"])) +    print(subprocess.check_output(["git", "merge", "--squash", 'origin/' + ref.replace('refs/heads/','')])) +    print(subprocess.check_output(["git", "commit", "-m\"Merge %s PR %s\"" % (tagline, pn)])) + +def main(): +    url = 'https://%sdev.azure.com/%s/%s/_apis/git/pullrequests?api-version=5.1' % (user, org, repo) +    response = requests.get(url) +    if (response.ok): +        js = response.json() +        tagged_prs = filter(lambda pr: check_individual(pr['repository']['id'], pr['pullRequestId']), js['value']) +        map(lambda pr: merge_pr(pr['pullRequestId'], pr['sourceRefName']), tagged_prs) + +if __name__ == '__main__': +    try: +        main() +    except: +        traceback.print_exc(file=sys.stdout) +        sys.exit(-1) diff --git a/.ci/scripts/merge/apply-patches-by-label.py b/.ci/scripts/merge/apply-patches-by-label.py index b346001a5..43ed74d7f 100644 --- a/.ci/scripts/merge/apply-patches-by-label.py +++ b/.ci/scripts/merge/apply-patches-by-label.py @@ -1,7 +1,9 @@  # Download all pull requests as patches that match a specific label  # Usage: python download-patches-by-label.py <Label to Match> <Root Path Folder to DL to> -import requests, sys, json, urllib3.request, shutil, subprocess +import requests, sys, json, urllib3.request, shutil, subprocess, os + +tagline = sys.argv[2]  http = urllib3.PoolManager()  dl_list = {} @@ -12,17 +14,23 @@ def check_individual(labels):              return True      return False -try: -    url = 'https://api.github.com/repos/yuzu-emu/yuzu/pulls' +def do_page(page): +    url = 'https://api.github.com/repos/yuzu-emu/yuzu/pulls?page=%s' % page      response = requests.get(url)      if (response.ok):          j = json.loads(response.content) +        if j == []: +            return          for pr in j:              if (check_individual(pr["labels"])):                  pn = pr["number"]                  print("Matched PR# %s" % pn)                  print(subprocess.check_output(["git", "fetch", "https://github.com/yuzu-emu/yuzu.git", "pull/%s/head:pr-%s" % (pn, pn), "-f"]))                  print(subprocess.check_output(["git", "merge", "--squash", "pr-%s" % pn])) -                print(subprocess.check_output(["git", "commit", "-m\"Merge PR %s\"" % pn])) +                print(subprocess.check_output(["git", "commit", "-m\"Merge %s PR %s\"" % (tagline, pn)])) + +try: +    for i in range(1,30): +        do_page(i)  except:      sys.exit(-1) diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index f7093363b..e8f26933a 100644 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -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 diff --git a/.ci/scripts/windows/exec.sh b/.ci/scripts/windows/exec.sh index d6a994856..4155ed5fc 100644 --- a/.ci/scripts/windows/exec.sh +++ b/.ci/scripts/windows/exec.sh @@ -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 diff --git a/.ci/scripts/windows/upload.ps1 b/.ci/scripts/windows/upload.ps1 new file mode 100644 index 000000000..3cb709924 --- /dev/null +++ b/.ci/scripts/windows/upload.ps1 @@ -0,0 +1,38 @@ +$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 " ", "" +$MSVC_TAR = "yuzu-windows-msvc-$GITDATE-$GITREV.tar" -replace " ", "" +$MSVC_TARXZ = "yuzu-windows-msvc-$GITDATE-$GITREV.tar.xz" -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 + +7z a -r -ttar $MSVC_TAR $RELEASE_DIST +7z a -r -txz $MSVC_TARXZ $MSVC_TAR + +Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts" +Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts" +Get-ChildItem . -Filter "*.tar.xz" | Copy-Item -destination "artifacts"
\ No newline at end of file diff --git a/.ci/templates/build-mock.yml b/.ci/templates/build-mock.yml new file mode 100644 index 000000000..0318a0ad8 --- /dev/null +++ b/.ci/templates/build-mock.yml @@ -0,0 +1,5 @@ +steps: +  - script: mkdir artifacts || echo 'X' > artifacts/T1.txt +  - publish: artifacts +    artifact: 'yuzu-$(BuildName)-mock' +    displayName: 'Upload Artifacts'
\ No newline at end of file diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml new file mode 100644 index 000000000..b44a08247 --- /dev/null +++ b/.ci/templates/build-msvc.yml @@ -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' diff --git a/.ci/templates/build-single.yml b/.ci/templates/build-single.yml index 357731eb9..7b27693be 100644 --- a/.ci/templates/build-single.yml +++ b/.ci/templates/build-single.yml @@ -1,20 +1,20 @@  parameters:    artifactSource: 'true'    cache: 'false' +  version: ''  steps:  - task: DockerInstaller@0    displayName: 'Prepare Environment'    inputs:      dockerVersion: '17.09.0-ce' -- ${{ if eq(parameters.cache, 'true') }}: -  - task: CacheBeta@0 -    displayName: 'Cache Build System' -    inputs: -      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 +- task: CacheBeta@0 +  displayName: 'Cache Build System' +  inputs: +    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 ${{ parameters['version'] }}    displayName: 'Build'  - script: chmod a+x ./.ci/scripts/$(ScriptFolder)/upload.sh && RELEASE_NAME=$(BuildName) ./.ci/scripts/$(ScriptFolder)/upload.sh    displayName: 'Package Artifacts' diff --git a/.ci/templates/build-standard.yml b/.ci/templates/build-standard.yml index aa180894e..7422c8346 100644 --- a/.ci/templates/build-standard.yml +++ b/.ci/templates/build-standard.yml @@ -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)
\ No newline at end of file +      cache: $(parameters.cache) +      version: $(parameters.version)
\ No newline at end of file diff --git a/.ci/templates/build-testing.yml b/.ci/templates/build-testing.yml index 4c9625944..30c8aaac3 100644 --- a/.ci/templates/build-testing.yml +++ b/.ci/templates/build-testing.yml @@ -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)
\ No newline at end of file diff --git a/.ci/templates/merge-private.yml b/.ci/templates/merge-private.yml new file mode 100644 index 000000000..f15a74355 --- /dev/null +++ b/.ci/templates/merge-private.yml @@ -0,0 +1,37 @@ +jobs: +- job: merge +  displayName: 'pull requests' +  steps: +  - checkout: self +    submodules: recursive +  - template: ./mergebot-private.yml +    parameters: +      matchLabel: '$(BuildName)-merge' +      matchLabelPublic: '$(PublicBuildName)-merge' +  - task: ArchiveFiles@2 +    displayName: 'Package Source' +    inputs: +      rootFolderOrFile: '$(System.DefaultWorkingDirectory)' +      includeRootFolder: false +      archiveType: '7z' +      archiveFile: '$(Build.ArtifactStagingDirectory)/yuzu-$(BuildName)-source.7z' +  - task: PublishPipelineArtifact@1 +    displayName: 'Upload Artifacts' +    inputs: +      targetPath: '$(Build.ArtifactStagingDirectory)/yuzu-$(BuildName)-source.7z' +      artifact: 'yuzu-$(BuildName)-source' +      replaceExistingArchive: true +- job: upload_source +  displayName: 'upload' +  dependsOn: merge +  steps: +  - template: ./sync-source.yml +    parameters: +      artifactSource: 'true' +      needSubmodules: 'true' +  - script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh +    displayName: 'Apply Git Configuration' +  - script: git remote add other $(GitRepoPushChangesURL) +    displayName: 'Register Repository' +  - script: git push --force other HEAD:$(GitPushBranch) +    displayName: 'Update Code' diff --git a/.ci/templates/merge.yml b/.ci/templates/merge.yml index efc82778a..460dfa1c1 100644 --- a/.ci/templates/merge.yml +++ b/.ci/templates/merge.yml @@ -30,17 +30,7 @@ jobs:        needSubmodules: 'true'    - script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh      displayName: 'Apply Git Configuration' -  - script: git tag -a $(BuildName)-$(Build.BuildId) -m "yuzu $(BuildName) $(Build.BuildNumber) $(Build.DefinitionName)" -    displayName: 'Tag Source'    - script: git remote add other $(GitRepoPushChangesURL)      displayName: 'Register Repository' -  - script: git push --follow-tags --force other HEAD:$(GitPushBranch) +  - script: git push --force other HEAD:$(GitPushBranch)      displayName: 'Update Code' -  - script: git rev-list -n 1 $(BuildName)-$(Build.BuildId) > $(Build.ArtifactStagingDirectory)/tag-commit.sha -    displayName: 'Calculate Release Point' -  - task: PublishPipelineArtifact@1 -    displayName: 'Upload Release Point' -    inputs: -      targetPath: '$(Build.ArtifactStagingDirectory)/tag-commit.sha' -      artifact: 'yuzu-$(BuildName)-release-point' -      replaceExistingArchive: true
\ No newline at end of file diff --git a/.ci/templates/mergebot-private.yml b/.ci/templates/mergebot-private.yml new file mode 100644 index 000000000..f9a40cf61 --- /dev/null +++ b/.ci/templates/mergebot-private.yml @@ -0,0 +1,30 @@ +parameters: +  matchLabel: 'dummy-merge' +  matchLabelPublic: 'dummy-merge' + +steps: +  - script: mkdir $(System.DefaultWorkingDirectory)/patches && pip install requests urllib3 +    displayName: 'Prepare Environment' +  - script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh +    displayName: 'Apply Git Configuration' +  - task: PythonScript@0 +    displayName: 'Discover, Download, and Apply Patches (Mainline)' +    inputs: +      scriptSource: 'filePath' +      scriptPath: '.ci/scripts/merge/apply-patches-by-label.py' +      arguments: '${{ parameters.matchLabelPublic }} $(MergeTaglinePublic) patches-public' +      workingDirectory: '$(System.DefaultWorkingDirectory)' +  - task: PythonScript@0 +    displayName: 'Discover, Download, and Apply Patches (Patreon Public)' +    inputs: +      scriptSource: 'filePath' +      scriptPath: '.ci/scripts/merge/apply-patches-by-label.py' +      arguments: '${{ parameters.matchLabel }} "$(MergeTaglinePrivate) Public" patches-mixed-public' +      workingDirectory: '$(System.DefaultWorkingDirectory)' +  - task: PythonScript@0 +    displayName: 'Discover, Download, and Apply Patches (Patreon Private)' +    inputs: +      scriptSource: 'filePath' +      scriptPath: '.ci/scripts/merge/apply-patches-by-label-private.py' +      arguments: '$(PrivateMergeUser) ${{ parameters.matchLabel }} "$(MergeTaglinePrivate) Private" patches-private' +      workingDirectory: '$(System.DefaultWorkingDirectory)' diff --git a/.ci/templates/mergebot.yml b/.ci/templates/mergebot.yml index 5211efcc6..a4c5c2a28 100644 --- a/.ci/templates/mergebot.yml +++ b/.ci/templates/mergebot.yml @@ -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)' diff --git a/.ci/templates/release-download.yml b/.ci/templates/release-download.yml new file mode 100644 index 000000000..f7e30690f --- /dev/null +++ b/.ci/templates/release-download.yml @@ -0,0 +1,13 @@ +steps: +  - task: DownloadPipelineArtifact@2 +    displayName: 'Download Windows Release' +    inputs: +      artifactName: 'yuzu-$(BuildName)-windows-msvc' +      buildType: 'current' +      targetPath: '$(Build.ArtifactStagingDirectory)' +  - task: DownloadPipelineArtifact@2 +    displayName: 'Download Linux Release' +    inputs: +      artifactName: 'yuzu-$(BuildName)-linux' +      buildType: 'current' +      targetPath: '$(Build.ArtifactStagingDirectory)'
\ No newline at end of file diff --git a/.ci/templates/release-github.yml b/.ci/templates/release-github.yml new file mode 100644 index 000000000..c200954f1 --- /dev/null +++ b/.ci/templates/release-github.yml @@ -0,0 +1,13 @@ +steps: +  - template: ./release-download.yml +  - task: GitHubRelease@0 +    displayName: 'GitHub Release' +    inputs: +      action: 'create' +      title: '$(ReleasePrefix) $(DisplayVersion)' +      assets: '$(Build.ArtifactStagingDirectory)/*' +      gitHubConnection: $(GitHubReleaseConnectionName) +      repositoryName: '$(Build.Repository.Name)' +      target: '$(Build.SourceVersion)' +      tagSource: manual +      tag: $(BuildName)-$(DisplayPrefix)-$(DisplayVersion)
\ No newline at end of file diff --git a/.ci/templates/release-private-tag.yml b/.ci/templates/release-private-tag.yml new file mode 100644 index 000000000..e80d57593 --- /dev/null +++ b/.ci/templates/release-private-tag.yml @@ -0,0 +1,9 @@ +steps: +  - script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh +    displayName: 'Apply Git Configuration' +  - script: git tag -a $(BuildName)-$(DisplayPrefix)-$(DisplayVersion) -m "yuzu $(BuildName) $(Build.BuildNumber) $(Build.DefinitionName) $(DisplayPrefix)-$(DisplayVersion)" +    displayName: 'Tag Source' +  - script: git remote add other $(GitRepoPushChangesURL) +    displayName: 'Register Repository' +  - script: git push other $(BuildName)-$(DisplayPrefix)-$(DisplayVersion) +    displayName: 'Update Code'
\ No newline at end of file diff --git a/.ci/templates/release-universal.yml b/.ci/templates/release-universal.yml new file mode 100644 index 000000000..707697007 --- /dev/null +++ b/.ci/templates/release-universal.yml @@ -0,0 +1,10 @@ +steps: +  - template: ./release-download.yml +  - task: UniversalPackages@0 +    displayName: Publish Artifacts +    inputs: +      command: publish +      publishDirectory: '$(Build.ArtifactStagingDirectory)' +      vstsFeedPublish: 'yuzu-$(BuildName)' +      vstsFeedPackagePublish: 'main' +      packagePublishDescription: 'Yuzu Windows and Linux Executable Packages'
\ No newline at end of file diff --git a/.ci/yuzu-mainline-step1.yml b/.ci/yuzu-mainline-step1.yml new file mode 100644 index 000000000..3fd33d75a --- /dev/null +++ b/.ci/yuzu-mainline-step1.yml @@ -0,0 +1,8 @@ +trigger: +- master + +stages: +- stage: merge +  displayName: 'merge' +  jobs: +  - template: ./templates/merge.yml diff --git a/.ci/yuzu-mainline-step2.yml b/.ci/yuzu-mainline-step2.yml new file mode 100644 index 000000000..5f2dfb3d8 --- /dev/null +++ b/.ci/yuzu-mainline-step2.yml @@ -0,0 +1,68 @@ +trigger: +- master + +variables: +  DisplayVersion: $[counter(variables['DisplayPrefix'], 1)] + +stages: +- stage: format +  displayName: 'format' +  jobs: +  - job: format +    displayName: 'clang' +    pool: +      vmImage: ubuntu-latest +    steps: +    - template: ./templates/format-check.yml +- stage: build +  dependsOn: format +  displayName: 'build' +  jobs: +  - 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 +  - build_win +  jobs: +  - job: github +    displayName: 'github' +    steps: +    - template: ./templates/release-github.yml
\ No newline at end of file diff --git a/.ci/yuzu-mainline.yml b/.ci/yuzu-mainline.yml deleted file mode 100644 index 2930a8564..000000000 --- a/.ci/yuzu-mainline.yml +++ /dev/null @@ -1,25 +0,0 @@ -trigger: -- master - -stages: -- stage: merge -  displayName: 'merge' -  jobs: -  - template: ./templates/merge.yml -- stage: format -  dependsOn: merge -  displayName: 'format' -  jobs: -  - job: format -    displayName: 'clang' -    pool: -      vmImage: ubuntu-latest -    steps: -    - template: ./templates/format-check.yml -- stage: build -  displayName: 'build' -  dependsOn: format -  jobs: -  - template: ./templates/build-standard.yml -    parameters: -      cache: 'true' diff --git a/.ci/yuzu-patreon-step1.yml b/.ci/yuzu-patreon-step1.yml new file mode 100644 index 000000000..cf30397cd --- /dev/null +++ b/.ci/yuzu-patreon-step1.yml @@ -0,0 +1,8 @@ +trigger: +- master + +stages: +- stage: merge +  displayName: 'merge' +  jobs: +  - template: ./templates/merge-private.yml diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml new file mode 100644 index 000000000..35c5fbe36 --- /dev/null +++ b/.ci/yuzu-patreon-step2.yml @@ -0,0 +1,42 @@ +trigger: +- master + +variables: +  DisplayVersion: $[counter(variables['DisplayPrefix'], 1)] + +stages: +- stage: format +  displayName: 'format' +  jobs: +  - job: format +    displayName: 'clang' +    pool: +      vmImage: ubuntu-latest +    steps: +    - template: ./templates/format-check.yml +- stage: build +  dependsOn: format +  displayName: 'build' +  jobs: +  - job: build +    displayName: 'windows-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: $(parameters.cache) +        version: $(DisplayVersion) +- stage: release +  displayName: 'release' +  dependsOn: build +  jobs: +    - job: release +      displayName: 'source' +      steps: +        - template: ./templates/release-private-tag.yml diff --git a/.ci/yuzu-patreon.yml b/.ci/yuzu-patreon.yml deleted file mode 100644 index aa912913d..000000000 --- a/.ci/yuzu-patreon.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -trigger: -- master - -pool: -  vmImage: 'ubuntu-latest' - -steps: -- script: echo Hello, world! -  displayName: 'Run a one-line script' - -- script: | -    echo Add other tasks to build, test, and deploy your project. -    echo See https://aka.ms/yaml -  displayName: 'Run a multi-line script' diff --git a/.gitmodules b/.gitmodules index 3a49c4874..35e0d1240 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index bfa104034..9b3b0d6d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/CMakeModules/GenerateSCMRev.cmake b/CMakeModules/GenerateSCMRev.cmake index a1ace89cb..09eabe2c7 100644 --- a/CMakeModules/GenerateSCMRev.cmake +++ b/CMakeModules/GenerateSCMRev.cmake @@ -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" @@ -1,7 +1,6 @@  yuzu emulator  =============  [](https://travis-ci.org/yuzu-emu/yuzu) -[](https://ci.appveyor.com/project/bunnei/yuzu)  [](https://dev.azure.com/yuzu-emu/yuzu/)  yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/). diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e6fa11a03..3539828b8 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -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 "") diff --git a/externals/fmt b/externals/fmt -Subproject 7512a55aa3ae309587ca89668ef9ec4074a51a1 +Subproject 4b8f8fac96a7819f28f4be523ca10a2d5d8aaaf diff --git a/externals/libzip b/externals/libzip new file mode 160000 +Subproject bd7a8103e96bc6d50164447f6b7b57bb786d8e2 diff --git a/externals/zlib b/externals/zlib new file mode 160000 +Subproject cacf7f1d4e3d44d871b605da3b647f07d718623 diff --git a/license.txt b/license.txt index 2b858f9a7..bf5aec0e6 100644 --- a/license.txt +++ b/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. diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 01abdb3bb..906c486fd 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -10,13 +10,28 @@ if (DEFINED ENV{CI})    elseif(DEFINED ENV{APPVEYOR})      set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})      set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) +  elseif(DEFINED ENV{AZURE}) +    set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME}) +    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, @@ -57,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" diff --git a/src/common/alignment.h b/src/common/alignment.h index 88d5d3a65..cdd4833f8 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -51,7 +51,17 @@ public:      using reference = T&;      using const_reference = const T&; +    using propagate_on_container_copy_assignment = std::true_type; +    using propagate_on_container_move_assignment = std::true_type; +    using propagate_on_container_swap = std::true_type; +    using is_always_equal = std::true_type; +  public: +    constexpr AlignmentAllocator() noexcept = default; + +    template <typename T2> +    constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {} +      pointer address(reference r) noexcept {          return std::addressof(r);      } diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 2d9374783..41167f57a 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -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;          }      } diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index d69038f65..5f126f324 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -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 diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index 666bf0367..563015ec9 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -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 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index be3ffb779..3b1d72cf9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 @@ -501,6 +512,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) diff --git a/src/core/core.cpp b/src/core/core.cpp index bf62085a8..4d0ac72a5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -112,7 +112,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,  }  struct System::Impl {      explicit Impl(System& system) -        : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {} +        : kernel{system}, fs_controller{system}, cpu_core_manager{system}, +          applet_manager{system}, reporter{system} {}      Cpu& CurrentCpuCore() {          return cpu_core_manager.GetCurrentCore(); @@ -343,6 +344,7 @@ struct System::Impl {      Reporter reporter;      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; @@ -651,6 +653,14 @@ bool System::GetExitLock() const {      return impl->exit_lock;  } +void System::SetCurrentProcessBuildID(const CurrentBuildProcessID& id) { +    impl->build_id = id; +} + +const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const { +    return impl->build_id; +} +  System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {      return impl->Init(*this, emu_window);  } diff --git a/src/core/core.h b/src/core/core.h index 0170e0b05..90e7ac607 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -8,7 +8,6 @@  #include <memory>  #include <string> -#include <map>  #include "common/common_types.h"  #include "core/file_sys/vfs_types.h"  #include "core/hle/kernel/object.h" @@ -102,6 +101,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,  class System {  public: +    using CurrentBuildProcessID = std::array<u8, 0x20>; +      System(const System&) = delete;      System& operator=(const System&) = delete; @@ -338,6 +339,10 @@ public:      bool GetExitLock() const; +    void SetCurrentProcessBuildID(const CurrentBuildProcessID& id); + +    const CurrentBuildProcessID& GetCurrentProcessBuildID() const; +  private:      System(); @@ -361,8 +366,4 @@ private:      static System s_instance;  }; -inline Kernel::Process* CurrentProcess() { -    return System::GetInstance().CurrentProcess(); -} -  } // namespace Core diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 46aceec3d..222fc95ba 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -423,7 +423,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {  std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,                                                       const RSAKeyPair<2048>& key) {      const auto issuer = ticket.GetData().issuer; -    if (issuer == std::array<u8, 0x40>{}) +    if (IsAllZeroArray(issuer))          return {};      if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {          LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 8f758d6d9..0af44f340 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -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 diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index bdfe728c9..8f0451c98 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -61,6 +61,8 @@ public:      u64 GetUserNANDTotalSpace() const;      u64 GetFullNANDTotalSpace() const; +    VirtualDir GetBCATDirectory(u64 title_id) const; +  private:      VirtualDir nand_root;      VirtualDir load_root; diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index 84cd4684c..4bd2e6183 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -35,11 +35,11 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {      this->update_raw = std::move(update_raw);  } -ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const { +ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {      if (!updatable)          return MakeResult<VirtualFile>(file); -    const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID()); +    const PatchManager patch_manager(current_process_title_id);      return MakeResult<VirtualFile>(          patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));  } diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index da63a313a..c5d40285c 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -33,7 +33,7 @@ public:      ~RomFSFactory();      void SetPackedUpdate(VirtualFile update_raw); -    ResultVal<VirtualFile> OpenCurrentProcess() const; +    ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;      ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;  private: diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index f77cc02ac..fc8755c78 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -127,8 +127,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ                                           u128 user_id, u64 save_id) {      // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should      // be interpreted as the title id of the current process. -    if (type == SaveDataType::SaveData && title_id == 0) -        title_id = Core::CurrentProcess()->GetTitleID(); +    if (type == SaveDataType::SaveData && title_id == 0) { +        title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); +    }      std::string out = GetSaveDataSpaceIdPath(space); diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp new file mode 100644 index 000000000..8bdaa7e4a --- /dev/null +++ b/src/core/file_sys/vfs_libzip.cpp @@ -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 diff --git a/src/core/file_sys/vfs_libzip.h b/src/core/file_sys/vfs_libzip.h new file mode 100644 index 000000000..f68af576a --- /dev/null +++ b/src/core/file_sys/vfs_libzip.h @@ -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 diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index afa812598..db51d722f 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -641,7 +641,8 @@ static void HandleQuery() {                         strlen("Xfer:features:read:target.xml:")) == 0) {          SendReply(target_xml);      } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) { -        const VAddr base_address = Core::CurrentProcess()->VMManager().GetCodeRegionBaseAddress(); +        const VAddr base_address = +            Core::System::GetInstance().CurrentProcess()->VMManager().GetCodeRegionBaseAddress();          std::string buffer = fmt::format("TextSeg={:0x}", base_address);          SendReply(buffer.c_str());      } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index bdfaa977f..2cc5d536b 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -103,7 +103,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {      if (handle == CurrentThread) {          return GetCurrentThread();      } else if (handle == CurrentProcess) { -        return Core::CurrentProcess(); +        return Core::System::GetInstance().CurrentProcess();      }      if (!IsValid(handle)) { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 797c9a06f..941ebc93a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -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, +}; + +constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA; -struct LaunchParameters { +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 = system.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({system.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( @@ -1165,7 +1200,7 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {      LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);      FileSys::SaveDataDescriptor descriptor{}; -    descriptor.title_id = Core::CurrentProcess()->GetTitleID(); +    descriptor.title_id = system.CurrentProcess()->GetTitleID();      descriptor.user_id = user_id;      descriptor.type = FileSys::SaveDataType::SaveData;      const auto res = system.GetFileSystemController().CreateSaveData( diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index a3baeb673..ccd053c13 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -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;  }; diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index d2e35362f..720fe766f 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -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); diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index 764c3418c..226be88b1 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -190,6 +190,8 @@ public:      explicit AppletManager(Core::System& system_);      ~AppletManager(); +    const AppletFrontendSet& GetAppletFrontendSet() const; +      void SetAppletFrontendSet(AppletFrontendSet set);      void SetDefaultAppletFrontendSet();      void SetDefaultAppletsIfMissing(); diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp index 4376612eb..073d0f6fa 100644 --- a/src/core/hle/service/apm/controller.cpp +++ b/src/core/hle/service/apm/controller.cpp @@ -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 diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h index 8ac80eaea..454caa6eb 100644 --- a/src/core/hle/service/apm/controller.h +++ b/src/core/hle/service/apm/controller.h @@ -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 diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index fb84a8f13..9afefb5c6 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -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; diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp new file mode 100644 index 000000000..9d6946bc5 --- /dev/null +++ b/src/core/hle/service/bcat/backend/backend.cpp @@ -0,0 +1,137 @@ +// 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_view event_name) { +    auto& kernel{Core::System::GetInstance().Kernel()}; +    event = Kernel::WritableEvent::CreateEventPair( +        kernel, Kernel::ResetType::Automatic, +        std::string("ProgressServiceBackend:UpdateEvent:").append(event_name)); +} + +Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const { +    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(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 diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h new file mode 100644 index 000000000..51dbd3316 --- /dev/null +++ b/src/core/hle/service/bcat/backend/backend.h @@ -0,0 +1,150 @@ +// 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 <string> +#include <string_view> + +#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_view event_name); + +    Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent() const; +    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(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 diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp new file mode 100644 index 000000000..64022982b --- /dev/null +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -0,0 +1,504 @@ +// 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& error) { +        LOG_ERROR(Service_BCAT, "{}", error.what()); +        return StatusResult::ParseError; +    } +} + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h new file mode 100644 index 000000000..601151189 --- /dev/null +++ b/src/core/hle/service/bcat/backend/boxcat.h @@ -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 diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp index 179aa4949..8bb2528c9 100644 --- a/src/core/hle/service/bcat/bcat.cpp +++ b/src/core/hle/service/bcat/bcat.cpp @@ -6,11 +6,16 @@  namespace Service::BCAT { -BCAT::BCAT(std::shared_ptr<Module> module, const char* name) -    : Module::Interface(std::move(module), name) { +BCAT::BCAT(Core::System& system, std::shared_ptr<Module> module, +           FileSystem::FileSystemController& fsc, const char* name) +    : Interface(system, 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);  } diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h index 802bd689a..6354465fc 100644 --- a/src/core/hle/service/bcat/bcat.h +++ b/src/core/hle/service/bcat/bcat.h @@ -6,11 +6,16 @@  #include "core/hle/service/bcat/module.h" +namespace Core { +class System; +} +  namespace Service::BCAT {  class BCAT final : public Module::Interface {  public: -    explicit BCAT(std::shared_ptr<Module> module, const char* name); +    explicit BCAT(Core::System& system, std::shared_ptr<Module> module, +                  FileSystem::FileSystemController& fsc, const char* name);      ~BCAT() override;  }; diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index b7bd738fc..4e4aa758b 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -2,34 +2,257 @@  // 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 Core::System::CurrentBuildProcessID& id) { +    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") { +    explicit IBcatService(Core::System& system_, Backend& backend_) +        : ServiceFramework("IBcatService"), system{system_}, 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({system.CurrentProcess()->GetTitleID(), +                             GetCurrentBuildID(system.GetCurrentProcessBuildID())}, +                            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({system.CurrentProcess()->GetTitleID(), +                                      GetCurrentBuildID(system.GetCurrentProcessBuildID())}, +                                     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); +    } + +    Core::System& system; +    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 +260,332 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {      IPC::ResponseBuilder rb{ctx, 2, 0, 1};      rb.Push(RESULT_SUCCESS); -    rb.PushIpcInterface<IBcatService>(); +    rb.PushIpcInterface<IBcatService>(system, *backend); +} + +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(static_cast<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(static_cast<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(static_cast<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(system.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, const char* name) -    : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(Core::System& system_, 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); })}, +      system{system_} {}  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>(system, module, system.GetFileSystemController(), "bcat:a") +        ->InstallAsService(system.ServiceManager()); +    std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m") +        ->InstallAsService(system.ServiceManager()); +    std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u") +        ->InstallAsService(system.ServiceManager()); +    std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s") +        ->InstallAsService(system.ServiceManager());  }  } // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h index f0d63cab0..e4ba23ba0 100644 --- a/src/core/hle/service/bcat/module.h +++ b/src/core/hle/service/bcat/module.h @@ -6,23 +6,46 @@  #include "core/hle/service/service.h" -namespace Service::BCAT { +namespace Core { +class System; +} + +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(Core::System& system_, 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; + +    private: +        Core::System& system;      };  };  /// Registers all BCAT services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); + +} // namespace BCAT -} // namespace Service::BCAT +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index b2ebf6240..2546d7595 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -66,7 +66,7 @@ enum class FatalType : u32 {  static void GenerateErrorReport(Core::System& system, ResultCode error_code,                                  const FatalInfo& info) { -    const auto title_id = Core::CurrentProcess()->GetTitleID(); +    const auto title_id = system.CurrentProcess()->GetTitleID();      std::string crash_report = fmt::format(          "Yuzu {}-{} crash report\n"          "Title ID:                        {:016x}\n" diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 14cd0e322..11e5c56b7 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -241,7 +241,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(      return FileSys::ERROR_PATH_NOT_FOUND;  } -FileSystemController::FileSystemController() = default; +FileSystemController::FileSystemController(Core::System& system_) : system{system_} {}  FileSystemController::~FileSystemController() = default; @@ -290,7 +290,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess()          return ResultCode(-1);      } -    return romfs_factory->OpenCurrentProcess(); +    return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID());  }  ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( @@ -447,10 +447,10 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy          FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE};          FileSys::NACP nacp; -        const auto res = Core::System::GetInstance().GetAppLoader().ReadControlData(nacp); +        const auto res = system.GetAppLoader().ReadControlData(nacp);          if (res != Loader::ResultStatus::Success) { -            FileSys::PatchManager pm{Core::CurrentProcess()->GetTitleID()}; +            FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};              auto [nacp_unique, discard] = pm.GetControlMetadata();              if (nacp_unique != nullptr) { @@ -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; @@ -693,10 +702,10 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove      if (bis_factory == nullptr) {          bis_factory =              std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); -        Core::System::GetInstance().RegisterContentProvider( -            FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents()); -        Core::System::GetInstance().RegisterContentProvider( -            FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents()); +        system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, +                                       bis_factory->GetSystemNANDContents()); +        system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, +                                       bis_factory->GetUserNANDContents());      }      if (save_data_factory == nullptr) { @@ -705,8 +714,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove      if (sdmc_factory == nullptr) {          sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); -        Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, -                                                            sdmc_factory->GetSDMCContents()); +        system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, +                                       sdmc_factory->GetSDMCContents());      }  } diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 3e0c03ec0..1b0a6a949 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -10,6 +10,10 @@  #include "core/file_sys/vfs.h"  #include "core/hle/result.h" +namespace Core { +class System; +} +  namespace FileSys {  class BISFactory;  class RegisteredCache; @@ -52,7 +56,7 @@ enum class ImageDirectoryId : u32 {  class FileSystemController {  public: -    FileSystemController(); +    explicit FileSystemController(Core::System& system_);      ~FileSystemController();      ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); @@ -110,6 +114,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); @@ -123,6 +129,8 @@ private:      std::unique_ptr<FileSys::XCI> gamecard;      std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;      std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder; + +    Core::System& system;  };  void InstallInterfaces(Core::System& system); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index eb982ad49..cbd5466c1 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -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(), diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 42b4ee861..75dd9043b 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -237,7 +237,6 @@ private:      };      Common::UUID uuid; -    bool is_event_created = false;      Kernel::EventPair notification_event;      std::queue<SizedNotificationInfo> notifications;      States states{}; diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index 8e8263f5b..1f2131ec8 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 6c4de817e..555b29d76 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -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 diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 80da0a0d3..6e990dd00 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h index 396897527..f650b8338 100644 --- a/src/core/hle/service/hid/controllers/gesture.h +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -59,6 +59,5 @@ private:          std::array<GestureState, 17> gesture_states;      };      SharedMemory shared_memory{}; -    Core::System& system;  };  } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index e587b2e15..358cb9329 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index ef586f7eb..f3eef5936 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -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 diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 88f2ca4c1..93d88ea50 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index df2da6ae3..357ab7107 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -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 diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 44b668fbf..a2b25a796 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -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); @@ -165,13 +167,14 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {      controller.battery_level[0] = BATTERY_FULL;      controller.battery_level[1] = BATTERY_FULL;      controller.battery_level[2] = BATTERY_FULL; +    styleset_changed_events[controller_idx].writable->Signal();  }  void Controller_NPad::OnInit() {      auto& kernel = system.Kernel();      for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {          styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( -            kernel, Kernel::ResetType::Automatic, fmt::format("npad:NpadStyleSetChanged_{}", i)); +            kernel, Kernel::ResetType::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i));      }      if (!IsControllerActivated()) { @@ -238,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;      } @@ -345,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); @@ -433,7 +438,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {      supported_npad_id_types.clear();      supported_npad_id_types.resize(length / sizeof(u32));      std::memcpy(supported_npad_id_types.data(), data, length); -    bool had_controller_update = false;      for (std::size_t i = 0; i < connected_controllers.size(); i++) {          auto& controller = connected_controllers[i];          if (!controller.is_connected) { @@ -452,10 +456,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {                  controller.type = requested_controller;                  InitNewlyAddedControler(i);              } -            had_controller_update = true; -        } -        if (had_controller_update) { -            styleset_changed_events[i].writable->Signal();          }      }  } @@ -481,7 +481,6 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)      const std::size_t npad_index = NPadIdToIndex(npad_id);      ASSERT(npad_index < shared_memory_entries.size());      if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { -        styleset_changed_events[npad_index].writable->Signal();          shared_memory_entries[npad_index].pad_assignment = assignment_mode;      }  } @@ -507,7 +506,6 @@ Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEven      // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should      // be signalled at least once, and signaled after a new controller is connected?      const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; -    styleset_event.writable->Signal();      return styleset_event.readable;  } diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp index 9b829341e..9e527d176 100644 --- a/src/core/hle/service/hid/controllers/stubbed.cpp +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h index 37d7d8538..4fa83ac85 100644 --- a/src/core/hle/service/hid/controllers/stubbed.h +++ b/src/core/hle/service/hid/controllers/stubbed.h @@ -30,6 +30,5 @@ public:  private:      bool smart_update{};      std::size_t common_offset{}; -    Core::System& system;  };  } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 25912fd69..1c6e55566 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 3429c84db..a1d97269e 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -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 diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp index 1bce044b4..27511b27b 100644 --- a/src/core/hle/service/hid/controllers/xpad.cpp +++ b/src/core/hle/service/hid/controllers/xpad.cpp @@ -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() {} diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h index c445ebec0..ad229787c 100644 --- a/src/core/hle/service/hid/controllers/xpad.h +++ b/src/core/hle/service/hid/controllers/xpad.h @@ -56,6 +56,5 @@ private:      };      static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");      SharedMemory shared_memory{}; -    Core::System& system;  };  } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 8d76ba746..ba1da4181 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -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) @@ -193,7 +195,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {          {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},          {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},          {103, &Hid::ActivateNpad, "ActivateNpad"}, -        {104, nullptr, "DeactivateNpad"}, +        {104, &Hid::DeactivateNpad, "DeactivateNpad"},          {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},          {107, &Hid::DisconnectNpad, "DisconnectNpad"},          {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, @@ -468,6 +470,17 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) {      applet_resource->ActivateController(HidController::NPad);  } +void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp{ctx}; +    const auto applet_resource_user_id{rp.Pop<u64>()}; + +    LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(RESULT_SUCCESS); +    applet_resource->DeactivateController(HidController::NPad); +} +  void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx};      const auto npad_id{rp.Pop<u32>()}; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 35b663679..01852e019 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -99,6 +99,7 @@ private:      void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);      void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);      void ActivateNpad(Kernel::HLERequestContext& ctx); +    void DeactivateNpad(Kernel::HLERequestContext& ctx);      void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);      void DisconnectNpad(Kernel::HLERequestContext& ctx);      void GetPlayerLedPattern(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 3164ca26e..499376bfc 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -163,7 +163,7 @@ public:              return;          } -        if (Core::CurrentProcess()->GetTitleID() != header.title_id) { +        if (system.CurrentProcess()->GetTitleID() != header.title_id) {              LOG_ERROR(Service_LDR,                        "Attempting to load NRR with title ID other than current process. (actual "                        "{:016X})!", @@ -327,7 +327,7 @@ public:          }          // Load NRO as new executable module -        auto* process = Core::CurrentProcess(); +        auto* process = system.CurrentProcess();          auto& vm_manager = process->VMManager();          auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size); @@ -411,7 +411,7 @@ public:              return;          } -        auto& vm_manager = Core::CurrentProcess()->VMManager(); +        auto& vm_manager = system.CurrentProcess()->VMManager();          const auto& nro_info = iter->second;          // Unmap the mirrored memory diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index a42c22d44..aa886cd3e 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -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) { diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 24d1813a7..756a2af57 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -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;  }; diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 7dcdb4a07..f64535237 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -324,14 +324,14 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {  void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {      // Map backing memory for the font data      LOG_DEBUG(Service_NS, "called"); -    Core::CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, -                                                       SHARED_FONT_MEM_SIZE, -                                                       Kernel::MemoryState::Shared); +    system.CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, +                                                        SHARED_FONT_MEM_SIZE, +                                                        Kernel::MemoryState::Shared);      // Create shared font memory object      auto& kernel = system.Kernel();      impl->shared_font_mem = Kernel::SharedMemory::Create( -        kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, +        kernel, system.CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,          Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,          "PL_U:shared_font_mem"); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 6bc053f27..07c88465e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -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) diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index ff6b1abae..eb88fee1b 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -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) { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index 389ace76f..cc2192e5c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -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, diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 2b8d1bef6..9de0ace22 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -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) { diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index c12a746c8..7c5302017 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -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); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index e75c700ad..f629892ae 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -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); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9e030789d..fa49f3dd0 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -146,7 +146,7 @@ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {   * using a VMA from the current process.   */  static u8* GetPointerFromVMA(VAddr vaddr) { -    return GetPointerFromVMA(*Core::CurrentProcess(), vaddr); +    return GetPointerFromVMA(*Core::System::GetInstance().CurrentProcess(), vaddr);  }  template <typename T> @@ -226,7 +226,7 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {  }  bool IsValidVirtualAddress(const VAddr vaddr) { -    return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr); +    return IsValidVirtualAddress(*Core::System::GetInstance().CurrentProcess(), vaddr);  }  bool IsKernelVirtualAddress(const VAddr vaddr) { @@ -387,7 +387,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_  }  void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) { -    ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size); +    ReadBlock(*Core::System::GetInstance().CurrentProcess(), src_addr, dest_buffer, size);  }  void Write8(const VAddr addr, const u8 data) { @@ -450,7 +450,7 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi  }  void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { -    WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); +    WriteBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_buffer, size);  }  void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) { @@ -539,7 +539,7 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,  }  void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) { -    CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size); +    CopyBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_addr, size);  }  } // namespace Memory diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 7de3fd1e5..d1fc94060 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -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 diff --git a/src/core/settings.h b/src/core/settings.h index 47bddfb30..9c98a9287 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -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; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index e2f85c5f1..eaa694ff8 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -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 diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 28272ef6f..7a6355ce2 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -544,7 +544,7 @@ enum class VoteOperation : u64 {      Eq = 2,  // allThreadsEqualNV  }; -enum class ImageAtomicSize : u64 { +enum class ImageAtomicOperationType : u64 {      U32 = 0,      S32 = 1,      U64 = 2, @@ -1432,11 +1432,11 @@ union Instruction {              ASSERT(mode == SurfaceDataMode::D_BA);              return store_data_layout;          } -    } sust; +    } suldst;      union {          BitField<28, 1, u64> is_ba; -        BitField<51, 3, ImageAtomicSize> size; +        BitField<51, 3, ImageAtomicOperationType> operation_type;          BitField<33, 3, ImageType> image_type;          BitField<29, 4, ImageAtomicOperation> operation;          BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; @@ -1595,6 +1595,7 @@ public:          TMML_B, // Texture Mip Map Level          TMML,   // Texture Mip Map Level          SUST,   // Surface Store +        SULD,   // Surface Load          SUATOM, // Surface Atomic Operation          EXIT,          NOP, @@ -1884,6 +1885,7 @@ private:              INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),              INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),              INST("11101011001-----", Id::SUST, Type::Image, "SUST"), +            INST("11101011000-----", Id::SULD, Type::Image, "SULD"),              INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"),              INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"),              INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 4f59a87b4..64de7e425 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -2,8 +2,10 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm>  #include <array>  #include <cstddef> +#include <vector>  #include <glad/glad.h>  #include "common/logging/log.h" @@ -30,9 +32,27 @@ bool TestProgram(const GLchar* glsl) {      return link_status == GL_TRUE;  } +std::vector<std::string_view> GetExtensions() { +    GLint num_extensions; +    glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); +    std::vector<std::string_view> extensions; +    extensions.reserve(num_extensions); +    for (GLint index = 0; index < num_extensions; ++index) { +        extensions.push_back( +            reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index)))); +    } +    return extensions; +} + +bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) { +    return std::find(images.begin(), images.end(), extension) != images.end(); +} +  } // Anonymous namespace  Device::Device() { +    const std::vector extensions = GetExtensions(); +      uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);      shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);      max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS); @@ -40,6 +60,7 @@ Device::Device() {      has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&                            GLAD_GL_NV_shader_thread_shuffle;      has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; +    has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");      has_variable_aoffi = TestVariableAoffi();      has_component_indexing_bug = TestComponentIndexingBug();      has_precise_bug = TestPreciseBug(); @@ -55,6 +76,7 @@ Device::Device(std::nullptr_t) {      max_varyings = 15;      has_warp_intrinsics = true;      has_vertex_viewport_layer = true; +    has_image_load_formatted = true;      has_variable_aoffi = true;      has_component_indexing_bug = false;      has_precise_bug = false; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index ba6dcd3be..bb273c3d6 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -38,6 +38,10 @@ public:          return has_vertex_viewport_layer;      } +    bool HasImageLoadFormatted() const { +        return has_image_load_formatted; +    } +      bool HasVariableAoffi() const {          return has_variable_aoffi;      } @@ -61,6 +65,7 @@ private:      u32 max_varyings{};      bool has_warp_intrinsics{};      bool has_vertex_viewport_layer{}; +    bool has_image_load_formatted{};      bool has_variable_aoffi{};      bool has_component_indexing_bug{};      bool has_precise_bug{}; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 0dbc4c02f..42ca3b1bd 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -211,14 +211,14 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn      const auto primitive_mode{variant.primitive_mode};      const auto texture_buffer_usage{variant.texture_buffer_usage}; -    std::string source = "#version 430 core\n" -                         "#extension GL_ARB_separate_shader_objects : enable\n" -                         "#extension GL_NV_gpu_shader5 : enable\n" -                         "#extension GL_NV_shader_thread_group : enable\n" -                         "#extension GL_NV_shader_thread_shuffle : enable\n"; -    if (entries.shader_viewport_layer_array) { -        source += "#extension GL_ARB_shader_viewport_layer_array : enable\n"; -    } +    std::string source = R"(#version 430 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shader_viewport_layer_array : enable +#extension GL_EXT_shader_image_load_formatted : enable +#extension GL_NV_gpu_shader5 : enable +#extension GL_NV_shader_thread_group : enable +#extension GL_NV_shader_thread_shuffle : enable +)";      if (program_type == ProgramType::Compute) {          source += "#extension GL_ARB_compute_variable_group_size : require\n";      } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 74cb59bc1..6a610a3bc 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -19,6 +19,8 @@  #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"  namespace OpenGL::GLShader { @@ -241,6 +243,26 @@ constexpr const char* GetTypeString(Type type) {      }  } +constexpr const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) { +    switch (image_type) { +    case Tegra::Shader::ImageType::Texture1D: +        return "1D"; +    case Tegra::Shader::ImageType::TextureBuffer: +        return "Buffer"; +    case Tegra::Shader::ImageType::Texture1DArray: +        return "1DArray"; +    case Tegra::Shader::ImageType::Texture2D: +        return "2D"; +    case Tegra::Shader::ImageType::Texture2DArray: +        return "2DArray"; +    case Tegra::Shader::ImageType::Texture3D: +        return "3D"; +    default: +        UNREACHABLE(); +        return "1D"; +    } +} +  /// Generates code to use for a swizzle operation.  constexpr const char* GetSwizzle(u32 element) {      constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; @@ -313,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)); @@ -371,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() { @@ -398,13 +432,14 @@ public:                                                         usage.is_read, usage.is_written);          }          entries.clip_distances = ir.GetClipDistances(); -        entries.shader_viewport_layer_array = -            IsVertexShader(stage) && (ir.UsesLayer() || ir.UsesViewportIndex());          entries.shader_length = ir.GetLength();          return entries;      }  private: +    friend class ASTDecompiler; +    friend class ExprDecompiler; +      void DeclareVertex() {          if (!IsVertexShader(stage))              return; @@ -722,42 +757,6 @@ private:      void DeclareImages() {          const auto& images{ir.GetImages()};          for (const auto& [offset, image] : images) { -            const char* image_type = [&] { -                switch (image.GetType()) { -                case Tegra::Shader::ImageType::Texture1D: -                    return "image1D"; -                case Tegra::Shader::ImageType::TextureBuffer: -                    return "imageBuffer"; -                case Tegra::Shader::ImageType::Texture1DArray: -                    return "image1DArray"; -                case Tegra::Shader::ImageType::Texture2D: -                    return "image2D"; -                case Tegra::Shader::ImageType::Texture2DArray: -                    return "image2DArray"; -                case Tegra::Shader::ImageType::Texture3D: -                    return "image3D"; -                default: -                    UNREACHABLE(); -                    return "image1D"; -                } -            }(); - -            const auto [type_prefix, format] = [&]() -> std::pair<const char*, const char*> { -                if (!image.IsSizeKnown()) { -                    return {"", ""}; -                } -                switch (image.GetSize()) { -                case Tegra::Shader::ImageAtomicSize::U32: -                    return {"u", "r32ui, "}; -                case Tegra::Shader::ImageAtomicSize::S32: -                    return {"i", "r32i, "}; -                default: -                    UNIMPLEMENTED_MSG("Unimplemented atomic size={}", -                                      static_cast<u32>(image.GetSize())); -                    return {"", ""}; -                } -            }(); -              std::string qualifier = "coherent volatile";              if (image.IsRead() && !image.IsWritten()) {                  qualifier += " readonly"; @@ -765,9 +764,10 @@ private:                  qualifier += " writeonly";              } -            code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " -                         "{} {};", -                         image.GetIndex(), qualifier, image_type, GetImage(image)); +            const char* format = image.IsAtomic() ? "r32ui, " : ""; +            const char* type_declaration = GetImageTypeDeclaration(image.GetType()); +            code.AddLine("layout ({}binding = IMAGE_BINDING_{}) {} uniform uimage{} {};", format, +                         image.GetIndex(), qualifier, type_declaration, GetImage(image));          }          if (!images.empty()) {              code.AddNewLine(); @@ -1234,28 +1234,13 @@ private:      }      std::string BuildImageValues(Operation operation) { +        constexpr std::array constructors{"uint", "uvec2", "uvec3", "uvec4"};          const auto meta{std::get<MetaImage>(operation.GetMeta())}; -        const auto [constructors, type] = [&]() -> std::pair<std::array<const char*, 4>, Type> { -            constexpr std::array float_constructors{"float", "vec2", "vec3", "vec4"}; -            if (!meta.image.IsSizeKnown()) { -                return {float_constructors, Type::Float}; -            } -            switch (meta.image.GetSize()) { -            case Tegra::Shader::ImageAtomicSize::U32: -                return {{"uint", "uvec2", "uvec3", "uvec4"}, Type::Uint}; -            case Tegra::Shader::ImageAtomicSize::S32: -                return {{"int", "ivec2", "ivec3", "ivec4"}, Type::Uint}; -            default: -                UNIMPLEMENTED_MSG("Unimplemented image size={}", -                                  static_cast<u32>(meta.image.GetSize())); -                return {float_constructors, Type::Float}; -            } -        }();          const std::size_t values_count{meta.values.size()};          std::string expr = fmt::format("{}(", constructors.at(values_count - 1));          for (std::size_t i = 0; i < values_count; ++i) { -            expr += Visit(meta.values.at(i)).As(type); +            expr += Visit(meta.values.at(i)).AsUint();              if (i + 1 < values_count) {                  expr += ", ";              } @@ -1264,29 +1249,6 @@ private:          return expr;      } -    Expression AtomicImage(Operation operation, const char* opname) { -        constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; -        const auto meta{std::get<MetaImage>(operation.GetMeta())}; -        ASSERT(meta.values.size() == 1); -        ASSERT(meta.image.IsSizeKnown()); - -        const auto type = [&]() { -            switch (const auto size = meta.image.GetSize()) { -            case Tegra::Shader::ImageAtomicSize::U32: -                return Type::Uint; -            case Tegra::Shader::ImageAtomicSize::S32: -                return Type::Int; -            default: -                UNIMPLEMENTED_MSG("Unimplemented image size={}", static_cast<u32>(size)); -                return Type::Uint; -            } -        }(); - -        return {fmt::format("{}({}, {}, {})", opname, GetImage(meta.image), -                            BuildIntegerCoordinates(operation), Visit(meta.values[0]).As(type)), -                type}; -    } -      Expression Assign(Operation operation) {          const Node& dest = operation[0];          const Node& src = operation[1]; @@ -1545,6 +1507,8 @@ private:          case Tegra::Shader::HalfType::H1_H1:              return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};          } +        UNREACHABLE(); +        return {"0", Type::Int};      }      Expression HMergeF32(Operation operation) { @@ -1809,6 +1773,19 @@ private:          return {tmp, Type::Float};      } +    Expression ImageLoad(Operation operation) { +        if (!device.HasImageLoadFormatted()) { +            LOG_ERROR(Render_OpenGL, +                      "Device lacks GL_EXT_shader_image_load_formatted, stubbing image load"); +            return {"0", Type::Int}; +        } + +        const auto meta{std::get<MetaImage>(operation.GetMeta())}; +        return {fmt::format("imageLoad({}, {}){}", GetImage(meta.image), +                            BuildIntegerCoordinates(operation), GetSwizzle(meta.element)), +                Type::Uint}; +    } +      Expression ImageStore(Operation operation) {          const auto meta{std::get<MetaImage>(operation.GetMeta())};          code.AddLine("imageStore({}, {}, {});", GetImage(meta.image), @@ -1816,31 +1793,14 @@ private:          return {};      } -    Expression AtomicImageAdd(Operation operation) { -        return AtomicImage(operation, "imageAtomicAdd"); -    } - -    Expression AtomicImageMin(Operation operation) { -        return AtomicImage(operation, "imageAtomicMin"); -    } - -    Expression AtomicImageMax(Operation operation) { -        return AtomicImage(operation, "imageAtomicMax"); -    } -    Expression AtomicImageAnd(Operation operation) { -        return AtomicImage(operation, "imageAtomicAnd"); -    } - -    Expression AtomicImageOr(Operation operation) { -        return AtomicImage(operation, "imageAtomicOr"); -    } - -    Expression AtomicImageXor(Operation operation) { -        return AtomicImage(operation, "imageAtomicXor"); -    } +    template <const std::string_view& opname> +    Expression AtomicImage(Operation operation) { +        const auto meta{std::get<MetaImage>(operation.GetMeta())}; +        ASSERT(meta.values.size() == 1); -    Expression AtomicImageExchange(Operation operation) { -        return AtomicImage(operation, "imageAtomicExchange"); +        return {fmt::format("imageAtomic{}({}, {}, {})", opname, GetImage(meta.image), +                            BuildIntegerCoordinates(operation), Visit(meta.values[0]).AsUint()), +                Type::Uint};      }      Expression Branch(Operation operation) { @@ -1877,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 { @@ -1912,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 {};      } @@ -2035,6 +1997,12 @@ private:          Func() = delete;          ~Func() = delete; +        static constexpr std::string_view Add = "Add"; +        static constexpr std::string_view And = "And"; +        static constexpr std::string_view Or = "Or"; +        static constexpr std::string_view Xor = "Xor"; +        static constexpr std::string_view Exchange = "Exchange"; +          static constexpr std::string_view ShuffleIndexed = "shuffleNV";          static constexpr std::string_view ShuffleUp = "shuffleUpNV";          static constexpr std::string_view ShuffleDown = "shuffleDownNV"; @@ -2172,14 +2140,14 @@ private:          &GLSLDecompiler::TextureQueryLod,          &GLSLDecompiler::TexelFetch, +        &GLSLDecompiler::ImageLoad,          &GLSLDecompiler::ImageStore, -        &GLSLDecompiler::AtomicImageAdd, -        &GLSLDecompiler::AtomicImageMin, -        &GLSLDecompiler::AtomicImageMax, -        &GLSLDecompiler::AtomicImageAnd, -        &GLSLDecompiler::AtomicImageOr, -        &GLSLDecompiler::AtomicImageXor, -        &GLSLDecompiler::AtomicImageExchange, + +        &GLSLDecompiler::AtomicImage<Func::Add>, +        &GLSLDecompiler::AtomicImage<Func::And>, +        &GLSLDecompiler::AtomicImage<Func::Or>, +        &GLSLDecompiler::AtomicImage<Func::Xor>, +        &GLSLDecompiler::AtomicImage<Func::Exchange>,          &GLSLDecompiler::Branch,          &GLSLDecompiler::BranchIndirect, @@ -2303,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() { diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 2ea02f5bf..e538dc001 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -90,7 +90,6 @@ struct ShaderEntries {      std::vector<ImageEntry> images;      std::vector<GlobalMemoryEntry> global_memory_entries;      std::array<bool, Maxwell::NumClipDistances> clip_distances{}; -    bool shader_viewport_layer_array{};      std::size_t shader_length{};  }; diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index f141c4e3b..74cc33476 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -112,14 +112,15 @@ std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskC  ShaderDiskCacheOpenGL::LoadTransferable() {      // Skip games without title id      const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0; -    if (!Settings::values.use_disk_shader_cache || !has_title_id) +    if (!Settings::values.use_disk_shader_cache || !has_title_id) {          return {}; -    tried_to_load = true; +    }      FileUtil::IOFile file(GetTransferablePath(), "rb");      if (!file.IsOpen()) {          LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}",                   GetTitleID()); +        is_usable = true;          return {};      } @@ -135,6 +136,7 @@ ShaderDiskCacheOpenGL::LoadTransferable() {          LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing");          file.Close();          InvalidateTransferable(); +        is_usable = true;          return {};      }      if (version > NativeVersion) { @@ -180,13 +182,15 @@ ShaderDiskCacheOpenGL::LoadTransferable() {          }      } -    return {{raws, usages}}; +    is_usable = true; +    return {{std::move(raws), std::move(usages)}};  }  std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>  ShaderDiskCacheOpenGL::LoadPrecompiled() { -    if (!IsUsable()) +    if (!is_usable) {          return {}; +    }      FileUtil::IOFile file(GetPrecompiledPath(), "rb");      if (!file.IsOpen()) { @@ -343,20 +347,17 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn          u8 is_bindless{};          u8 is_written{};          u8 is_read{}; -        u8 is_size_known{}; -        u32 size{}; +        u8 is_atomic{};          if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||              !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||              !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) || -            !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) { +            !LoadObjectFromPrecompiled(is_atomic)) {              return {};          }          entry.entries.images.emplace_back(              static_cast<std::size_t>(offset), static_cast<std::size_t>(index),              static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0, -            is_read != 0, -            is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size)) -                          : std::nullopt); +            is_read != 0, is_atomic != 0);      }      u32 global_memory_count{}; @@ -382,12 +383,6 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn          }      } -    bool shader_viewport_layer_array{}; -    if (!LoadObjectFromPrecompiled(shader_viewport_layer_array)) { -        return {}; -    } -    entry.entries.shader_viewport_layer_array = shader_viewport_layer_array; -      u64 shader_length{};      if (!LoadObjectFromPrecompiled(shader_length)) {          return {}; @@ -435,14 +430,13 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:          return false;      }      for (const auto& image : entries.images) { -        const u32 size = image.IsSizeKnown() ? static_cast<u32>(image.GetSize()) : 0U;          if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||              !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||              !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||              !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||              !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) ||              !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || -            !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) { +            !SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) {              return false;          }      } @@ -464,10 +458,6 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:          }      } -    if (!SaveObjectToPrecompiled(entries.shader_viewport_layer_array)) { -        return false; -    } -      if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {          return false;      } @@ -493,8 +483,9 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {  }  void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) { -    if (!IsUsable()) +    if (!is_usable) {          return; +    }      const u64 id = entry.GetUniqueIdentifier();      if (transferable.find(id) != transferable.end()) { @@ -515,8 +506,9 @@ void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) {  }  void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) { -    if (!IsUsable()) +    if (!is_usable) {          return; +    }      const auto it = transferable.find(usage.unique_identifier);      ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously"); @@ -542,8 +534,9 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {  void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::string& code,                                             const GLShader::ShaderEntries& entries) { -    if (!IsUsable()) +    if (!is_usable) {          return; +    }      if (precompiled_cache_virtual_file.GetSize() == 0) {          SavePrecompiledHeaderToVirtualPrecompiledCache(); @@ -557,8 +550,9 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str  }  void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint program) { -    if (!IsUsable()) +    if (!is_usable) {          return; +    }      GLint binary_length{};      glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length); @@ -579,10 +573,6 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p      }  } -bool ShaderDiskCacheOpenGL::IsUsable() const { -    return tried_to_load && Settings::values.use_disk_shader_cache; -} -  FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {      if (!EnsureDirectories())          return {}; diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h index cc8bbd61e..9595bd71b 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h @@ -224,9 +224,6 @@ private:      bool SaveDecompiledFile(u64 unique_identifier, const std::string& code,                              const GLShader::ShaderEntries& entries); -    /// Returns if the cache can be used -    bool IsUsable() const; -      /// Opens current game's transferable file and write it's header if it doesn't exist      FileUtil::IOFile AppendTransferableFile() const; @@ -297,7 +294,7 @@ private:      std::unordered_map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;      // The cache has been loaded at boot -    bool tried_to_load{}; +    bool is_usable{};  };  } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 3a8d9e1da..b5a43e79e 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -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; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index f7fbbb6e4..8bcd04221 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -19,6 +19,7 @@  #include "video_core/engines/shader_header.h"  #include "video_core/renderer_vulkan/vk_device.h"  #include "video_core/renderer_vulkan/vk_shader_decompiler.h" +#include "video_core/shader/node.h"  #include "video_core/shader/shader_ir.h"  namespace Vulkan::VKShader { @@ -87,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) @@ -96,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"); @@ -173,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());      } @@ -205,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() { @@ -293,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); @@ -614,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 {}; @@ -939,22 +977,17 @@ private:          return {};      } -    Id ImageStore(Operation operation) { -        UNIMPLEMENTED(); -        return {}; -    } - -    Id AtomicImageAdd(Operation operation) { +    Id ImageLoad(Operation operation) {          UNIMPLEMENTED();          return {};      } -    Id AtomicImageMin(Operation operation) { +    Id ImageStore(Operation operation) {          UNIMPLEMENTED();          return {};      } -    Id AtomicImageMax(Operation operation) { +    Id AtomicImageAdd(Operation operation) {          UNIMPLEMENTED();          return {};      } @@ -984,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 {};      } @@ -992,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 {};      } @@ -1019,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 @@ -1071,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 {};      } @@ -1271,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. @@ -1440,10 +1497,9 @@ private:          &SPIRVDecompiler::TextureQueryLod,          &SPIRVDecompiler::TexelFetch, +        &SPIRVDecompiler::ImageLoad,          &SPIRVDecompiler::ImageStore,          &SPIRVDecompiler::AtomicImageAdd, -        &SPIRVDecompiler::AtomicImageMin, -        &SPIRVDecompiler::AtomicImageMax,          &SPIRVDecompiler::AtomicImageAnd,          &SPIRVDecompiler::AtomicImageOr,          &SPIRVDecompiler::AtomicImageXor, @@ -1488,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"); @@ -1550,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; @@ -1585,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); diff --git a/src/video_core/shader/ast.cpp b/src/video_core/shader/ast.cpp new file mode 100644 index 000000000..436d45f4b --- /dev/null +++ b/src/video_core/shader/ast.cpp @@ -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 diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h new file mode 100644 index 000000000..d7bf11821 --- /dev/null +++ b/src/video_core/shader/ast.h @@ -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 diff --git a/src/video_core/shader/compiler_settings.cpp b/src/video_core/shader/compiler_settings.cpp new file mode 100644 index 000000000..cddcbd4f0 --- /dev/null +++ b/src/video_core/shader/compiler_settings.cpp @@ -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 diff --git a/src/video_core/shader/compiler_settings.h b/src/video_core/shader/compiler_settings.h new file mode 100644 index 000000000..916018c01 --- /dev/null +++ b/src/video_core/shader/compiler_settings.h @@ -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 diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp index ec3a76690..268d1aed0 100644 --- a/src/video_core/shader/control_flow.cpp +++ b/src/video_core/shader/control_flow.cpp @@ -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 diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h index b0a5e4f8c..74e54a5c7 100644 --- a/src/video_core/shader/control_flow.h +++ b/src/video_core/shader/control_flow.h @@ -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 diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index 47a9fd961..2626b1616 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp @@ -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));      }  } diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp index 840694527..fec8f2dbe 100644 --- a/src/video_core/shader/decode/half_set_predicate.cpp +++ b/src/video_core/shader/decode/half_set_predicate.cpp @@ -4,6 +4,7 @@  #include "common/assert.h"  #include "common/common_types.h" +#include "common/logging/log.h"  #include "video_core/engines/shader_bytecode.h"  #include "video_core/shader/node_helper.h"  #include "video_core/shader/shader_ir.h" @@ -18,7 +19,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {      const Instruction instr = {program_code[pc]};      const auto opcode = OpCode::Decode(instr); -    DEBUG_ASSERT(instr.hsetp2.ftz == 0); +    LOG_DEBUG(HW_GPU, "ftz={}", static_cast<u32>(instr.hsetp2.ftz));      Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a);      op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a); @@ -32,6 +33,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {          h_and = instr.hsetp2.cbuf_and_imm.h_and;          op_b = GetOperandAbsNegHalf(GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),                                      instr.hsetp2.cbuf.abs_b, instr.hsetp2.cbuf.negate_b); +        // F32 is hardcoded in hardware +        op_b = UnpackHalfFloat(std::move(op_b), Tegra::Shader::HalfType::F32);          break;      case OpCode::Id::HSETP2_IMM:          cond = instr.hsetp2.cbuf_and_imm.cond; diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index d54fb88c9..95ec1cdd9 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp @@ -41,11 +41,46 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {      const Instruction instr = {program_code[pc]};      const auto opcode = OpCode::Decode(instr); +    const auto GetCoordinates = [this, instr](Tegra::Shader::ImageType image_type) { +        std::vector<Node> coords; +        const std::size_t num_coords{GetImageTypeNumCoordinates(image_type)}; +        coords.reserve(num_coords); +        for (std::size_t i = 0; i < num_coords; ++i) { +            coords.push_back(GetRegister(instr.gpr8.Value() + i)); +        } +        return coords; +    }; +      switch (opcode->get().GetId()) { +    case OpCode::Id::SULD: { +        UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P); +        UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store != +                         Tegra::Shader::OutOfBoundsStore::Ignore); + +        const auto type{instr.suldst.image_type}; +        auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type) +                                              : GetBindlessImage(instr.gpr39, type)}; +        image.MarkRead(); + +        u32 indexer = 0; +        for (u32 element = 0; element < 4; ++element) { +            if (!instr.suldst.IsComponentEnabled(element)) { +                continue; +            } +            MetaImage meta{image, {}, element}; +            Node value = Operation(OperationCode::ImageLoad, meta, GetCoordinates(type)); +            SetTemporary(bb, indexer++, std::move(value)); +        } +        for (u32 i = 0; i < indexer; ++i) { +            SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); +        } +        break; +    }      case OpCode::Id::SUST: { -        UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); -        UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); -        UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store +        UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P); +        UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store != +                         Tegra::Shader::OutOfBoundsStore::Ignore); +        UNIMPLEMENTED_IF(instr.suldst.component_mask_selector != 0xf); // Ensure we have RGBA          std::vector<Node> values;          constexpr std::size_t hardcoded_size{4}; @@ -53,58 +88,51 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {              values.push_back(GetRegister(instr.gpr0.Value() + i));          } -        std::vector<Node> coords; -        const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; -        for (std::size_t i = 0; i < num_coords; ++i) { -            coords.push_back(GetRegister(instr.gpr8.Value() + i)); -        } - -        const auto type{instr.sust.image_type}; -        auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) -                                            : GetBindlessImage(instr.gpr39, type)}; +        const auto type{instr.suldst.image_type}; +        auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type) +                                              : GetBindlessImage(instr.gpr39, type)};          image.MarkWrite(); -        MetaImage meta{image, values}; -        bb.push_back(Operation(OperationCode::ImageStore, meta, std::move(coords))); +        MetaImage meta{image, std::move(values)}; +        bb.push_back(Operation(OperationCode::ImageStore, meta, GetCoordinates(type)));          break;      }      case OpCode::Id::SUATOM: {          UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0); -        Node value = GetRegister(instr.gpr0); - -        std::vector<Node> coords; -        const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; -        for (std::size_t i = 0; i < num_coords; ++i) { -            coords.push_back(GetRegister(instr.gpr8.Value() + i)); -        } -          const OperationCode operation_code = [instr] { -            switch (instr.suatom_d.operation) { -            case Tegra::Shader::ImageAtomicOperation::Add: -                return OperationCode::AtomicImageAdd; -            case Tegra::Shader::ImageAtomicOperation::Min: -                return OperationCode::AtomicImageMin; -            case Tegra::Shader::ImageAtomicOperation::Max: -                return OperationCode::AtomicImageMax; -            case Tegra::Shader::ImageAtomicOperation::And: -                return OperationCode::AtomicImageAnd; -            case Tegra::Shader::ImageAtomicOperation::Or: -                return OperationCode::AtomicImageOr; -            case Tegra::Shader::ImageAtomicOperation::Xor: -                return OperationCode::AtomicImageXor; -            case Tegra::Shader::ImageAtomicOperation::Exch: -                return OperationCode::AtomicImageExchange; +            switch (instr.suatom_d.operation_type) { +            case Tegra::Shader::ImageAtomicOperationType::S32: +            case Tegra::Shader::ImageAtomicOperationType::U32: +                switch (instr.suatom_d.operation) { +                case Tegra::Shader::ImageAtomicOperation::Add: +                    return OperationCode::AtomicImageAdd; +                case Tegra::Shader::ImageAtomicOperation::And: +                    return OperationCode::AtomicImageAnd; +                case Tegra::Shader::ImageAtomicOperation::Or: +                    return OperationCode::AtomicImageOr; +                case Tegra::Shader::ImageAtomicOperation::Xor: +                    return OperationCode::AtomicImageXor; +                case Tegra::Shader::ImageAtomicOperation::Exch: +                    return OperationCode::AtomicImageExchange; +                }              default: -                UNIMPLEMENTED_MSG("Unimplemented operation={}", -                                  static_cast<u32>(instr.suatom_d.operation.Value())); -                return OperationCode::AtomicImageAdd; +                break;              } +            UNIMPLEMENTED_MSG("Unimplemented operation={} type={}", +                              static_cast<u64>(instr.suatom_d.operation.Value()), +                              static_cast<u64>(instr.suatom_d.operation_type.Value())); +            return OperationCode::AtomicImageAdd;          }(); -        const auto& image{GetImage(instr.image, instr.suatom_d.image_type, instr.suatom_d.size)}; +        Node value = GetRegister(instr.gpr0); + +        const auto type = instr.suatom_d.image_type; +        auto& image = GetImage(instr.image, type); +        image.MarkAtomic(); +          MetaImage meta{image, {std::move(value)}}; -        SetRegister(bb, instr.gpr0, Operation(operation_code, meta, std::move(coords))); +        SetRegister(bb, instr.gpr0, Operation(operation_code, meta, GetCoordinates(type)));          break;      }      default: @@ -114,35 +142,32 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {      return pc;  } -Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, -                          std::optional<Tegra::Shader::ImageAtomicSize> size) { +Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {      const auto offset{static_cast<std::size_t>(image.index.Value())}; -    if (const auto image = TryUseExistingImage(offset, type, size)) { +    if (const auto image = TryUseExistingImage(offset, type)) {          return *image;      }      const std::size_t next_index{used_images.size()}; -    return used_images.emplace(offset, Image{offset, next_index, type, size}).first->second; +    return used_images.emplace(offset, Image{offset, next_index, type}).first->second;  } -Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, -                                  std::optional<Tegra::Shader::ImageAtomicSize> size) { +Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {      const Node image_register{GetRegister(reg)};      const auto [base_image, cbuf_index, cbuf_offset]{          TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))};      const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; -    if (const auto image = TryUseExistingImage(cbuf_key, type, size)) { +    if (const auto image = TryUseExistingImage(cbuf_key, type)) {          return *image;      }      const std::size_t next_index{used_images.size()}; -    return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type, size}) +    return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type})          .first->second;  } -Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, -                                     std::optional<Tegra::Shader::ImageAtomicSize> size) { +Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type) {      auto it = used_images.find(offset);      if (it == used_images.end()) {          return nullptr; @@ -150,14 +175,6 @@ Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,      auto& image = it->second;      ASSERT(image.GetType() == type); -    if (size) { -        // We know the size, if it's known it has to be the same as before, otherwise we can set it. -        if (image.IsSizeKnown()) { -            ASSERT(image.GetSize() == size); -        } else { -            image.SetSize(*size); -        } -    }      return ℑ  } diff --git a/src/video_core/shader/expr.cpp b/src/video_core/shader/expr.cpp new file mode 100644 index 000000000..2647865d4 --- /dev/null +++ b/src/video_core/shader/expr.cpp @@ -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 diff --git a/src/video_core/shader/expr.h b/src/video_core/shader/expr.h new file mode 100644 index 000000000..d3dcd00ec --- /dev/null +++ b/src/video_core/shader/expr.h @@ -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 diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index abf2cb1ab..338bab17c 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -149,10 +149,10 @@ enum class OperationCode {      TextureQueryLod,        /// (MetaTexture, float[N] coords) -> float4      TexelFetch,             /// (MetaTexture, int[N], int) -> float4 -    ImageStore,          /// (MetaImage, int[N] values) -> void +    ImageLoad,  /// (MetaImage, int[N] coords) -> void +    ImageStore, /// (MetaImage, int[N] coords) -> void +      AtomicImageAdd,      /// (MetaImage, int[N] coords) -> void -    AtomicImageMin,      /// (MetaImage, int[N] coords) -> void -    AtomicImageMax,      /// (MetaImage, int[N] coords) -> void      AtomicImageAnd,      /// (MetaImage, int[N] coords) -> void      AtomicImageOr,       /// (MetaImage, int[N] coords) -> void      AtomicImageXor,      /// (MetaImage, int[N] coords) -> void @@ -294,21 +294,18 @@ private:  class Image final {  public: -    constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, -                             std::optional<Tegra::Shader::ImageAtomicSize> size) -        : offset{offset}, index{index}, type{type}, is_bindless{false}, size{size} {} +    constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) +        : offset{offset}, index{index}, type{type}, is_bindless{false} {}      constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, -                             Tegra::Shader::ImageType type, -                             std::optional<Tegra::Shader::ImageAtomicSize> size) +                             Tegra::Shader::ImageType type)          : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, -          is_bindless{true}, size{size} {} +          is_bindless{true} {}      constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, -                             bool is_bindless, bool is_written, bool is_read, -                             std::optional<Tegra::Shader::ImageAtomicSize> size) +                             bool is_bindless, bool is_written, bool is_read, bool is_atomic)          : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, -          is_written{is_written}, is_read{is_read}, size{size} {} +          is_written{is_written}, is_read{is_read}, is_atomic{is_atomic} {}      void MarkWrite() {          is_written = true; @@ -318,8 +315,10 @@ public:          is_read = true;      } -    void SetSize(Tegra::Shader::ImageAtomicSize size_) { -        size = size_; +    void MarkAtomic() { +        MarkWrite(); +        MarkRead(); +        is_atomic = true;      }      constexpr std::size_t GetOffset() const { @@ -346,21 +345,17 @@ public:          return is_read;      } -    constexpr std::pair<u32, u32> GetBindlessCBuf() const { -        return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; +    constexpr bool IsAtomic() const { +        return is_atomic;      } -    constexpr bool IsSizeKnown() const { -        return size.has_value(); -    } - -    constexpr Tegra::Shader::ImageAtomicSize GetSize() const { -        return size.value(); +    constexpr std::pair<u32, u32> GetBindlessCBuf() const { +        return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};      }      constexpr bool operator<(const Image& rhs) const { -        return std::tie(offset, index, type, size, is_bindless) < -               std::tie(rhs.offset, rhs.index, rhs.type, rhs.size, rhs.is_bindless); +        return std::tie(offset, index, type, is_bindless) < +               std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless);      }  private: @@ -370,7 +365,7 @@ private:      bool is_bindless{};      bool is_written{};      bool is_read{}; -    std::optional<Tegra::Shader::ImageAtomicSize> size{}; +    bool is_atomic{};  };  struct GlobalMemoryBase { @@ -402,6 +397,7 @@ struct MetaTexture {  struct MetaImage {      const Image& image;      std::vector<Node> values; +    u32 element{};  };  /// Parameters that modify an operation but are not part of any particular operand diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp index 2c357f310..c1f2b88c8 100644 --- a/src/video_core/shader/shader_ir.cpp +++ b/src/video_core/shader/shader_ir.cpp @@ -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);      }  } diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index 2f03d83ba..105981d67 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -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); @@ -284,16 +304,13 @@ private:                                        bool is_shadow);      /// Accesses an image. -    Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, -                    std::optional<Tegra::Shader::ImageAtomicSize> size = {}); +    Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);      /// Access a bindless image sampler. -    Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, -                            std::optional<Tegra::Shader::ImageAtomicSize> size = {}); +    Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);      /// Tries to access an existing image, updating it's state as needed -    Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, -                               std::optional<Tegra::Shader::ImageAtomicSize> size); +    Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type);      /// Extracts a sequence of bits from a node      Node BitfieldExtract(Node value, u32 offset, u32 bits); @@ -360,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{}; @@ -367,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; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 877c6635d..ca2da8f97 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -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)}; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index dc6fa07fc..ff1c1d985 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -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() diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 92d9fb161..4cb27ddb2 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -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")); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 6b523ecdd..ba6888004 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -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(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 49fadd0ef..372427ae2 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -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> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 7c875ae87..25b2e1b05 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -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); diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp new file mode 100644 index 000000000..06566e981 --- /dev/null +++ b/src/yuzu/configuration/configure_service.cpp @@ -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 +} diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_service.h new file mode 100644 index 000000000..f5c1b703a --- /dev/null +++ b/src/yuzu/configuration/configure_service.h @@ -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}; +}; diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_service.ui new file mode 100644 index 000000000..9668dd557 --- /dev/null +++ b/src/yuzu/configuration/configure_service.ui @@ -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> diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index d5fab2f1f..a2b88c787 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -172,9 +172,7 @@ void GameList::onTextChanged(const QString& new_text) {      const int folder_count = tree_view->model()->rowCount();      QString edit_filter_text = new_text.toLower();      QStandardItem* folder; -    QStandardItem* child;      int children_total = 0; -    QModelIndex root_index = item_model->invisibleRootItem()->index();      // If the searchfield is empty every item is visible      // Otherwise the filter gets applied @@ -272,6 +270,8 @@ void GameList::onUpdateThemedIcons() {                      .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),                  Qt::DecorationRole);              break; +        default: +            break;          }      }  } @@ -392,6 +392,8 @@ void GameList::ValidateEntry(const QModelIndex& item) {      case GameListItemType::AddDir:          emit AddDirectory();          break; +    default: +        break;      }  } @@ -462,6 +464,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {      case GameListItemType::SysNandDir:          AddPermDirPopup(context_menu, selected);          break; +    default: +        break;      }      context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));  } diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index a8d888fee..1c2b37afd 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -247,7 +247,7 @@ public:                  Qt::DecorationRole);              setData(QObject::tr("System Titles"), Qt::DisplayRole);              break; -        case GameListItemType::CustomDir: +        case GameListItemType::CustomDir: {              const QString icon_name = QFileInfo::exists(game_dir->path)                                            ? QStringLiteral("folder")                                            : QStringLiteral("bad_folder"); @@ -256,8 +256,11 @@ public:                      Qt::DecorationRole);              setData(game_dir->path, Qt::DisplayRole);              break; -        }; -    }; +        } +        default: +            break; +        } +    }      int type() const override {          return static_cast<int>(dir_type); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index fd21a9761..4c81ef12b 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -326,10 +326,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa                  }              } else {                  std::vector<u8> icon; -                const auto res1 = loader->ReadIcon(icon); +                [[maybe_unused]] const auto res1 = loader->ReadIcon(icon);                  std::string name = " "; -                const auto res3 = loader->ReadTitle(name); +                [[maybe_unused]] const auto res3 = loader->ReadTitle(name);                  const FileSys::PatchManager patch{program_id}; @@ -354,20 +354,20 @@ void GameListWorker::run() {      for (UISettings::GameDir& game_dir : game_dirs) {          if (game_dir.path == QStringLiteral("SDMC")) {              auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); -            emit DirEntryReady({game_list_dir}); +            emit DirEntryReady(game_list_dir);              AddTitlesToGameList(game_list_dir);          } else if (game_dir.path == QStringLiteral("UserNAND")) {              auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); -            emit DirEntryReady({game_list_dir}); +            emit DirEntryReady(game_list_dir);              AddTitlesToGameList(game_list_dir);          } else if (game_dir.path == QStringLiteral("SysNAND")) {              auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); -            emit DirEntryReady({game_list_dir}); +            emit DirEntryReady(game_list_dir);              AddTitlesToGameList(game_list_dir);          } else {              watch_list.append(game_dir.path);              auto* const game_list_dir = new GameListDir(game_dir); -            emit DirEntryReady({game_list_dir}); +            emit DirEntryReady(game_list_dir);              provider->ClearAllEntries();              ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,                             game_list_dir); diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 6e52fca89..84e4e1b42 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -75,8 +75,9 @@ private:      std::shared_ptr<FileSys::VfsFilesystem> vfs;      FileSys::ManualContentProvider* provider; -    QStringList watch_list; -    const CompatibilityList& compatibility_list;      QVector<UISettings::GameDir>& game_dirs; +    const CompatibilityList& compatibility_list; + +    QStringList watch_list;      std::atomic_bool stop_processing;  }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 2d82df739..451e4a4d6 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1889,15 +1889,24 @@ void GMainWindow::OnCaptureScreenshot() {  }  void GMainWindow::UpdateWindowTitle(const QString& title_name) { -    const QString full_name = QString::fromUtf8(Common::g_build_fullname); -    const QString branch_name = QString::fromUtf8(Common::g_scm_branch); -    const QString description = QString::fromUtf8(Common::g_scm_desc); +    const auto full_name = std::string(Common::g_build_fullname); +    const auto branch_name = std::string(Common::g_scm_branch); +    const auto description = std::string(Common::g_scm_desc); +    const auto build_id = std::string(Common::g_build_id); + +    const auto date = +        QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString();      if (title_name.isEmpty()) { -        setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description)); +        const auto fmt = std::string(Common::g_title_bar_format_idle); +        setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt, +                                                          full_name, branch_name, description, +                                                          std::string{}, date, build_id)));      } else { -        setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3") -                           .arg(full_name, branch_name, description, title_name)); +        const auto fmt = std::string(Common::g_title_bar_format_running); +        setWindowTitle(QString::fromStdString( +            fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name, +                        description, title_name.toStdString(), date, build_id)));      }  } diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index 7f7d247a3..43bad9678 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp @@ -9,6 +9,8 @@ namespace UISettings {  const Themes themes{{      {"Default", "default"},      {"Dark", "qdarkstyle"}, +    {"Colorful", "colorful"}, +    {"Colorful Dark", "colorful_dark"},  }};  Values values = {}; diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index c57290006..a8eaf5f8c 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -24,7 +24,7 @@ struct Shortcut {      ContextualShortcut shortcut;  }; -using Themes = std::array<std::pair<const char*, const char*>, 2>; +using Themes = std::array<std::pair<const char*, const char*>, 4>;  extern const Themes themes;  struct GameDir { diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index d82438502..1a812cb87 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -433,6 +433,11 @@ void Config::ReadValues() {          sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");      Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");      Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); + +    // Services +    Settings::values.bcat_backend = sdl2_config->Get("Services", "bcat_backend", "boxcat"); +    Settings::values.bcat_boxcat_local = +        sdl2_config->GetBoolean("Services", "bcat_boxcat_local", false);  }  void Config::Reload() { diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index a6171c3ed..8d18a4a5a 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -251,6 +251,11 @@ web_api_url = https://api.yuzu-emu.org  yuzu_username =  yuzu_token = +[Services] +# The name of the backend to use for BCAT +# If this is set to 'boxcat' boxcat will be used, otherwise a null implementation will be used +bcat_backend = +  [AddOns]  # Used to disable add-ons  # List of title IDs of games that will have add-ons disabled (separated by '|'): diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index a6edc089a..b1c512db1 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -4,6 +4,9 @@  #include <SDL.h>  #include "common/logging/log.h" +#include "common/scm_rev.h" +#include "core/core.h" +#include "core/perf_stats.h"  #include "input_common/keyboard.h"  #include "input_common/main.h"  #include "input_common/motion_emu.h" @@ -170,6 +173,16 @@ void EmuWindow_SDL2::PollEvents() {              break;          }      } + +    const u32 current_time = SDL_GetTicks(); +    if (current_time > last_time + 2000) { +        const auto results = Core::System::GetInstance().GetAndResetPerfStats(); +        const auto title = fmt::format( +            "yuzu {} | {}-{} | FPS: {:.0f} ({:.0%})", Common::g_build_fullname, +            Common::g_scm_branch, Common::g_scm_desc, results.game_fps, results.emulation_speed); +        SDL_SetWindowTitle(render_window, title.c_str()); +        last_time = current_time; +    }  }  void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) { diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index d8051ebdf..eaa971f77 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -60,4 +60,7 @@ protected:      /// Internal SDL2 render window      SDL_Window* render_window; + +    /// Keeps track of how often to update the title bar during gameplay +    u32 last_time = 0;  }; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index bac05b959..3ee088a91 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -186,8 +186,6 @@ int main(int argc, char** argv) {      system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());      system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); -    SCOPE_EXIT({ system.Shutdown(); }); -      const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};      switch (load_result) { @@ -227,6 +225,8 @@ int main(int argc, char** argv) {          system.RunLoop();      } +    system.Shutdown(); +      detached_tasks.WaitForAllTasks();      return 0;  } diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp index 9a11dc6c3..84ab4d687 100644 --- a/src/yuzu_tester/config.cpp +++ b/src/yuzu_tester/config.cpp @@ -93,7 +93,6 @@ void Config::ReadValues() {      // System      Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); -    const auto size = sdl2_config->GetInteger("System", "users_size", 0);      Settings::values.current_user = std::clamp<int>(          sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); | 
