summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.ci/scripts/linux/docker.sh3
-rw-r--r--.ci/scripts/merge/apply-patches-by-label-private.py45
-rw-r--r--.ci/scripts/merge/apply-patches-by-label.py16
-rw-r--r--.ci/scripts/windows/upload.ps132
-rw-r--r--.ci/templates/build-mock.yml5
-rw-r--r--.ci/templates/build-msvc.yml21
-rw-r--r--.ci/templates/build-single.yml15
-rw-r--r--.ci/templates/merge-private.yml47
-rw-r--r--.ci/templates/mergebot-private.yml30
-rw-r--r--.ci/templates/mergebot.yml2
-rw-r--r--.ci/templates/release-download.yml13
-rw-r--r--.ci/templates/release-github.yml11
-rw-r--r--.ci/templates/release-universal.yml10
-rw-r--r--.ci/yuzu-mainline-step1.yml8
-rw-r--r--.ci/yuzu-mainline-step2.yml63
-rw-r--r--.ci/yuzu-mainline.yml25
-rw-r--r--.ci/yuzu-patreon-step1.yml8
-rw-r--r--.ci/yuzu-patreon-step2.yml30
-rw-r--r--.ci/yuzu-patreon.yml19
-rw-r--r--.gitmodules6
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md1
-rw-r--r--externals/CMakeLists.txt6
m---------externals/fmt0
m---------externals/libzip0
m---------externals/zlib0
-rw-r--r--license.txt33
-rw-r--r--src/audio_core/audio_renderer.cpp10
-rw-r--r--src/audio_core/audio_renderer.h25
-rw-r--r--src/common/CMakeLists.txt3
-rw-r--r--src/core/CMakeLists.txt20
-rw-r--r--src/core/core.cpp20
-rw-r--r--src/core/core.h8
-rw-r--r--src/core/crypto/key_manager.cpp2
-rw-r--r--src/core/file_sys/bis_factory.cpp5
-rw-r--r--src/core/file_sys/bis_factory.h2
-rw-r--r--src/core/file_sys/vfs_libzip.cpp79
-rw-r--r--src/core/file_sys/vfs_libzip.h13
-rw-r--r--src/core/hle/service/acc/acc.cpp14
-rw-r--r--src/core/hle/service/am/am.cpp100
-rw-r--r--src/core/hle/service/am/am.h7
-rw-r--r--src/core/hle/service/am/applet_ae.h2
-rw-r--r--src/core/hle/service/am/applet_oe.h2
-rw-r--r--src/core/hle/service/am/applets/applets.cpp4
-rw-r--r--src/core/hle/service/am/applets/applets.h2
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp19
-rw-r--r--src/core/hle/service/aoc/aoc_u.h5
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp136
-rw-r--r--src/core/hle/service/bcat/backend/backend.h147
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp503
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.h58
-rw-r--r--src/core/hle/service/bcat/bcat.cpp8
-rw-r--r--src/core/hle/service/bcat/bcat.h3
-rw-r--r--src/core/hle/service/bcat/module.cpp557
-rw-r--r--src/core/hle/service/bcat/module.h24
-rw-r--r--src/core/hle/service/btdrv/btdrv.cpp8
-rw-r--r--src/core/hle/service/btdrv/btdrv.h6
-rw-r--r--src/core/hle/service/btm/btm.cpp14
-rw-r--r--src/core/hle/service/btm/btm.h6
-rw-r--r--src/core/hle/service/fatal/fatal.cpp29
-rw-r--r--src/core/hle/service/fatal/fatal.h9
-rw-r--r--src/core/hle/service/fatal/fatal_p.cpp4
-rw-r--r--src/core/hle/service/fatal/fatal_p.h2
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp3
-rw-r--r--src/core/hle/service/fatal/fatal_u.h2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp9
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/core/hle/service/friend/friend.cpp31
-rw-r--r--src/core/hle/service/friend/friend.h9
-rw-r--r--src/core/hle/service/friend/interface.cpp4
-rw-r--r--src/core/hle/service/friend/interface.h2
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h8
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h3
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h3
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h3
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h3
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp28
-rw-r--r--src/core/hle/service/hid/controllers/npad.h8
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.h3
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h3
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h3
-rw-r--r--src/core/hle/service/hid/hid.cpp25
-rw-r--r--src/core/hle/service/hid/hid.h10
-rw-r--r--src/core/hle/service/hid/irs.cpp6
-rw-r--r--src/core/hle/service/hid/irs.h3
-rw-r--r--src/core/hle/service/ldr/ldr.cpp11
-rw-r--r--src/core/hle/service/ldr/ldr.h2
-rw-r--r--src/core/hle/service/nfp/nfp.cpp19
-rw-r--r--src/core/hle/service/nfp/nfp.h5
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp4
-rw-r--r--src/core/hle/service/nfp/nfp_user.h2
-rw-r--r--src/core/hle/service/nifm/nifm.cpp43
-rw-r--r--src/core/hle/service/nifm/nifm.h6
-rw-r--r--src/core/hle/service/nim/nim.cpp13
-rw-r--r--src/core/hle/service/nim/nim.h6
-rw-r--r--src/core/hle/service/ns/ns.cpp5
-rw-r--r--src/core/hle/service/ns/ns.h3
-rw-r--r--src/core/hle/service/ns/pl_u.cpp10
-rw-r--r--src/core/hle/service/ns/pl_u.h3
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp18
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp20
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h5
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp75
-rw-r--r--src/core/hle/service/nvdrv/interface.h3
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h6
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp7
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h5
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp38
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h5
-rw-r--r--src/core/hle/service/prepo/prepo.cpp14
-rw-r--r--src/core/hle/service/prepo/prepo.h6
-rw-r--r--src/core/hle/service/service.cpp28
-rw-r--r--src/core/hle/service/time/interface.cpp4
-rw-r--r--src/core/hle/service/time/interface.h2
-rw-r--r--src/core/hle/service/time/time.cpp25
-rw-r--r--src/core/hle/service/time/time.h4
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp4
-rw-r--r--src/core/hle/service/vi/display/vi_display.h2
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/settings.cpp2
-rw-r--r--src/core/settings.h4
-rw-r--r--src/video_core/engines/fermi_2d.cpp4
-rw-r--r--src/video_core/engines/maxwell_3d.cpp102
-rw-r--r--src/video_core/engines/maxwell_3d.h29
-rw-r--r--src/video_core/engines/shader_bytecode.h8
-rw-r--r--src/video_core/gpu.cpp1
-rw-r--r--src/video_core/gpu.h1
-rw-r--r--src/video_core/macro_interpreter.cpp2
-rw-r--r--src/video_core/morton.cpp2
-rw-r--r--src/video_core/rasterizer_interface.h9
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_device.h5
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp181
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h9
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp16
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp184
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp1
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp15
-rw-r--r--src/video_core/shader/decode/image.cpp137
-rw-r--r--src/video_core/shader/node.h46
-rw-r--r--src/video_core/shader/shader_ir.cpp12
-rw-r--r--src/video_core/shader/shader_ir.h19
-rw-r--r--src/video_core/surface.cpp3
-rw-r--r--src/video_core/surface.h50
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp21
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_general.cpp1
-rw-r--r--src/yuzu/configuration/configure_service.cpp136
-rw-r--r--src/yuzu/configuration/configure_service.h34
-rw-r--r--src/yuzu/configuration/configure_service.ui124
-rw-r--r--src/yuzu/main.cpp43
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu/uisettings.cpp2
-rw-r--r--src/yuzu/uisettings.h2
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/default_ini.h5
186 files changed, 3480 insertions, 798 deletions
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index f538a4081..296b14e56 100644
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -11,4 +11,5 @@ 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/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/upload.ps1 b/.ci/scripts/windows/upload.ps1
new file mode 100644
index 000000000..de86e7390
--- /dev/null
+++ b/.ci/scripts/windows/upload.ps1
@@ -0,0 +1,32 @@
+$GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
+$GITREV = $(git show -s --format='%h')
+$RELEASE_DIST = "yuzu-windows-msvc"
+
+$MSVC_BUILD_ZIP = "yuzu-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
+$MSVC_BUILD_PDB = "yuzu-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
+$MSVC_SEVENZIP = "yuzu-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
+
+$env:BUILD_ZIP = $MSVC_BUILD_ZIP
+$env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
+$env:BUILD_UPDATE = $MSVC_SEVENZIP
+
+$BUILD_DIR = ".\build\bin\Release"
+
+mkdir pdb
+Get-ChildItem "$BUILD_DIR\" -Recurse -Filter "*.pdb" | Copy-Item -destination .\pdb
+7z a -tzip $MSVC_BUILD_PDB .\pdb\*.pdb
+rm "$BUILD_DIR\*.pdb"
+mkdir $RELEASE_DIST
+mkdir "artifacts"
+
+Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
+rm "$RELEASE_DIST\*.exe"
+Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
+Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
+Copy-Item .\license.txt -Destination $RELEASE_DIST
+Copy-Item .\README.md -Destination $RELEASE_DIST
+7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
+7z a $MSVC_SEVENZIP $RELEASE_DIST
+
+Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts"
+Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts" \ 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..e7aba93de
--- /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)-$(BuildSuffix)'
+ 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..906afa382
--- /dev/null
+++ b/.ci/templates/build-msvc.yml
@@ -0,0 +1,21 @@
+parameters:
+ artifactSource: 'true'
+ cache: 'false'
+
+steps:
+- script: mkdir build && cd build && set DATE=`date '+%Y.%m.%d'` && set CI=true && set AZURE_REPO_NAME=yuzu-emu/yuzu-$(BuildName) && set AZURE_REPO_TAG=$(BuildName)-$DATE && 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 .. && 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..002f5d24f 100644
--- a/.ci/templates/build-single.yml
+++ b/.ci/templates/build-single.yml
@@ -7,14 +7,13 @@ steps:
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: export DATE=`date '+%Y.%m.%d'` && export CI=true && export AZURE_REPO_NAME=yuzu-emu/yuzu-$(BuildName) && export AZURE_REPO_TAG=$(BuildName)-$DATE && chmod a+x ./.ci/scripts/$(ScriptFolder)/exec.sh && ./.ci/scripts/$(ScriptFolder)/exec.sh
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/merge-private.yml b/.ci/templates/merge-private.yml
new file mode 100644
index 000000000..a640cfbde
--- /dev/null
+++ b/.ci/templates/merge-private.yml
@@ -0,0 +1,47 @@
+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 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)
+ 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..39fd47f1c
--- /dev/null
+++ b/.ci/templates/release-github.yml
@@ -0,0 +1,11 @@
+steps:
+ - template: ./release-download.yml
+ - task: GitHubRelease@0
+ inputs:
+ action: 'create'
+ title: 'yuzu $(BuildName) #$(Build.BuildId)'
+ assets: '$(Build.ArtifactStagingDirectory)/*'
+ gitHubConnection: $(GitHubReleaseConnectionName)
+ repositoryName: '$(Build.Repository.Name)'
+ target: '$(Build.SourceVersion)'
+ tagSource: 'auto' \ 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..0c1af397b
--- /dev/null
+++ b/.ci/yuzu-mainline-step2.yml
@@ -0,0 +1,63 @@
+trigger:
+- master
+
+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'
+- 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'
+- stage: release
+ displayName: 'Release'
+ dependsOn:
+ - build
+ - build_win
+ jobs:
+ - job: github
+ displayName: 'GitHub Release'
+ 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..23e45d9dd
--- /dev/null
+++ b/.ci/yuzu-patreon-step2.yml
@@ -0,0 +1,30 @@
+trigger:
+- master
+
+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)
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/README.md b/README.md
index ecace43f2..bdee2e872 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
yuzu emulator
=============
[![Travis CI Build Status](https://travis-ci.org/yuzu-emu/yuzu.svg?branch=master)](https://travis-ci.org/yuzu-emu/yuzu)
-[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/77k97svb2usreu68?svg=true)](https://ci.appveyor.com/project/bunnei/yuzu)
[![Azure Mainline CI Build Status](https://dev.azure.com/yuzu-emu/yuzu/_apis/build/status/yuzu%20mainline?branchName=master)](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/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index da50a0bbc..e6f38d600 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -107,6 +107,11 @@ Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
+static constexpr u32 VersionFromRevision(u32_le rev) {
+ // "REV7" -> 7
+ return ((rev >> 24) & 0xff) - 0x30;
+}
+
std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
// Copy UpdateDataHeader struct
UpdateDataHeader config{};
@@ -166,6 +171,11 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
// Copy output header
UpdateDataHeader response_data{worker_params};
std::vector<u8> output_params(response_data.total_size);
+ const auto audren_revision = VersionFromRevision(config.revision);
+ if (audren_revision >= 5) {
+ response_data.frame_count = 0x10;
+ response_data.total_size += 0x10;
+ }
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
// Copy output memory pool entries
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index 45afbe759..4f14b91cd 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -194,21 +194,24 @@ struct UpdateDataHeader {
mixes_size = 0x0;
sinks_size = config.sink_count * 0x20;
performance_manager_size = 0x10;
+ frame_count = 0;
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
effects_size + sinks_size + performance_manager_size;
}
- u32_le revision;
- u32_le behavior_size;
- u32_le memory_pools_size;
- u32_le voices_size;
- u32_le voice_resource_size;
- u32_le effects_size;
- u32_le mixes_size;
- u32_le sinks_size;
- u32_le performance_manager_size;
- INSERT_PADDING_WORDS(6);
- u32_le total_size;
+ u32_le revision{};
+ u32_le behavior_size{};
+ u32_le memory_pools_size{};
+ u32_le voices_size{};
+ u32_le voice_resource_size{};
+ u32_le effects_size{};
+ u32_le mixes_size{};
+ u32_le sinks_size{};
+ u32_le performance_manager_size{};
+ INSERT_PADDING_WORDS(1);
+ u32_le frame_count{};
+ INSERT_PADDING_WORDS(4);
+ u32_le total_size{};
};
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 01abdb3bb..dfed8b51d 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -10,6 +10,9 @@ 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()
add_custom_command(OUTPUT scm_rev.cpp
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a6b56c9c6..3416854db 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
@@ -499,6 +510,15 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
+
+if (YUZU_ENABLE_BOXCAT)
+ get_directory_property(OPENSSL_LIBS
+ DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
+ DEFINITION OPENSSL_LIBS)
+ target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
+ target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
+endif()
+
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 76bb2bae9..75a7ffb97 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -163,6 +163,7 @@ struct System::Impl {
gpu_core = VideoCore::CreateGPU(system);
is_powered_on = true;
+ exit_lock = false;
LOG_DEBUG(Core, "Initialized OK");
@@ -249,6 +250,7 @@ struct System::Impl {
perf_stats->GetMeanFrametime());
is_powered_on = false;
+ exit_lock = false;
// Shutdown emulation session
renderer.reset();
@@ -333,9 +335,11 @@ struct System::Impl {
std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager;
CpuCoreManager cpu_core_manager;
bool is_powered_on = false;
+ bool exit_lock = false;
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;
@@ -629,6 +633,22 @@ const Service::APM::Controller& System::GetAPMController() const {
return impl->apm_controller;
}
+void System::SetExitLock(bool locked) {
+ impl->exit_lock = locked;
+}
+
+bool System::GetExitLock() const {
+ return impl->exit_lock;
+}
+
+void System::SetCurrentProcessBuildID(std::array<u8, 32> id) {
+ impl->build_id = id;
+}
+
+const std::array<u8, 32>& System::GetCurrentProcessBuildID() const {
+ return impl->build_id;
+}
+
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(*this, emu_window);
}
diff --git a/src/core/core.h b/src/core/core.h
index d2a3c82d8..f49b7fbf9 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -326,6 +326,14 @@ public:
const Service::APM::Controller& GetAPMController() const;
+ void SetExitLock(bool locked);
+
+ bool GetExitLock() const;
+
+ void SetCurrentProcessBuildID(std::array<u8, 0x20> id);
+
+ const std::array<u8, 0x20>& GetCurrentProcessBuildID() const;
+
private:
System();
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/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/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index a7c55e116..0c0f7ed6e 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -70,7 +70,7 @@ public:
protected:
void Get(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
+ LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
ProfileData data{};
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
@@ -89,7 +89,7 @@ protected:
}
void GetBase(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
+ LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
if (profile_manager.GetProfileBase(user_id, profile_base)) {
IPC::ResponseBuilder rb{ctx, 16};
@@ -263,7 +263,7 @@ private:
};
void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_ACC, "called");
+ LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount()));
@@ -272,7 +272,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
Common::UUID user_id = rp.PopRaw<Common::UUID>();
- LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
+ LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -280,21 +280,21 @@ void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
}
void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_ACC, "called");
+ LOG_DEBUG(Service_ACC, "called");
ctx.WriteBuffer(profile_manager->GetAllUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_ACC, "called");
+ LOG_DEBUG(Service_ACC, "called");
ctx.WriteBuffer(profile_manager->GetOpenUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_ACC, "called");
+ LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser());
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 6c594dcaf..34409e0c3 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_} {
@@ -232,12 +238,12 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
IDebugFunctions::~IDebugFunctions() = default;
-ISelfController::ISelfController(Core::System& system_,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger_)
- : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) {
+ISelfController::ISelfController(Core::System& system,
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Exit"},
+ {0, &ISelfController::Exit, "Exit"},
{1, &ISelfController::LockExit, "LockExit"},
{2, &ISelfController::UnlockExit, "UnlockExit"},
{3, &ISelfController::EnterFatalSection, "EnterFatalSection"},
@@ -282,7 +288,7 @@ ISelfController::ISelfController(Core::System& system_,
RegisterHandlers(functions);
- auto& kernel = system_.Kernel();
+ auto& kernel = system.Kernel();
launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"ISelfController:LaunchableEvent");
@@ -298,15 +304,28 @@ ISelfController::ISelfController(Core::System& system_,
ISelfController::~ISelfController() = default;
+void ISelfController::Exit(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ system.Shutdown();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ system.SetExitLock(true);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ system.SetExitLock(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -550,6 +569,10 @@ void AppletMessageQueue::OperationModeChanged() {
on_operation_mode_changed.writable->Signal();
}
+void AppletMessageQueue::RequestExit() {
+ PushMessage(AppletMessage::ExitRequested);
+}
+
ICommonStateGetter::ICommonStateGetter(Core::System& system,
std::shared_ptr<AppletMessageQueue> msg_queue)
: ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) {
@@ -1066,7 +1089,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
}
@@ -1111,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
}
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_AM, "called");
+ IPC::RequestParser rp{ctx};
+ const auto kind = rp.PopEnum<LaunchParameterKind>();
- LaunchParameters params{};
+ LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
- params.magic = POP_LAUNCH_PARAMETER_MAGIC;
- params.is_account_selected = 1;
+ if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
+ const auto backend = BCAT::CreateBackendFromSettings(
+ [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); });
+ const auto build_id_full = Core::System::GetInstance().GetCurrentProcessBuildID();
+ u64 build_id{};
+ std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
- Account::ProfileManager profile_manager{};
- const auto uuid = profile_manager.GetUser(Settings::values.current_user);
- ASSERT(uuid);
- params.current_user = uuid->uuid;
+ const auto data =
+ backend->GetLaunchParameter({Core::CurrentProcess()->GetTitleID(), build_id});
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ if (data.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorage>(*data);
+ launch_popped_application_specific = true;
+ return;
+ }
+ } else if (kind == LaunchParameterKind::AccountPreselectedUser &&
+ !launch_popped_account_preselect) {
+ LaunchParameterAccountPreselectedUser params{};
- rb.Push(RESULT_SUCCESS);
+ params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
+ params.is_account_selected = 1;
+
+ Account::ProfileManager profile_manager{};
+ const auto uuid = profile_manager.GetUser(Settings::values.current_user);
+ ASSERT(uuid);
+ params.current_user = uuid->uuid;
- std::vector<u8> buffer(sizeof(LaunchParameters));
- std::memcpy(buffer.data(), &params, buffer.size());
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+
+ std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
+ std::memcpy(buffer.data(), &params, buffer.size());
- rb.PushIpcInterface<AM::IStorage>(buffer);
+ 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(
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 28f870302..9169eb2bd 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -45,6 +45,7 @@ class AppletMessageQueue {
public:
enum class AppletMessage : u32 {
NoMessage = 0,
+ ExitRequested = 4,
FocusStateChanged = 15,
OperationModeChanged = 30,
PerformanceModeChanged = 31,
@@ -59,6 +60,7 @@ public:
AppletMessage PopMessage();
std::size_t GetMessageCount() const;
void OperationModeChanged();
+ void RequestExit();
private:
std::queue<AppletMessage> messages;
@@ -123,6 +125,7 @@ public:
~ISelfController() override;
private:
+ void Exit(Kernel::HLERequestContext& ctx);
void LockExit(Kernel::HLERequestContext& ctx);
void UnlockExit(Kernel::HLERequestContext& ctx);
void EnterFatalSection(Kernel::HLERequestContext& ctx);
@@ -151,6 +154,8 @@ 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> {
@@ -250,6 +255,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/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index 0e0d10858..2e3e45915 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.h
@@ -19,6 +19,8 @@ class NVFlinger;
namespace AM {
+class AppletMessageQueue;
+
class AppletAE final : public ServiceFramework<AppletAE> {
public:
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index 99a65e7b5..758da792d 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.h
@@ -19,6 +19,8 @@ class NVFlinger;
namespace AM {
+class AppletMessageQueue;
+
class AppletOE final : public ServiceFramework<AppletOE> {
public:
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
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/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index d3e97776b..e9cf1e840 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -29,9 +29,9 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
}
-static std::vector<u64> AccumulateAOCTitleIDs() {
+static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
std::vector<u64> add_on_content;
- const auto& rcu = Core::System::GetInstance().GetContentProvider();
+ const auto& rcu = system.GetContentProvider();
const auto list =
rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
@@ -47,7 +47,8 @@ static std::vector<u64> AccumulateAOCTitleIDs() {
return add_on_content;
}
-AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs()) {
+AOC_U::AOC_U(Core::System& system)
+ : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs(system)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CountAddOnContentByApplicationId"},
@@ -65,7 +66,7 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"GetAddOnContentListChanged:Event");
}
@@ -86,7 +87,7 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ const auto current = system.CurrentProcess()->GetTitleID();
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
@@ -113,7 +114,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
process_id);
- const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ const auto current = system.CurrentProcess()->GetTitleID();
std::vector<u32> out;
const auto& disabled = Settings::values.disabled_addons[current];
@@ -159,7 +160,7 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
FileSys::PatchManager pm{title_id};
const auto res = pm.GetControlMetadata();
@@ -196,8 +197,8 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(aoc_change_event.readable);
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<AOC_U>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<AOC_U>(system)->InstallAsService(service_manager);
}
} // namespace Service::AOC
diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h
index 5effea730..848b2f416 100644
--- a/src/core/hle/service/aoc/aoc_u.h
+++ b/src/core/hle/service/aoc/aoc_u.h
@@ -14,7 +14,7 @@ namespace Service::AOC {
class AOC_U final : public ServiceFramework<AOC_U> {
public:
- AOC_U();
+ explicit AOC_U(Core::System& system);
~AOC_U() override;
private:
@@ -26,9 +26,10 @@ private:
std::vector<u64> add_on_content;
Kernel::EventPair aoc_change_event;
+ Core::System& system;
};
/// Registers all AOC services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::AOC
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..9b677debe
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -0,0 +1,136 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/lock.h"
+#include "core/hle/service/bcat/backend/backend.h"
+
+namespace Service::BCAT {
+
+ProgressServiceBackend::ProgressServiceBackend(std::string event_name) : impl{} {
+ auto& kernel{Core::System::GetInstance().Kernel()};
+ event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Automatic, "ProgressServiceBackend:UpdateEvent:" + event_name);
+}
+
+Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
+ return event.readable;
+}
+
+DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
+ return impl;
+}
+
+void ProgressServiceBackend::SetNeedHLELock(bool need) {
+ need_hle_lock = need;
+}
+
+void ProgressServiceBackend::SetTotalSize(u64 size) {
+ impl.total_bytes = size;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::StartConnecting() {
+ impl.status = DeliveryCacheProgressImpl::Status::Connecting;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::StartProcessingDataList() {
+ impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
+ std::string_view file_name, u64 file_size) {
+ impl.status = DeliveryCacheProgressImpl::Status::Downloading;
+ impl.current_downloaded_bytes = 0;
+ impl.current_total_bytes = file_size;
+ std::memcpy(impl.current_directory.data(), dir_name.data(),
+ std::min<u64>(dir_name.size(), 0x31ull));
+ std::memcpy(impl.current_file.data(), file_name.data(),
+ std::min<u64>(file_name.size(), 0x31ull));
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
+ impl.current_downloaded_bytes = downloaded;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::FinishDownloadingFile() {
+ impl.total_downloaded_bytes += impl.current_total_bytes;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
+ impl.status = DeliveryCacheProgressImpl::Status::Committing;
+ impl.current_file.fill(0);
+ impl.current_downloaded_bytes = 0;
+ impl.current_total_bytes = 0;
+ std::memcpy(impl.current_directory.data(), dir_name.data(),
+ std::min<u64>(dir_name.size(), 0x31ull));
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::FinishDownload(ResultCode result) {
+ impl.total_downloaded_bytes = impl.total_bytes;
+ impl.status = DeliveryCacheProgressImpl::Status::Done;
+ impl.result = result;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::SignalUpdate() const {
+ if (need_hle_lock) {
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ event.writable->Signal();
+ } else {
+ event.writable->Signal();
+ }
+}
+
+Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
+
+Backend::~Backend() = default;
+
+NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(getter)) {}
+
+NullBackend::~NullBackend() = default;
+
+bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
+ title.build_id);
+
+ progress.FinishDownload(RESULT_SUCCESS);
+ return true;
+}
+
+bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
+ title.build_id, name);
+
+ progress.FinishDownload(RESULT_SUCCESS);
+ return true;
+}
+
+bool NullBackend::Clear(u64 title_id) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}");
+
+ return true;
+}
+
+void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
+ Common::HexToString(passphrase));
+}
+
+std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
+ title.build_id);
+ return std::nullopt;
+}
+
+} // namespace Service::BCAT
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..3f5d8b5dd
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -0,0 +1,147 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <optional>
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/result.h"
+
+namespace Service::BCAT {
+
+struct DeliveryCacheProgressImpl;
+
+using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
+using Passphrase = std::array<u8, 0x20>;
+
+struct TitleIDVersion {
+ u64 title_id;
+ u64 build_id;
+};
+
+using DirectoryName = std::array<char, 0x20>;
+using FileName = std::array<char, 0x20>;
+
+struct DeliveryCacheProgressImpl {
+ enum class Status : s32 {
+ None = 0x0,
+ Queued = 0x1,
+ Connecting = 0x2,
+ ProcessingDataList = 0x3,
+ Downloading = 0x4,
+ Committing = 0x5,
+ Done = 0x9,
+ };
+
+ Status status;
+ ResultCode result = RESULT_SUCCESS;
+ DirectoryName current_directory;
+ FileName current_file;
+ s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
+ s64 current_total_bytes; ///< Bytes total on current file.
+ s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
+ s64 total_bytes; ///< Bytes total on overall download.
+ INSERT_PADDING_BYTES(
+ 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
+};
+static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
+ "DeliveryCacheProgressImpl has incorrect size.");
+
+// A class to manage the signalling to the game about BCAT download progress.
+// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
+class ProgressServiceBackend {
+ friend class IBcatService;
+
+public:
+ // Clients should call this with true if any of the functions are going to be called from a
+ // non-HLE thread and this class need to lock the hle mutex. (default is false)
+ void SetNeedHLELock(bool need);
+
+ // Sets the number of bytes total in the entire download.
+ void SetTotalSize(u64 size);
+
+ // Notifies the application that the backend has started connecting to the server.
+ void StartConnecting();
+ // Notifies the application that the backend has begun accumulating and processing metadata.
+ void StartProcessingDataList();
+
+ // Notifies the application that a file is starting to be downloaded.
+ void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
+ // Updates the progress of the current file to the size passed.
+ void UpdateFileProgress(u64 downloaded);
+ // Notifies the application that the current file has completed download.
+ void FinishDownloadingFile();
+
+ // Notifies the application that all files in this directory have completed and are being
+ // finalized.
+ void CommitDirectory(std::string_view dir_name);
+
+ // Notifies the application that the operation completed with result code result.
+ void FinishDownload(ResultCode result);
+
+private:
+ explicit ProgressServiceBackend(std::string event_name);
+
+ Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
+ DeliveryCacheProgressImpl& GetImpl();
+
+ void SignalUpdate() const;
+
+ DeliveryCacheProgressImpl impl;
+ Kernel::EventPair event;
+ bool need_hle_lock = false;
+};
+
+// A class representing an abstract backend for BCAT functionality.
+class Backend {
+public:
+ explicit Backend(DirectoryGetter getter);
+ virtual ~Backend();
+
+ // Called when the backend is needed to synchronize the data for the game with title ID and
+ // version in title. A ProgressServiceBackend object is provided to alert the application of
+ // status.
+ virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
+ // Very similar to Synchronize, but only for the directory provided. Backends should not alter
+ // the data for any other directories.
+ virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) = 0;
+
+ // Removes all cached data associated with title id provided.
+ virtual bool Clear(u64 title_id) = 0;
+
+ // Sets the BCAT Passphrase to be used with the associated title ID.
+ virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
+
+ // Gets the launch parameter used by AM associated with the title ID and version provided.
+ virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
+
+protected:
+ DirectoryGetter dir_getter;
+};
+
+// A backend of BCAT that provides no operation.
+class NullBackend : public Backend {
+public:
+ explicit NullBackend(const DirectoryGetter& getter);
+ ~NullBackend() override;
+
+ bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
+ bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) override;
+
+ bool Clear(u64 title_id) override;
+
+ void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
+
+ std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
+};
+
+std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
+
+} // namespace Service::BCAT
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..e6ee0810b
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -0,0 +1,503 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/ostream.h>
+#include <httplib.h>
+#include <json.hpp>
+#include <mbedtls/sha256.h>
+#include "common/hex_util.h"
+#include "common/logging/backend.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_libzip.h"
+#include "core/file_sys/vfs_vector.h"
+#include "core/frontend/applets/error.h"
+#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/bcat/backend/boxcat.h"
+#include "core/settings.h"
+
+namespace {
+
+// Prevents conflicts with windows macro called CreateFile
+FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
+ return dir->CreateFile(name);
+}
+
+// Prevents conflicts with windows macro called DeleteFile
+bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
+ return dir->DeleteFile(name);
+}
+
+} // Anonymous namespace
+
+namespace Service::BCAT {
+
+constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
+
+constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
+
+// Formatted using fmt with arg[0] = hex title id
+constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
+constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
+
+constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
+
+constexpr char BOXCAT_API_VERSION[] = "1";
+constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
+
+// HTTP status codes for Boxcat
+enum class ResponseStatus {
+ Ok = 200, ///< Operation completed successfully.
+ BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
+ NoUpdate = 304, ///< The digest provided would match the new data, no need to update.
+ NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation.
+ NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format
+ ///< issues or whatnot) and has no data.
+};
+
+enum class DownloadResult {
+ Success = 0,
+ NoResponse,
+ GeneralWebError,
+ NoMatchTitleId,
+ NoMatchBuildId,
+ InvalidContentType,
+ GeneralFSError,
+ BadClientVersion,
+};
+
+constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{
+ "Success",
+ "There was no response from the server.",
+ "There was a general web error code returned from the server.",
+ "The title ID of the current game doesn't have a boxcat implementation. If you believe an "
+ "implementation should be added, contact yuzu support.",
+ "The build ID of the current version of the game is marked as incompatible with the current "
+ "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.",
+ "The content type of the web response was invalid.",
+ "There was a general filesystem error while saving the zip file.",
+ "The server is either too new or too old to serve the request. Try using the latest version of "
+ "an official release of yuzu.",
+};
+
+std::ostream& operator<<(std::ostream& os, DownloadResult result) {
+ return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result));
+}
+
+constexpr u32 PORT = 443;
+constexpr u32 TIMEOUT_SECONDS = 30;
+constexpr u64 VFS_COPY_BLOCK_SIZE = 1ull << 24; // 4MB
+
+namespace {
+
+std::string GetBINFilePath(u64 title_id) {
+ return fmt::format("{}bcat/{:016X}/launchparam.bin",
+ FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+}
+
+std::string GetZIPFilePath(u64 title_id) {
+ return fmt::format("{}bcat/{:016X}/data.zip",
+ FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+}
+
+// If the error is something the user should know about (build ID mismatch, bad client version),
+// display an error.
+void HandleDownloadDisplayResult(DownloadResult res) {
+ if (res == DownloadResult::Success || res == DownloadResult::NoResponse ||
+ res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError ||
+ res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) {
+ return;
+ }
+
+ const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()};
+ frontend.error->ShowCustomErrorText(
+ ResultCode(-1), "There was an error while attempting to use Boxcat.",
+ DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
+}
+
+bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
+ std::string_view dir_name, ProgressServiceBackend& progress,
+ std::size_t block_size = 0x1000) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+ if (!dest->Resize(src->GetSize()))
+ return false;
+
+ progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
+
+ std::vector<u8> temp(std::min(block_size, src->GetSize()));
+ for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
+ const auto read = std::min(block_size, src->GetSize() - i);
+
+ if (src->Read(temp.data(), read, i) != read) {
+ return false;
+ }
+
+ if (dest->Write(temp.data(), read, i) != read) {
+ return false;
+ }
+
+ progress.UpdateFileProgress(i);
+ }
+
+ progress.FinishDownloadingFile();
+
+ return true;
+}
+
+bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
+ ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+
+ for (const auto& file : src->GetFiles()) {
+ const auto out_file = VfsCreateFileWrap(dest, file->GetName());
+ if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
+ return false;
+ }
+ }
+ progress.CommitDirectory(src->GetName());
+
+ return true;
+}
+
+bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
+ ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+
+ for (const auto& dir : src->GetSubdirectories()) {
+ const auto out = dest->CreateSubdirectory(dir->GetName());
+ if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // Anonymous namespace
+
+class Boxcat::Client {
+public:
+ Client(std::string path, u64 title_id, u64 build_id)
+ : path(std::move(path)), title_id(title_id), build_id(build_id) {}
+
+ DownloadResult DownloadDataZip() {
+ return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
+ "application/zip");
+ }
+
+ DownloadResult DownloadLaunchParam() {
+ return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
+ TIMEOUT_SECONDS / 3, "application/octet-stream");
+ }
+
+private:
+ DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
+ const std::string& content_type_name) {
+ if (client == nullptr) {
+ client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
+ }
+
+ httplib::Headers headers{
+ {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
+ {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
+ {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
+ };
+
+ if (FileUtil::Exists(path)) {
+ FileUtil::IOFile file{path, "rb"};
+ if (file.IsOpen()) {
+ std::vector<u8> bytes(file.GetSize());
+ file.ReadBytes(bytes.data(), bytes.size());
+ const auto digest = DigestFile(bytes);
+ headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
+ }
+ }
+
+ const auto response = client->Get(resolved_path.c_str(), headers);
+ if (response == nullptr)
+ return DownloadResult::NoResponse;
+
+ if (response->status == static_cast<int>(ResponseStatus::NoUpdate))
+ return DownloadResult::Success;
+ if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
+ return DownloadResult::BadClientVersion;
+ if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId))
+ return DownloadResult::NoMatchTitleId;
+ if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
+ return DownloadResult::NoMatchBuildId;
+ if (response->status != static_cast<int>(ResponseStatus::Ok))
+ return DownloadResult::GeneralWebError;
+
+ const auto content_type = response->headers.find("content-type");
+ if (content_type == response->headers.end() ||
+ content_type->second.find(content_type_name) == std::string::npos) {
+ return DownloadResult::InvalidContentType;
+ }
+
+ FileUtil::CreateFullPath(path);
+ FileUtil::IOFile file{path, "wb"};
+ if (!file.IsOpen())
+ return DownloadResult::GeneralFSError;
+ if (!file.Resize(response->body.size()))
+ return DownloadResult::GeneralFSError;
+ if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size())
+ return DownloadResult::GeneralFSError;
+
+ return DownloadResult::Success;
+ }
+
+ using Digest = std::array<u8, 0x20>;
+ static Digest DigestFile(std::vector<u8> bytes) {
+ Digest out{};
+ mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0);
+ return out;
+ }
+
+ std::unique_ptr<httplib::Client> client;
+ std::string path;
+ u64 title_id;
+ u64 build_id;
+};
+
+Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
+
+Boxcat::~Boxcat() = default;
+
+void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
+ ProgressServiceBackend& progress,
+ std::optional<std::string> dir_name = {}) {
+ progress.SetNeedHLELock(true);
+
+ if (Settings::values.bcat_boxcat_local) {
+ LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
+ const auto dir = dir_getter(title.title_id);
+ if (dir)
+ progress.SetTotalSize(dir->GetSize());
+ progress.FinishDownload(RESULT_SUCCESS);
+ return;
+ }
+
+ const auto zip_path{GetZIPFilePath(title.title_id)};
+ Boxcat::Client client{zip_path, title.title_id, title.build_id};
+
+ progress.StartConnecting();
+
+ const auto res = client.DownloadDataZip();
+ if (res != DownloadResult::Success) {
+ LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
+
+ if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
+ FileUtil::Delete(zip_path);
+ }
+
+ HandleDownloadDisplayResult(res);
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ progress.StartProcessingDataList();
+
+ FileUtil::IOFile zip{zip_path, "rb"};
+ const auto size = zip.GetSize();
+ std::vector<u8> bytes(size);
+ if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
+ if (extracted == nullptr) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ if (dir_name == std::nullopt) {
+ progress.SetTotalSize(extracted->GetSize());
+
+ const auto target_dir = dir_getter(title.title_id);
+ if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+ } else {
+ const auto target_dir = dir_getter(title.title_id);
+ if (target_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ const auto target_sub = target_dir->GetSubdirectory(*dir_name);
+ const auto source_sub = extracted->GetSubdirectory(*dir_name);
+
+ progress.SetTotalSize(source_sub->GetSize());
+
+ std::vector<std::string> filenames;
+ {
+ const auto files = target_sub->GetFiles();
+ std::transform(files.begin(), files.end(), std::back_inserter(filenames),
+ [](const auto& vfile) { return vfile->GetName(); });
+ }
+
+ for (const auto& filename : filenames) {
+ VfsDeleteFileWrap(target_sub, filename);
+ }
+
+ if (target_sub == nullptr || source_sub == nullptr ||
+ !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+ }
+
+ progress.FinishDownload(RESULT_SUCCESS);
+}
+
+bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
+ is_syncing.exchange(true);
+ std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
+ .detach();
+ return true;
+}
+
+bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) {
+ is_syncing.exchange(true);
+ std::thread(
+ [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
+ .detach();
+ return true;
+}
+
+bool Boxcat::Clear(u64 title_id) {
+ if (Settings::values.bcat_boxcat_local) {
+ LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear.");
+ return true;
+ }
+
+ const auto dir = dir_getter(title_id);
+
+ std::vector<std::string> dirnames;
+
+ for (const auto& subdir : dir->GetSubdirectories())
+ dirnames.push_back(subdir->GetName());
+
+ for (const auto& subdir : dirnames) {
+ if (!dir->DeleteSubdirectoryRecursive(subdir))
+ return false;
+ }
+
+ return true;
+}
+
+void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
+ Common::HexToString(passphrase));
+}
+
+std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
+ const auto path{GetBINFilePath(title.title_id)};
+
+ if (Settings::values.bcat_boxcat_local) {
+ LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
+ } else {
+ Boxcat::Client client{path, title.title_id, title.build_id};
+
+ const auto res = client.DownloadLaunchParam();
+ if (res != DownloadResult::Success) {
+ LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
+
+ if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
+ FileUtil::Delete(path);
+ }
+
+ HandleDownloadDisplayResult(res);
+ return std::nullopt;
+ }
+ }
+
+ FileUtil::IOFile bin{path, "rb"};
+ const auto size = bin.GetSize();
+ std::vector<u8> bytes(size);
+ if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
+ path);
+ return std::nullopt;
+ }
+
+ return bytes;
+}
+
+Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
+ std::map<std::string, EventStatus>& games) {
+ httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT),
+ static_cast<int>(TIMEOUT_SECONDS)};
+
+ httplib::Headers headers{
+ {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
+ {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
+ };
+
+ const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
+ if (response == nullptr)
+ return StatusResult::Offline;
+
+ if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
+ return StatusResult::BadClientVersion;
+
+ try {
+ nlohmann::json json = nlohmann::json::parse(response->body);
+
+ if (!json["online"].get<bool>())
+ return StatusResult::Offline;
+
+ if (json["global"].is_null())
+ global = std::nullopt;
+ else
+ global = json["global"].get<std::string>();
+
+ if (json["games"].is_array()) {
+ for (const auto object : json["games"]) {
+ if (object.is_object() && object.find("name") != object.end()) {
+ EventStatus detail{};
+ if (object["header"].is_string()) {
+ detail.header = object["header"].get<std::string>();
+ } else {
+ detail.header = std::nullopt;
+ }
+
+ if (object["footer"].is_string()) {
+ detail.footer = object["footer"].get<std::string>();
+ } else {
+ detail.footer = std::nullopt;
+ }
+
+ if (object["events"].is_array()) {
+ for (const auto& event : object["events"]) {
+ if (!event.is_string())
+ continue;
+ detail.events.push_back(event.get<std::string>());
+ }
+ }
+
+ games.insert_or_assign(object["name"], std::move(detail));
+ }
+ }
+ }
+
+ return StatusResult::Success;
+ } catch (const nlohmann::json::parse_error& e) {
+ return StatusResult::ParseError;
+ }
+}
+
+} // namespace Service::BCAT
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..c2f946424 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -6,11 +6,15 @@
namespace Service::BCAT {
-BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
- : Module::Interface(std::move(module), name) {
+BCAT::BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, const char* name)
+ : Module::Interface(std::move(module), fsc, name) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &BCAT::CreateBcatService, "CreateBcatService"},
+ {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
+ {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
};
+ // clang-format on
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index 802bd689a..813073658 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.h
@@ -10,7 +10,8 @@ namespace Service::BCAT {
class BCAT final : public Module::Interface {
public:
- explicit BCAT(std::shared_ptr<Module> module, const char* name);
+ explicit BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
+ const char* name);
~BCAT() override;
};
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index b7bd738fc..b3fed56c7 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -2,34 +2,254 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cctype>
+#include <mbedtls/md5.h>
+#include "backend/boxcat.h"
+#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/bcat/bcat.h"
#include "core/hle/service/bcat/module.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/settings.h"
namespace Service::BCAT {
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
+constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
+constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
+constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
+
+// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
+// and if any of them have a non-zero result it just forwards that result. This is the FS error code
+// for permission denied, which is the closest approximation of this scenario.
+constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
+
+using BCATDigest = std::array<u8, 0x10>;
+
+namespace {
+
+u64 GetCurrentBuildID() {
+ const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID();
+ u64 out{};
+ std::memcpy(&out, id.data(), sizeof(u64));
+ return out;
+}
+
+// The digest is only used to determine if a file is unique compared to others of the same name.
+// Since the algorithm isn't ever checked in game, MD5 is safe.
+BCATDigest DigestFile(const FileSys::VirtualFile& file) {
+ BCATDigest out{};
+ const auto bytes = file->ReadAllBytes();
+ mbedtls_md5(bytes.data(), bytes.size(), out.data());
+ return out;
+}
+
+// For a name to be valid it must be non-empty, must have a null terminating character as the final
+// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
+// file.
+bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
+ char match_char) {
+ const auto null_chars = std::count(name.begin(), name.end(), 0);
+ const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
+ return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
+ });
+ if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
+ LOG_ERROR(Service_BCAT, "Name passed was invalid!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return false;
+ }
+
+ return true;
+}
+
+bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
+ return VerifyNameValidInternal(ctx, name, '-');
+}
+
+bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
+ return VerifyNameValidInternal(ctx, name, '.');
+}
+
+} // Anonymous namespace
+
+struct DeliveryCacheDirectoryEntry {
+ FileName name;
+ u64 size;
+ BCATDigest digest;
+};
+
+class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
+public:
+ IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event,
+ const DeliveryCacheProgressImpl& impl)
+ : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
+ {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(event);
+ }
+
+ void GetImpl(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Kernel::SharedPtr<Kernel::ReadableEvent> event;
+ const DeliveryCacheProgressImpl& impl;
+};
+
class IBcatService final : public ServiceFramework<IBcatService> {
public:
- IBcatService() : ServiceFramework("IBcatService") {
+ IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) {
+ // clang-format off
static const FunctionInfo functions[] = {
- {10100, nullptr, "RequestSyncDeliveryCache"},
- {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"},
+ {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
+ {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
{10200, nullptr, "CancelSyncDeliveryCacheRequest"},
{20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
{20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
- {30100, nullptr, "SetPassphrase"},
+ {30100, &IBcatService::SetPassphrase, "SetPassphrase"},
{30200, nullptr, "RegisterBackgroundDeliveryTask"},
{30201, nullptr, "UnregisterBackgroundDeliveryTask"},
{30202, nullptr, "BlockDeliveryTask"},
{30203, nullptr, "UnblockDeliveryTask"},
{90100, nullptr, "EnumerateBackgroundDeliveryTask"},
{90200, nullptr, "GetDeliveryList"},
- {90201, nullptr, "ClearDeliveryCacheStorage"},
+ {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
{90300, nullptr, "GetPushNotificationLog"},
};
+ // clang-format on
RegisterHandlers(functions);
}
+
+private:
+ enum class SyncType {
+ Normal,
+ Directory,
+ Count,
+ };
+
+ std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
+ auto& backend{progress.at(static_cast<std::size_t>(type))};
+ return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
+ backend.GetImpl());
+ }
+
+ void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
+ progress.at(static_cast<std::size_t>(SyncType::Normal)));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
+ }
+
+ void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto name_raw = rp.PopRaw<DirectoryName>();
+ const auto name =
+ Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, name={}", name);
+
+ backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
+ name,
+ progress.at(static_cast<std::size_t>(SyncType::Directory)));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
+ }
+
+ void SetPassphrase(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ const auto passphrase_raw = ctx.ReadBuffer();
+
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
+ Common::HexToString(passphrase_raw));
+
+ if (title_id == 0) {
+ LOG_ERROR(Service_BCAT, "Invalid title ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ }
+
+ if (passphrase_raw.size() > 0x40) {
+ LOG_ERROR(Service_BCAT, "Passphrase too large!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ Passphrase passphrase{};
+ std::memcpy(passphrase.data(), passphrase_raw.data(),
+ std::min(passphrase.size(), passphrase_raw.size()));
+
+ backend.SetPassphrase(title_id, passphrase);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
+
+ if (title_id == 0) {
+ LOG_ERROR(Service_BCAT, "Invalid title ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ if (!backend.Clear(title_id)) {
+ LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_CLEAR_CACHE);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Backend& backend;
+
+ std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
+ ProgressServiceBackend{"Normal"},
+ ProgressServiceBackend{"Directory"},
+ };
};
void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
@@ -37,20 +257,331 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IBcatService>();
+ rb.PushIpcInterface<IBcatService>(*backend);
+}
+
+class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
+public:
+ IDeliveryCacheFileService(FileSys::VirtualDir root_)
+ : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheFileService::Open, "Open"},
+ {1, &IDeliveryCacheFileService::Read, "Read"},
+ {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
+ {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Open(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto dir_name_raw = rp.PopRaw<DirectoryName>();
+ const auto file_name_raw = rp.PopRaw<FileName>();
+
+ const auto dir_name =
+ Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
+ const auto file_name =
+ Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
+
+ if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
+ return;
+
+ if (current_file != nullptr) {
+ LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_ENTITY_ALREADY_OPEN);
+ return;
+ }
+
+ const auto dir = root->GetSubdirectory(dir_name);
+
+ if (dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_OPEN_ENTITY);
+ return;
+ }
+
+ current_file = dir->GetFile(file_name);
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_OPEN_ENTITY);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto offset{rp.PopRaw<u64>()};
+
+ auto size = ctx.GetWriteBufferSize();
+
+ LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no file currently open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ }
+
+ size = std::min<u64>(current_file->GetSize() - offset, size);
+ const auto buffer = current_file->ReadBytes(size, offset);
+ ctx.WriteBuffer(buffer);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(buffer.size());
+ }
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no file currently open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(current_file->GetSize());
+ }
+
+ void GetDigest(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no file currently open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(DigestFile(current_file));
+ }
+
+ FileSys::VirtualDir root;
+ FileSys::VirtualFile current_file;
+};
+
+class IDeliveryCacheDirectoryService final
+ : public ServiceFramework<IDeliveryCacheDirectoryService> {
+public:
+ IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
+ : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheDirectoryService::Open, "Open"},
+ {1, &IDeliveryCacheDirectoryService::Read, "Read"},
+ {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Open(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto name_raw = rp.PopRaw<DirectoryName>();
+ const auto name =
+ Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, name={}", name);
+
+ if (!VerifyNameValidDir(ctx, name_raw))
+ return;
+
+ if (current_dir != nullptr) {
+ LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_ENTITY_ALREADY_OPEN);
+ return;
+ }
+
+ current_dir = root->GetSubdirectory(name);
+
+ if (current_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_OPEN_ENTITY);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
+
+ LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
+
+ if (current_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no open directory!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ return;
+ }
+
+ const auto files = current_dir->GetFiles();
+ write_size = std::min<u64>(write_size, files.size());
+ std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
+ std::transform(
+ files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
+ FileName name{};
+ std::memcpy(name.data(), file->GetName().data(),
+ std::min(file->GetName().size(), name.size()));
+ return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
+ });
+
+ ctx.WriteBuffer(entries);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry));
+ }
+
+ void GetCount(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ if (current_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no open directory!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ return;
+ }
+
+ const auto files = current_dir->GetFiles();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(files.size());
+ }
+
+ FileSys::VirtualDir root;
+ FileSys::VirtualDir current_dir;
+};
+
+class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
+public:
+ IDeliveryCacheStorageService(FileSys::VirtualDir root_)
+ : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
+ {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
+ {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ for (const auto& subdir : root->GetSubdirectories()) {
+ DirectoryName name{};
+ std::memcpy(name.data(), subdir->GetName().data(),
+ std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
+ entries.push_back(name);
+ }
+ }
+
+private:
+ void CreateFileService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheFileService>(root);
+ }
+
+ void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
+ }
+
+ void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
+ auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
+
+ LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
+
+ size = std::min<u64>(size, entries.size() - next_read_index);
+ ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
+ next_read_index += size;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(size);
+ }
+
+ FileSys::VirtualDir root;
+ std::vector<DirectoryName> entries;
+ u64 next_read_index = 0;
+};
+
+void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheStorageService>(
+ fsc.GetBCATDirectory(Core::CurrentProcess()->GetTitleID()));
+}
+
+void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
+ Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
+}
+
+std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
+ const auto backend = Settings::values.bcat_backend;
+
+#ifdef YUZU_ENABLE_BOXCAT
+ if (backend == "boxcat")
+ return std::make_unique<Boxcat>(std::move(getter));
+#endif
+
+ return std::make_unique<NullBackend>(std::move(getter));
}
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {}
+Module::Interface::Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
+ const char* name)
+ : ServiceFramework(name), module(std::move(module)), fsc(fsc),
+ backend(CreateBackendFromSettings([&fsc](u64 tid) { return fsc.GetBCATDirectory(tid); })) {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager);
- std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager);
- std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager);
- std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager);
+ std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:a")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:m")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:u")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:s")
+ ->InstallAsService(system.ServiceManager());
}
} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index f0d63cab0..27469926a 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -6,23 +6,39 @@
#include "core/hle/service/service.h"
-namespace Service::BCAT {
+namespace Service {
+
+namespace FileSystem {
+class FileSystemController;
+} // namespace FileSystem
+
+namespace BCAT {
+
+class Backend;
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
+ const char* name);
~Interface() override;
void CreateBcatService(Kernel::HLERequestContext& ctx);
+ void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
+ void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
protected:
+ FileSystem::FileSystemController& fsc;
+
std::shared_ptr<Module> module;
+ std::unique_ptr<Backend> backend;
};
};
/// Registers all BCAT services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
+
+} // namespace BCAT
-} // namespace Service::BCAT
+} // namespace Service
diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp
index 3c7ca2c44..afce581e5 100644
--- a/src/core/hle/service/btdrv/btdrv.cpp
+++ b/src/core/hle/service/btdrv/btdrv.cpp
@@ -16,7 +16,7 @@ namespace Service::BtDrv {
class Bt final : public ServiceFramework<Bt> {
public:
- explicit Bt() : ServiceFramework{"bt"} {
+ explicit Bt(Core::System& system) : ServiceFramework{"bt"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LeClientReadCharacteristic"},
@@ -33,7 +33,7 @@ public:
// clang-format on
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
register_event = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Automatic, "BT:RegisterEvent");
}
@@ -163,9 +163,9 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<BtDrv>()->InstallAsService(sm);
- std::make_shared<Bt>()->InstallAsService(sm);
+ std::make_shared<Bt>(system)->InstallAsService(sm);
}
} // namespace Service::BtDrv
diff --git a/src/core/hle/service/btdrv/btdrv.h b/src/core/hle/service/btdrv/btdrv.h
index 164e56f43..191410dbc 100644
--- a/src/core/hle/service/btdrv/btdrv.h
+++ b/src/core/hle/service/btdrv/btdrv.h
@@ -8,9 +8,13 @@ namespace Service::SM {
class ServiceManager;
}
+namespace Core {
+class System;
+}
+
namespace Service::BtDrv {
/// Registers all BtDrv services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::BtDrv
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index b439ee7ec..920fc6ff7 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -17,7 +17,7 @@ namespace Service::BTM {
class IBtmUserCore final : public ServiceFramework<IBtmUserCore> {
public:
- explicit IBtmUserCore() : ServiceFramework{"IBtmUserCore"} {
+ explicit IBtmUserCore(Core::System& system) : ServiceFramework{"IBtmUserCore"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"},
@@ -56,7 +56,7 @@ public:
// clang-format on
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IBtmUserCore:ScanEvent");
connection_event = Kernel::WritableEvent::CreateEventPair(
@@ -108,7 +108,7 @@ private:
class BTM_USR final : public ServiceFramework<BTM_USR> {
public:
- explicit BTM_USR() : ServiceFramework{"btm:u"} {
+ explicit BTM_USR(Core::System& system) : ServiceFramework{"btm:u"}, system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BTM_USR::GetCore, "GetCore"},
@@ -123,8 +123,10 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IBtmUserCore>();
+ rb.PushIpcInterface<IBtmUserCore>(system);
}
+
+ Core::System& system;
};
class BTM final : public ServiceFramework<BTM> {
@@ -268,11 +270,11 @@ private:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<BTM>()->InstallAsService(sm);
std::make_shared<BTM_DBG>()->InstallAsService(sm);
std::make_shared<BTM_SYS>()->InstallAsService(sm);
- std::make_shared<BTM_USR>()->InstallAsService(sm);
+ std::make_shared<BTM_USR>(system)->InstallAsService(sm);
}
} // namespace Service::BTM
diff --git a/src/core/hle/service/btm/btm.h b/src/core/hle/service/btm/btm.h
index e6425a7e3..c6b878043 100644
--- a/src/core/hle/service/btm/btm.h
+++ b/src/core/hle/service/btm/btm.h
@@ -8,8 +8,12 @@ namespace Service::SM {
class ServiceManager;
}
+namespace Core {
+class System;
+};
+
namespace Service::BTM {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::BTM
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index 01fa06ad3..b2ebf6240 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -20,8 +20,8 @@
namespace Service::Fatal {
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {}
+Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
+ : ServiceFramework(name), module(std::move(module)), system(system) {}
Module::Interface::~Interface() = default;
@@ -64,7 +64,8 @@ enum class FatalType : u32 {
ErrorScreen = 2,
};
-static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
+static void GenerateErrorReport(Core::System& system, ResultCode error_code,
+ const FatalInfo& info) {
const auto title_id = Core::CurrentProcess()->GetTitleID();
std::string crash_report = fmt::format(
"Yuzu {}-{} crash report\n"
@@ -101,18 +102,19 @@ static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
LOG_ERROR(Service_Fatal, "{}", crash_report);
- Core::System::GetInstance().GetReporter().SaveCrashReport(
+ system.GetReporter().SaveCrashReport(
title_id, error_code, info.set_flags, info.program_entry_point, info.sp, info.pc,
info.pstate, info.afsr0, info.afsr1, info.esr, info.far, info.registers, info.backtrace,
info.backtrace_size, info.ArchAsString(), info.unk10);
}
-static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {
+static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalType fatal_type,
+ const FatalInfo& info) {
LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}",
static_cast<u32>(fatal_type), error_code.raw);
switch (fatal_type) {
case FatalType::ErrorReportAndScreen:
- GenerateErrorReport(error_code, info);
+ GenerateErrorReport(system, error_code, info);
[[fallthrough]];
case FatalType::ErrorScreen:
// Since we have no fatal:u error screen. We should just kill execution instead
@@ -120,7 +122,7 @@ static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const F
break;
// Should not throw a fatal screen but should generate an error report
case FatalType::ErrorReport:
- GenerateErrorReport(error_code, info);
+ GenerateErrorReport(system, error_code, info);
break;
}
}
@@ -130,7 +132,7 @@ void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto error_code = rp.Pop<ResultCode>();
- ThrowFatalError(error_code, FatalType::ErrorScreen, {});
+ ThrowFatalError(system, error_code, FatalType::ErrorScreen, {});
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -141,7 +143,8 @@ void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
const auto error_code = rp.Pop<ResultCode>();
const auto fatal_type = rp.PopEnum<FatalType>();
- ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy
+ ThrowFatalError(system, error_code, fatal_type,
+ {}); // No info is passed with ThrowFatalWithPolicy
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -157,15 +160,15 @@ void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx)
ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!");
std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo));
- ThrowFatalError(error_code, fatal_type, info);
+ ThrowFatalError(system, error_code, fatal_type, info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<Fatal_P>(module)->InstallAsService(service_manager);
- std::make_shared<Fatal_U>(module)->InstallAsService(service_manager);
+ std::make_shared<Fatal_P>(module, system)->InstallAsService(service_manager);
+ std::make_shared<Fatal_U>(module, system)->InstallAsService(service_manager);
}
} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
index 09371ff7f..bd9339dfc 100644
--- a/src/core/hle/service/fatal/fatal.h
+++ b/src/core/hle/service/fatal/fatal.h
@@ -6,13 +6,17 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Fatal {
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
~Interface() override;
void ThrowFatal(Kernel::HLERequestContext& ctx);
@@ -21,9 +25,10 @@ public:
protected:
std::shared_ptr<Module> module;
+ Core::System& system;
};
};
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp
index 9e5f872ff..066ccf6b0 100644
--- a/src/core/hle/service/fatal/fatal_p.cpp
+++ b/src/core/hle/service/fatal/fatal_p.cpp
@@ -6,8 +6,8 @@
namespace Service::Fatal {
-Fatal_P::Fatal_P(std::shared_ptr<Module> module)
- : Module::Interface(std::move(module), "fatal:p") {}
+Fatal_P::Fatal_P(std::shared_ptr<Module> module, Core::System& system)
+ : Module::Interface(std::move(module), system, "fatal:p") {}
Fatal_P::~Fatal_P() = default;
diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h
index 6e9c5979f..c6d953cb5 100644
--- a/src/core/hle/service/fatal/fatal_p.h
+++ b/src/core/hle/service/fatal/fatal_p.h
@@ -10,7 +10,7 @@ namespace Service::Fatal {
class Fatal_P final : public Module::Interface {
public:
- explicit Fatal_P(std::shared_ptr<Module> module);
+ explicit Fatal_P(std::shared_ptr<Module> module, Core::System& system);
~Fatal_P() override;
};
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
index 1572a2051..8d72ed485 100644
--- a/src/core/hle/service/fatal/fatal_u.cpp
+++ b/src/core/hle/service/fatal/fatal_u.cpp
@@ -6,7 +6,8 @@
namespace Service::Fatal {
-Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
+Fatal_U::Fatal_U(std::shared_ptr<Module> module, Core::System& system)
+ : Module::Interface(std::move(module), system, "fatal:u") {
static const FunctionInfo functions[] = {
{0, &Fatal_U::ThrowFatal, "ThrowFatal"},
{1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h
index 72cb6d076..34c5c7f95 100644
--- a/src/core/hle/service/fatal/fatal_u.h
+++ b/src/core/hle/service/fatal/fatal_u.h
@@ -10,7 +10,7 @@ namespace Service::Fatal {
class Fatal_U final : public Module::Interface {
public:
- explicit Fatal_U(std::shared_ptr<Module> module);
+ explicit Fatal_U(std::shared_ptr<Module> module, Core::System& system);
~Fatal_U() override;
};
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 14cd0e322..7fa4e820b 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -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;
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 3e0c03ec0..e6b49d8a2 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -110,6 +110,8 @@ public:
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
+ FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
+
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index d1ec12ef9..42b4ee861 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -149,7 +149,8 @@ private:
class INotificationService final : public ServiceFramework<INotificationService> {
public:
- INotificationService(Common::UUID uuid) : ServiceFramework("INotificationService"), uuid(uuid) {
+ INotificationService(Common::UUID uuid, Core::System& system)
+ : ServiceFramework("INotificationService"), uuid(uuid) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &INotificationService::GetEvent, "GetEvent"},
@@ -159,6 +160,9 @@ public:
// clang-format on
RegisterHandlers(functions);
+
+ notification_event = Kernel::WritableEvent::CreateEventPair(
+ system.Kernel(), Kernel::ResetType::Manual, "INotificationService:NotifyEvent");
}
private:
@@ -167,13 +171,6 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
-
- if (!is_event_created) {
- auto& kernel = Core::System::GetInstance().Kernel();
- notification_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::Manual, "INotificationService:NotifyEvent");
- is_event_created = true;
- }
rb.PushCopyObjects(notification_event.readable);
}
@@ -261,21 +258,21 @@ void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<INotificationService>(uuid);
+ rb.PushIpcInterface<INotificationService>(uuid, system);
}
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {}
+Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
+ : ServiceFramework(name), module(std::move(module)), system(system) {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<Friend>(module, "friend:a")->InstallAsService(service_manager);
- std::make_shared<Friend>(module, "friend:m")->InstallAsService(service_manager);
- std::make_shared<Friend>(module, "friend:s")->InstallAsService(service_manager);
- std::make_shared<Friend>(module, "friend:u")->InstallAsService(service_manager);
- std::make_shared<Friend>(module, "friend:v")->InstallAsService(service_manager);
+ std::make_shared<Friend>(module, system, "friend:a")->InstallAsService(service_manager);
+ std::make_shared<Friend>(module, system, "friend:m")->InstallAsService(service_manager);
+ std::make_shared<Friend>(module, system, "friend:s")->InstallAsService(service_manager);
+ std::make_shared<Friend>(module, system, "friend:u")->InstallAsService(service_manager);
+ std::make_shared<Friend>(module, system, "friend:v")->InstallAsService(service_manager);
}
} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h
index 38d05fa8e..24f3fc969 100644
--- a/src/core/hle/service/friend/friend.h
+++ b/src/core/hle/service/friend/friend.h
@@ -6,13 +6,17 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Friend {
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
~Interface() override;
void CreateFriendService(Kernel::HLERequestContext& ctx);
@@ -20,10 +24,11 @@ public:
protected:
std::shared_ptr<Module> module;
+ Core::System& system;
};
};
/// Registers all Friend services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp
index 5b384f733..58155f652 100644
--- a/src/core/hle/service/friend/interface.cpp
+++ b/src/core/hle/service/friend/interface.cpp
@@ -6,8 +6,8 @@
namespace Service::Friend {
-Friend::Friend(std::shared_ptr<Module> module, const char* name)
- : Interface(std::move(module), name) {
+Friend::Friend(std::shared_ptr<Module> module, Core::System& system, const char* name)
+ : Interface(std::move(module), system, name) {
static const FunctionInfo functions[] = {
{0, &Friend::CreateFriendService, "CreateFriendService"},
{1, &Friend::CreateNotificationService, "CreateNotificationService"},
diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h
index 1963def39..465a35770 100644
--- a/src/core/hle/service/friend/interface.h
+++ b/src/core/hle/service/friend/interface.h
@@ -10,7 +10,7 @@ namespace Service::Friend {
class Friend final : public Module::Interface {
public:
- explicit Friend(std::shared_ptr<Module> module, const char* name);
+ explicit Friend(std::shared_ptr<Module> module, Core::System& system, const char* name);
~Friend() override;
};
diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp
index 0993a7815..8091db9d7 100644
--- a/src/core/hle/service/hid/controllers/controller_base.cpp
+++ b/src/core/hle/service/hid/controllers/controller_base.cpp
@@ -6,7 +6,7 @@
namespace Service::HID {
-ControllerBase::ControllerBase() = default;
+ControllerBase::ControllerBase(Core::System& system) : system(system) {}
ControllerBase::~ControllerBase() = default;
void ControllerBase::ActivateController() {
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
index 5e5097a03..8bc69c372 100644
--- a/src/core/hle/service/hid/controllers/controller_base.h
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -11,10 +11,14 @@ namespace Core::Timing {
class CoreTiming;
}
+namespace Core {
+class System;
+}
+
namespace Service::HID {
class ControllerBase {
public:
- ControllerBase();
+ explicit ControllerBase(Core::System& system);
virtual ~ControllerBase();
// Called when the controller is initialized
@@ -46,5 +50,7 @@ protected:
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+
+ Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index c5c2e032a..8e8263f5b 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -14,7 +14,8 @@ constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
-Controller_DebugPad::Controller_DebugPad() = default;
+Controller_DebugPad::Controller_DebugPad(Core::System& system)
+ : ControllerBase(system), system(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 e584b92ec..6c4de817e 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -16,7 +16,7 @@
namespace Service::HID {
class Controller_DebugPad final : public ControllerBase {
public:
- Controller_DebugPad();
+ explicit Controller_DebugPad(Core::System& system);
~Controller_DebugPad() override;
// Called when the controller is initialized
@@ -89,5 +89,6 @@ 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 a179252e3..80da0a0d3 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -10,7 +10,8 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
-Controller_Gesture::Controller_Gesture() = default;
+Controller_Gesture::Controller_Gesture(Core::System& system)
+ : ControllerBase(system), system(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 f305fe90f..396897527 100644
--- a/src/core/hle/service/hid/controllers/gesture.h
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -12,7 +12,7 @@
namespace Service::HID {
class Controller_Gesture final : public ControllerBase {
public:
- Controller_Gesture();
+ explicit Controller_Gesture(Core::System& system);
~Controller_Gesture() override;
// Called when the controller is initialized
@@ -59,5 +59,6 @@ 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 92d7bfb52..e587b2e15 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -12,7 +12,8 @@ namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
constexpr u8 KEYS_PER_BYTE = 8;
-Controller_Keyboard::Controller_Keyboard() = default;
+Controller_Keyboard::Controller_Keyboard(Core::System& system)
+ : ControllerBase(system), system(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 73cd2c7bb..ef586f7eb 100644
--- a/src/core/hle/service/hid/controllers/keyboard.h
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -15,7 +15,7 @@
namespace Service::HID {
class Controller_Keyboard final : public ControllerBase {
public:
- Controller_Keyboard();
+ explicit Controller_Keyboard(Core::System& system);
~Controller_Keyboard() override;
// Called when the controller is initialized
@@ -53,5 +53,6 @@ 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 11ab096d9..88f2ca4c1 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() = default;
+Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system), system(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 9d46eecbe..df2da6ae3 100644
--- a/src/core/hle/service/hid/controllers/mouse.h
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -14,7 +14,7 @@
namespace Service::HID {
class Controller_Mouse final : public ControllerBase {
public:
- Controller_Mouse();
+ explicit Controller_Mouse(Core::System& system);
~Controller_Mouse() override;
// Called when the controller is initialized
@@ -53,5 +53,6 @@ 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 e47fe8188..a9cd119c4 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -93,7 +93,7 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
};
}
-Controller_NPad::Controller_NPad() = default;
+Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
Controller_NPad::~Controller_NPad() = default;
void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
@@ -165,12 +165,15 @@ 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 = Core::System::GetInstance().Kernel();
- styleset_changed_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::Automatic, "npad:NpadStyleSetChanged");
+ 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::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i));
+ }
if (!IsControllerActivated()) {
return;
@@ -431,7 +434,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) {
@@ -450,10 +452,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_event.writable->Signal();
}
}
}
@@ -468,7 +466,6 @@ std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
}
void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
- styleset_changed_event.writable->Signal();
hold_type = joy_hold_type;
}
@@ -479,7 +476,9 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
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());
- shared_memory_entries[npad_index].pad_assignment = assignment_mode;
+ if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
+ shared_memory_entries[npad_index].pad_assignment = assignment_mode;
+ }
}
void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
@@ -498,11 +497,12 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
last_processed_vibration = vibrations.back();
}
-Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent() const {
+Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(
+ u32 npad_id) const {
// 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?
- styleset_changed_event.writable->Signal();
- return styleset_changed_event.readable;
+ const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
+ return styleset_event.readable;
}
Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index f28b36806..1bc3d55d6 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -20,7 +20,7 @@ constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this?
class Controller_NPad final : public ControllerBase {
public:
- Controller_NPad();
+ explicit Controller_NPad(Core::System& system);
~Controller_NPad() override;
// Called when the controller is initialized
@@ -109,7 +109,7 @@ public:
void VibrateController(const std::vector<u32>& controller_ids,
const std::vector<Vibration>& vibrations);
- Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent() const;
+ Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
Vibration GetLastVibration() const;
void AddNewController(NPadControllerType controller);
@@ -315,7 +315,8 @@ private:
sticks;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
- Kernel::EventPair styleset_changed_event;
+ // Each controller should have their own styleset changed event
+ std::array<Kernel::EventPair, 10> styleset_changed_events;
Vibration last_processed_vibration{};
std::array<ControllerHolder, 10> connected_controllers{};
bool can_controllers_vibrate{true};
@@ -327,5 +328,6 @@ private:
std::array<ControllerPad, 10> npad_pad_states{};
bool IsControllerSupported(NPadControllerType controller);
bool is_in_lr_assignment_mode{false};
+ Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
index 946948f5e..9b829341e 100644
--- a/src/core/hle/service/hid/controllers/stubbed.cpp
+++ b/src/core/hle/service/hid/controllers/stubbed.cpp
@@ -9,7 +9,8 @@
namespace Service::HID {
-Controller_Stubbed::Controller_Stubbed() = default;
+Controller_Stubbed::Controller_Stubbed(Core::System& system)
+ : ControllerBase(system), system(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 24469f03e..37d7d8538 100644
--- a/src/core/hle/service/hid/controllers/stubbed.h
+++ b/src/core/hle/service/hid/controllers/stubbed.h
@@ -10,7 +10,7 @@
namespace Service::HID {
class Controller_Stubbed final : public ControllerBase {
public:
- Controller_Stubbed();
+ explicit Controller_Stubbed(Core::System& system);
~Controller_Stubbed() override;
// Called when the controller is initialized
@@ -30,5 +30,6 @@ 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 1a8445a43..25912fd69 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -13,7 +13,8 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
-Controller_Touchscreen::Controller_Touchscreen() = default;
+Controller_Touchscreen::Controller_Touchscreen(Core::System& system)
+ : ControllerBase(system), system(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 76fc340e9..3429c84db 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -14,7 +14,7 @@
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
- Controller_Touchscreen();
+ explicit Controller_Touchscreen(Core::System& system);
~Controller_Touchscreen() override;
// Called when the controller is initialized
@@ -69,5 +69,6 @@ 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 1a9da9576..1bce044b4 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() = default;
+Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system), system(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 2864e6617..c445ebec0 100644
--- a/src/core/hle/service/hid/controllers/xpad.h
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -12,7 +12,7 @@
namespace Service::HID {
class Controller_XPad final : public ControllerBase {
public:
- Controller_XPad();
+ explicit Controller_XPad(Core::System& system);
~Controller_XPad() override;
// Called when the controller is initialized
@@ -56,5 +56,6 @@ 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 f8b1ca816..8d76ba746 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -42,13 +42,14 @@ constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_C
constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
-IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") {
+IAppletResource::IAppletResource(Core::System& system)
+ : ServiceFramework("IAppletResource"), system(system) {
static const FunctionInfo functions[] = {
{0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
};
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
shared_mem = Kernel::SharedMemory::Create(
kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
@@ -74,7 +75,7 @@ IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") {
GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
// Register update callbacks
- auto& core_timing = Core::System::GetInstance().CoreTiming();
+ auto& core_timing = system.CoreTiming();
pad_update_event =
core_timing.RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 cycles_late) {
UpdateControllers(userdata, cycles_late);
@@ -96,7 +97,7 @@ void IAppletResource::DeactivateController(HidController controller) {
}
IAppletResource ::~IAppletResource() {
- Core::System::GetInstance().CoreTiming().UnscheduleEvent(pad_update_event, 0);
+ system.CoreTiming().UnscheduleEvent(pad_update_event, 0);
}
void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
@@ -108,7 +109,7 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
}
void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) {
- auto& core_timing = Core::System::GetInstance().CoreTiming();
+ auto& core_timing = system.CoreTiming();
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
for (const auto& controller : controllers) {
@@ -141,13 +142,13 @@ private:
std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
if (applet_resource == nullptr) {
- applet_resource = std::make_shared<IAppletResource>();
+ applet_resource = std::make_shared<IAppletResource>(system);
}
return applet_resource;
}
-Hid::Hid() : ServiceFramework("hid") {
+Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Hid::CreateAppletResource, "CreateAppletResource"},
@@ -286,7 +287,7 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
if (applet_resource == nullptr) {
- applet_resource = std::make_shared<IAppletResource>();
+ applet_resource = std::make_shared<IAppletResource>(system);
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -479,7 +480,7 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetStyleSetChangedEvent());
+ .GetStyleSetChangedEvent(npad_id));
}
void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
@@ -1053,14 +1054,14 @@ void ReloadInputDevices() {
Settings::values.is_device_reload_pending.store(true);
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<Hid>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<Hid>(system)->InstallAsService(service_manager);
std::make_shared<HidBus>()->InstallAsService(service_manager);
std::make_shared<HidDbg>()->InstallAsService(service_manager);
std::make_shared<HidSys>()->InstallAsService(service_manager);
std::make_shared<HidTmp>()->InstallAsService(service_manager);
- std::make_shared<IRS>()->InstallAsService(service_manager);
+ std::make_shared<IRS>(system)->InstallAsService(service_manager);
std::make_shared<IRS_SYS>()->InstallAsService(service_manager);
std::make_shared<XCD_SYS>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 2fd6d9fc7..35b663679 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -42,7 +42,7 @@ enum class HidController : std::size_t {
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
- IAppletResource();
+ explicit IAppletResource(Core::System& system);
~IAppletResource() override;
void ActivateController(HidController controller);
@@ -61,7 +61,7 @@ public:
private:
template <typename T>
void MakeController(HidController controller) {
- controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
+ controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(system);
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
@@ -70,6 +70,7 @@ private:
Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
Core::Timing::EventType* pad_update_event;
+ Core::System& system;
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
controllers{};
@@ -77,7 +78,7 @@ private:
class Hid final : public ServiceFramework<Hid> {
public:
- Hid();
+ explicit Hid(Core::System& system);
~Hid() override;
std::shared_ptr<IAppletResource> GetAppletResource();
@@ -126,12 +127,13 @@ private:
void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
std::shared_ptr<IAppletResource> applet_resource;
+ Core::System& system;
};
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
/// Registers all HID services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 2c4625c99..5e79e2c1a 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -11,7 +11,7 @@
namespace Service::HID {
-IRS::IRS() : ServiceFramework{"irs"} {
+IRS::IRS(Core::System& system) : ServiceFramework{"irs"}, system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{302, &IRS::ActivateIrsensor, "ActivateIrsensor"},
@@ -37,7 +37,7 @@ IRS::IRS() : ServiceFramework{"irs"} {
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
shared_mem = Kernel::SharedMemory::Create(
kernel, nullptr, 0x8000, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "IRS:SharedMemory");
@@ -98,7 +98,7 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 5};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(Core::System::GetInstance().CoreTiming().GetTicks());
+ rb.PushRaw<u64>(system.CoreTiming().GetTicks());
rb.PushRaw<u32>(0);
}
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 12de6bfb3..eb4e898dd 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -15,7 +15,7 @@ namespace Service::HID {
class IRS final : public ServiceFramework<IRS> {
public:
- explicit IRS();
+ explicit IRS(Core::System& system);
~IRS() override;
private:
@@ -39,6 +39,7 @@ private:
void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
const u32 device_handle{0xABCD};
+ Core::System& system;
};
class IRS_SYS final : public ServiceFramework<IRS_SYS> {
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 8ddad8682..3164ca26e 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -78,7 +78,7 @@ public:
class RelocatableObject final : public ServiceFramework<RelocatableObject> {
public:
- explicit RelocatableObject() : ServiceFramework{"ldr:ro"} {
+ explicit RelocatableObject(Core::System& system) : ServiceFramework{"ldr:ro"}, system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &RelocatableObject::LoadNro, "LoadNro"},
@@ -364,7 +364,7 @@ public:
vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
Kernel::VMAPermission::ReadWrite);
- Core::System::GetInstance().InvalidateCpuInstructionCaches();
+ system.InvalidateCpuInstructionCaches();
nro.insert_or_assign(*map_address,
NROInfo{hash, nro_address, nro_size, bss_address, bss_size});
@@ -430,7 +430,7 @@ public:
.IsSuccess());
}
- Core::System::GetInstance().InvalidateCpuInstructionCaches();
+ system.InvalidateCpuInstructionCaches();
nro.erase(iter);
IPC::ResponseBuilder rb{ctx, 2};
@@ -516,13 +516,14 @@ private:
Common::Is4KBAligned(header.text_size) && Common::Is4KBAligned(header.ro_size) &&
Common::Is4KBAligned(header.rw_size);
}
+ Core::System& system;
};
-void InstallInterfaces(SM::ServiceManager& sm) {
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<DebugMonitor>()->InstallAsService(sm);
std::make_shared<ProcessManager>()->InstallAsService(sm);
std::make_shared<Shell>()->InstallAsService(sm);
- std::make_shared<RelocatableObject>()->InstallAsService(sm);
+ std::make_shared<RelocatableObject>(system)->InstallAsService(sm);
}
} // namespace Service::LDR
diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h
index 412410c4f..7ac8c0b65 100644
--- a/src/core/hle/service/ldr/ldr.h
+++ b/src/core/hle/service/ldr/ldr.h
@@ -11,6 +11,6 @@ class ServiceManager;
namespace Service::LDR {
/// Registers all LDR services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::LDR
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index a5cb06f8a..a42c22d44 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -23,9 +23,9 @@ constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
} // namespace ErrCodes
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {
- auto& kernel = Core::System::GetInstance().Kernel();
+Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
+ : ServiceFramework(name), module(std::move(module)), system(system) {
+ auto& kernel = system.Kernel();
nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IUser:NFCTagDetected");
}
@@ -34,8 +34,8 @@ Module::Interface::~Interface() = default;
class IUser final : public ServiceFramework<IUser> {
public:
- IUser(Module::Interface& nfp_interface)
- : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
+ IUser(Module::Interface& nfp_interface, Core::System& system)
+ : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface), system(system) {
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "Initialize"},
{1, &IUser::Finalize, "Finalize"},
@@ -65,7 +65,7 @@ public:
};
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
deactivate_event = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Automatic, "IUser:DeactivateEvent");
availability_change_event = Kernel::WritableEvent::CreateEventPair(
@@ -324,6 +324,7 @@ 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) {
@@ -331,7 +332,7 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IUser>(*this);
+ rb.PushIpcInterface<IUser>(*this, system);
}
bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
@@ -353,9 +354,9 @@ const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const
return amiibo;
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<NFP_User>(module)->InstallAsService(service_manager);
+ std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager);
}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index a1817e991..9718ef745 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -16,7 +16,7 @@ class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
~Interface() override;
struct ModelInfo {
@@ -43,9 +43,10 @@ public:
protected:
std::shared_ptr<Module> module;
+ Core::System& system;
};
};
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 784a87c1b..298184f17 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -6,8 +6,8 @@
namespace Service::NFP {
-NFP_User::NFP_User(std::shared_ptr<Module> module)
- : Module::Interface(std::move(module), "nfp:user") {
+NFP_User::NFP_User(std::shared_ptr<Module> module, Core::System& system)
+ : Module::Interface(std::move(module), system, "nfp:user") {
static const FunctionInfo functions[] = {
{0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
};
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 65d9aaf48..1686ebf20 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -10,7 +10,7 @@ namespace Service::NFP {
class NFP_User final : public Module::Interface {
public:
- explicit NFP_User(std::shared_ptr<Module> module);
+ explicit NFP_User(std::shared_ptr<Module> module, Core::System& system);
~NFP_User() override;
};
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 76b12b482..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") {
@@ -31,7 +38,7 @@ public:
class IRequest final : public ServiceFramework<IRequest> {
public:
- explicit IRequest() : ServiceFramework("IRequest") {
+ explicit IRequest(Core::System& system) : ServiceFramework("IRequest") {
static const FunctionInfo functions[] = {
{0, &IRequest::GetRequestState, "GetRequestState"},
{1, &IRequest::GetResult, "GetResult"},
@@ -61,7 +68,7 @@ public:
};
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IRequest:Event1");
event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
@@ -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) {
@@ -130,7 +137,7 @@ public:
class IGeneralService final : public ServiceFramework<IGeneralService> {
public:
- IGeneralService();
+ IGeneralService(Core::System& system);
private:
void GetClientId(Kernel::HLERequestContext& ctx) {
@@ -155,7 +162,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IRequest>();
+ rb.PushIpcInterface<IRequest>(system);
}
void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
@@ -189,18 +196,20 @@ 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;
};
-IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
+IGeneralService::IGeneralService(Core::System& system)
+ : ServiceFramework("IGeneralService"), system(system) {
static const FunctionInfo functions[] = {
{1, &IGeneralService::GetClientId, "GetClientId"},
{2, &IGeneralService::CreateScanRequest, "CreateScanRequest"},
@@ -245,7 +254,8 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
class NetworkInterface final : public ServiceFramework<NetworkInterface> {
public:
- explicit NetworkInterface(const char* name) : ServiceFramework{name} {
+ explicit NetworkInterface(const char* name, Core::System& system)
+ : ServiceFramework{name}, system(system) {
static const FunctionInfo functions[] = {
{4, &NetworkInterface::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
{5, &NetworkInterface::CreateGeneralService, "CreateGeneralService"},
@@ -258,7 +268,7 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
+ rb.PushIpcInterface<IGeneralService>(system);
}
void CreateGeneralService(Kernel::HLERequestContext& ctx) {
@@ -266,14 +276,17 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
+ rb.PushIpcInterface<IGeneralService>(system);
}
+
+private:
+ Core::System& system;
};
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<NetworkInterface>("nifm:a")->InstallAsService(service_manager);
- std::make_shared<NetworkInterface>("nifm:s")->InstallAsService(service_manager);
- std::make_shared<NetworkInterface>("nifm:u")->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<NetworkInterface>("nifm:a", system)->InstallAsService(service_manager);
+ std::make_shared<NetworkInterface>("nifm:s", system)->InstallAsService(service_manager);
+ std::make_shared<NetworkInterface>("nifm:u", system)->InstallAsService(service_manager);
}
} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index 4616b3b48..6857e18f9 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -8,9 +8,13 @@ namespace Service::SM {
class ServiceManager;
}
+namespace Core {
+class System;
+}
+
namespace Service::NIFM {
/// Registers all NIFM services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::NIFM
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index f319a3ca1..75d414952 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -126,7 +126,7 @@ public:
class IEnsureNetworkClockAvailabilityService final
: public ServiceFramework<IEnsureNetworkClockAvailabilityService> {
public:
- IEnsureNetworkClockAvailabilityService()
+ explicit IEnsureNetworkClockAvailabilityService(Core::System& system)
: ServiceFramework("IEnsureNetworkClockAvailabilityService") {
static const FunctionInfo functions[] = {
{0, &IEnsureNetworkClockAvailabilityService::StartTask, "StartTask"},
@@ -139,7 +139,7 @@ public:
};
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
finished_event = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Automatic,
"IEnsureNetworkClockAvailabilityService:FinishEvent");
@@ -200,7 +200,7 @@ private:
class NTC final : public ServiceFramework<NTC> {
public:
- explicit NTC() : ServiceFramework{"ntc"} {
+ explicit NTC(Core::System& system) : ServiceFramework{"ntc"}, system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NTC::OpenEnsureNetworkClockAvailabilityService, "OpenEnsureNetworkClockAvailabilityService"},
@@ -218,7 +218,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>();
+ rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(system);
}
// TODO(ogniK): Do we need these?
@@ -235,13 +235,14 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+ Core::System& system;
};
-void InstallInterfaces(SM::ServiceManager& sm) {
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<NIM>()->InstallAsService(sm);
std::make_shared<NIM_ECA>()->InstallAsService(sm);
std::make_shared<NIM_SHP>()->InstallAsService(sm);
- std::make_shared<NTC>()->InstallAsService(sm);
+ std::make_shared<NTC>(system)->InstallAsService(sm);
}
} // namespace Service::NIM
diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h
index 2a2a92df0..dbe25dc01 100644
--- a/src/core/hle/service/nim/nim.h
+++ b/src/core/hle/service/nim/nim.h
@@ -8,8 +8,12 @@ namespace Service::SM {
class ServiceManager;
}
+namespace Core {
+class System;
+}
+
namespace Service::NIM {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::NIM
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 13121c4f1..15c156ce1 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -617,7 +617,8 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSystemController& fsc) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+
std::make_shared<NS>("ns:am2")->InstallAsService(service_manager);
std::make_shared<NS>("ns:ec")->InstallAsService(service_manager);
std::make_shared<NS>("ns:rid")->InstallAsService(service_manager);
@@ -628,7 +629,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSyst
std::make_shared<NS_SU>()->InstallAsService(service_manager);
std::make_shared<NS_VM>()->InstallAsService(service_manager);
- std::make_shared<PL_U>(fsc)->InstallAsService(service_manager);
+ std::make_shared<PL_U>(system)->InstallAsService(service_manager);
}
} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index d067e7a9a..13a64ad88 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -97,8 +97,7 @@ private:
};
/// Registers all NS services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSystemController& fsc);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace NS
-
} // namespace Service
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 9d49f36e8..7dcdb4a07 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -150,8 +150,9 @@ struct PL_U::Impl {
std::vector<FontRegion> shared_font_regions;
};
-PL_U::PL_U(FileSystem::FileSystemController& fsc)
- : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
+PL_U::PL_U(Core::System& system)
+ : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()}, system(system) {
+
static const FunctionInfo functions[] = {
{0, &PL_U::RequestLoad, "RequestLoad"},
{1, &PL_U::GetLoadState, "GetLoadState"},
@@ -161,6 +162,9 @@ PL_U::PL_U(FileSystem::FileSystemController& fsc)
{5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
};
RegisterHandlers(functions);
+
+ auto& fsc = system.GetFileSystemController();
+
// Attempt to load shared font data from disk
const auto* nand = fsc.GetSystemNANDContents();
std::size_t offset = 0;
@@ -325,7 +329,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
Kernel::MemoryState::Shared);
// Create shared font memory object
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
impl->shared_font_mem = Kernel::SharedMemory::Create(
kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
index 35ca424d2..1063f4204 100644
--- a/src/core/hle/service/ns/pl_u.h
+++ b/src/core/hle/service/ns/pl_u.h
@@ -17,7 +17,7 @@ namespace NS {
class PL_U final : public ServiceFramework<PL_U> {
public:
- PL_U(FileSystem::FileSystemController& fsc);
+ explicit PL_U(Core::System& system);
~PL_U() override;
private:
@@ -30,6 +30,7 @@ private:
struct Impl;
std::unique_ptr<Impl> impl;
+ Core::System& system;
};
} // namespace NS
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 5b8248433..1b52511a5 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/service.h"
namespace Core {
class System;
@@ -38,8 +39,9 @@ public:
* @param output A buffer where the output data will be written to.
* @returns The result code of the ioctl.
*/
- virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) = 0;
+ virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) = 0;
protected:
Core::System& system;
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 926a1285d..f764388bc 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -17,8 +17,9 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0 ::~nvdisp_disp0() = default;
-u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index e79e490ff..6fcdeee84 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -20,8 +20,9 @@ public:
explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvdisp_disp0() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
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 24ab3f2e9..6bc053f27 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -26,8 +26,9 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
-u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index 30ca5f4c3..169fb8f0e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -20,8 +20,9 @@ public:
explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_as_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index 9a66a5f88..ff6b1abae 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -19,8 +19,9 @@ nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface)
: nvdevice(system), events_interface{events_interface} {}
nvhost_ctrl::~nvhost_ctrl() = default;
-u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 14e6e7e57..9898623de 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -17,8 +17,9 @@ public:
explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface);
~nvhost_ctrl() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
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 988effd90..389ace76f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -15,14 +15,15 @@ namespace Service::Nvidia::Devices {
nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
-u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& input2, std::vector<u8>& output,
+ std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::IocGetCharacteristicsCommand:
- return GetCharacteristics(input, output);
+ return GetCharacteristics(input, output, output2, version);
case IoctlCommand::IocGetTPCMasksCommand:
return GetTPCMasks(input, output);
case IoctlCommand::IocGetActiveSlotMaskCommand:
@@ -44,7 +45,8 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec
return 0;
}
-u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& output2, IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlCharacteristics params{};
std::memcpy(&params, input.data(), input.size());
@@ -85,7 +87,13 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto
params.gc.gr_compbit_store_base_hw = 0x0;
params.gpu_characteristics_buf_size = 0xA0;
params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
- std::memcpy(output.data(), &params, output.size());
+
+ if (version == IoctlVersion::Version3) {
+ std::memcpy(output.data(), input.data(), output.size());
+ std::memcpy(output2.data(), &params.gc, output2.size());
+ } else {
+ std::memcpy(output.data(), &params, output.size());
+ }
return 0;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 2b035ae3f..642b0a2cb 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -16,8 +16,9 @@ public:
explicit nvhost_ctrl_gpu(Core::System& system);
~nvhost_ctrl_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
@@ -162,7 +163,8 @@ private:
};
static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size");
- u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& output2, IoctlVersion version);
u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
u32 ZCullGetCtxSize(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 b4ee2a255..2b8d1bef6 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -17,8 +17,9 @@ nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_gpu::~nvhost_gpu() = default;
-u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@@ -50,7 +51,7 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u
return SubmitGPFIFO(input, output);
}
if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) {
- return KickoffPB(input, output);
+ return KickoffPB(input, output, input2, version);
}
}
@@ -173,7 +174,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
return 0;
}
-u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
+ const std::vector<u8>& input2, IoctlVersion version) {
if (input.size() < sizeof(IoctlSubmitGpfifo)) {
UNIMPLEMENTED();
}
@@ -183,9 +185,13 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
params.num_entries, params.flags.raw);
Tegra::CommandList entries(params.num_entries);
- Memory::ReadBlock(params.address, entries.data(),
- params.num_entries * sizeof(Tegra::CommandListHeader));
-
+ if (version == IoctlVersion::Version2) {
+ std::memcpy(entries.data(), input2.data(),
+ params.num_entries * sizeof(Tegra::CommandListHeader));
+ } else {
+ Memory::ReadBlock(params.address, entries.data(),
+ params.num_entries * sizeof(Tegra::CommandListHeader));
+ }
UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index d2e8fbae9..d056dd046 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -24,8 +24,9 @@ public:
explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
@@ -183,7 +184,8 @@ private:
u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
- u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
+ const std::vector<u8>& input2, IoctlVersion version);
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index f572ad30f..bdae8b887 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices {
nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {}
nvhost_nvdec::~nvhost_nvdec() = default;
-u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index 2710f0511..cbdac8069 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -16,8 +16,9 @@ public:
explicit nvhost_nvdec(Core::System& system);
~nvhost_nvdec() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index 38282956f..96e7b7dab 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
@@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices {
nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
nvhost_nvjpg::~nvhost_nvjpg() = default;
-u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 379766693..98dcac52f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
@@ -16,8 +16,9 @@ public:
explicit nvhost_nvjpg(Core::System& system);
~nvhost_nvjpg() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index 70e8091db..c695b8863 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices {
nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {}
nvhost_vic::~nvhost_vic() = default;
-u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index 7d111977e..bec32bea1 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
@@ -16,8 +16,9 @@ public:
explicit nvhost_vic(Core::System& system);
~nvhost_vic() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 223b496b7..8c742316c 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -28,8 +28,9 @@ VAddr nvmap::GetObjectAddress(u32 handle) const {
return object->addr;
}
-u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::Create:
return IocCreate(input, output);
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index bf4a101c2..73c2e8809 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -22,8 +22,9 @@ public:
/// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) override;
/// Represents an nvmap object.
struct Object {
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index d5be64ed2..5e0c23602 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -33,42 +33,77 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0);
}
-void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
-
+void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) {
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
u32 command = rp.Pop<u32>();
- std::vector<u8> output(ctx.GetWriteBufferSize());
+ /// Ioctl 3 has 2 outputs, first in the input params, second is the result
+ std::vector<u8> output(ctx.GetWriteBufferSize(0));
+ std::vector<u8> output2;
+ if (version == IoctlVersion::Version3) {
+ output2.resize((ctx.GetWriteBufferSize(1)));
+ }
+
+ /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer.
+ /// KickOfPB uses this
+ auto input = ctx.ReadBuffer(0);
+
+ std::vector<u8> input2;
+ if (version == IoctlVersion::Version2) {
+ input2 = ctx.ReadBuffer(1);
+ }
IoctlCtrl ctrl{};
- u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output, ctrl);
+ u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version);
if (ctrl.must_delay) {
ctrl.fresh_call = false;
- ctx.SleepClientThread(
- "NVServices::DelayedResponse", ctrl.timeout,
- [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
- Kernel::ThreadWakeupReason reason) {
- IoctlCtrl ctrl2{ctrl};
- std::vector<u8> output2 = output;
- u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output2, ctrl2);
- ctx.WriteBuffer(output2);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(result);
- },
- nvdrv->GetEventWriteable(ctrl.event_id));
+ ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout,
+ [=](Kernel::SharedPtr<Kernel::Thread> thread,
+ Kernel::HLERequestContext& ctx,
+ Kernel::ThreadWakeupReason reason) {
+ IoctlCtrl ctrl2{ctrl};
+ std::vector<u8> tmp_output = output;
+ std::vector<u8> tmp_output2 = output2;
+ u32 result = nvdrv->Ioctl(fd, command, input, input2, tmp_output,
+ tmp_output2, ctrl2, version);
+ ctx.WriteBuffer(tmp_output, 0);
+ if (version == IoctlVersion::Version3) {
+ ctx.WriteBuffer(tmp_output2, 1);
+ }
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(result);
+ },
+ nvdrv->GetEventWriteable(ctrl.event_id));
} else {
ctx.WriteBuffer(output);
+ if (version == IoctlVersion::Version3) {
+ ctx.WriteBuffer(output2, 1);
+ }
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(result);
}
+void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlBase(ctx, IoctlVersion::Version1);
+}
+
+void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlBase(ctx, IoctlVersion::Version2);
+}
+
+void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlBase(ctx, IoctlVersion::Version3);
+}
+
void NVDRV::Close(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
@@ -154,8 +189,8 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
{8, &NVDRV::SetClientPID, "SetClientPID"},
{9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"},
{10, nullptr, "InitializeDevtools"},
- {11, &NVDRV::Ioctl, "Ioctl2"},
- {12, nullptr, "Ioctl3"},
+ {11, &NVDRV::Ioctl2, "Ioctl2"},
+ {12, &NVDRV::Ioctl3, "Ioctl3"},
{13, &NVDRV::FinishInitialize, "FinishInitialize"},
};
RegisterHandlers(functions);
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index 10a0ecd52..9269ce00c 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -24,6 +24,8 @@ public:
private:
void Open(Kernel::HLERequestContext& ctx);
void Ioctl(Kernel::HLERequestContext& ctx);
+ void Ioctl2(Kernel::HLERequestContext& ctx);
+ void Ioctl3(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
void Initialize(Kernel::HLERequestContext& ctx);
void QueryEvent(Kernel::HLERequestContext& ctx);
@@ -31,6 +33,7 @@ private:
void FinishInitialize(Kernel::HLERequestContext& ctx);
void GetStatus(Kernel::HLERequestContext& ctx);
void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx);
+ void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version);
std::shared_ptr<Module> nvdrv;
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index ac03cbc23..529b03471 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -34,6 +34,12 @@ enum class EventState {
Busy = 3,
};
+enum class IoctlVersion : u32 {
+ Version1,
+ Version2,
+ Version3,
+};
+
struct IoctlCtrl {
// First call done to the servioce for services that call itself again after a call.
bool fresh_call{true};
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index 2011a226a..307a7e928 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -71,13 +71,14 @@ u32 Module::Open(const std::string& device_name) {
return fd;
}
-u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl) {
+u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version) {
auto itr = open_files.find(fd);
ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
auto& device = itr->second;
- return device->ioctl({command}, input, output, ctrl);
+ return device->ioctl({command}, input, input2, output, output2, ctrl, version);
}
ResultCode Module::Close(u32 fd) {
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index a339ab672..f8bb28969 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -106,8 +106,9 @@ public:
/// Opens a device node and returns a file descriptor to it.
u32 Open(const std::string& device_name);
/// Sends an ioctl command to the specified file descriptor.
- u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output,
- IoctlCtrl& ctrl);
+ u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
+ std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
+ IoctlVersion version);
/// Closes a device file descriptor and returns operation success.
ResultCode Close(u32 fd);
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index f9db79370..2e4d707b9 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -29,26 +29,28 @@ namespace Service::NVFlinger {
constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30);
-NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} {
- displays.emplace_back(0, "Default");
- displays.emplace_back(1, "External");
- displays.emplace_back(2, "Edid");
- displays.emplace_back(3, "Internal");
- displays.emplace_back(4, "Null");
+NVFlinger::NVFlinger(Core::System& system) : system(system) {
+ displays.emplace_back(0, "Default", system);
+ displays.emplace_back(1, "External", system);
+ displays.emplace_back(2, "Edid", system);
+ displays.emplace_back(3, "Internal", system);
+ displays.emplace_back(4, "Null", system);
// Schedule the screen composition events
- composition_event = core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata,
- s64 cycles_late) {
- Compose();
- const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks();
- this->core_timing.ScheduleEvent(std::max<s64>(0LL, ticks - cycles_late), composition_event);
- });
-
- core_timing.ScheduleEvent(frame_ticks, composition_event);
+ composition_event = system.CoreTiming().RegisterEvent(
+ "ScreenComposition", [this](u64 userdata, s64 cycles_late) {
+ Compose();
+ const auto ticks =
+ Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks();
+ this->system.CoreTiming().ScheduleEvent(std::max<s64>(0LL, ticks - cycles_late),
+ composition_event);
+ });
+
+ system.CoreTiming().ScheduleEvent(frame_ticks, composition_event);
}
NVFlinger::~NVFlinger() {
- core_timing.UnscheduleEvent(composition_event, 0);
+ system.CoreTiming().UnscheduleEvent(composition_event, 0);
}
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
@@ -185,11 +187,9 @@ void NVFlinger::Compose() {
MicroProfileFlip();
if (!buffer) {
- auto& system_instance = Core::System::GetInstance();
-
// There was no queued buffer to draw, render previous frame
- system_instance.GetPerfStats().EndGameFrame();
- system_instance.GPU().SwapBuffers({});
+ system.GetPerfStats().EndGameFrame();
+ system.GPU().SwapBuffers({});
continue;
}
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 988be8726..5d7e3bfb8 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -38,7 +38,7 @@ class BufferQueue;
class NVFlinger final {
public:
- explicit NVFlinger(Core::Timing::CoreTiming& core_timing);
+ explicit NVFlinger(Core::System& system);
~NVFlinger();
/// Sets the NVDrv module instance to use to send buffers to the GPU.
@@ -105,8 +105,7 @@ private:
/// Event that handles screen composition.
Core::Timing::EventType* composition_event;
- /// Core timing instance for registering/unregistering the composition event.
- Core::Timing::CoreTiming& core_timing;
+ Core::System& system;
};
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 0f79135ff..18d895263 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -15,7 +15,7 @@ namespace Service::PlayReport {
class PlayReport final : public ServiceFramework<PlayReport> {
public:
- explicit PlayReport(Core::System& system, const char* name)
+ explicit PlayReport(const char* name, Core::System& system)
: ServiceFramework{name}, system(system) {
// clang-format off
static const FunctionInfo functions[] = {
@@ -128,12 +128,12 @@ private:
Core::System& system;
};
-void InstallInterfaces(Core::System& system) {
- std::make_shared<PlayReport>(system, "prepo:a")->InstallAsService(system.ServiceManager());
- std::make_shared<PlayReport>(system, "prepo:a2")->InstallAsService(system.ServiceManager());
- std::make_shared<PlayReport>(system, "prepo:m")->InstallAsService(system.ServiceManager());
- std::make_shared<PlayReport>(system, "prepo:s")->InstallAsService(system.ServiceManager());
- std::make_shared<PlayReport>(system, "prepo:u")->InstallAsService(system.ServiceManager());
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<PlayReport>("prepo:a", system)->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:a2", system)->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:m", system)->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:s", system)->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:u", system)->InstallAsService(service_manager);
}
} // namespace Service::PlayReport
diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h
index 0ebc3a938..a5682ee26 100644
--- a/src/core/hle/service/prepo/prepo.h
+++ b/src/core/hle/service/prepo/prepo.h
@@ -8,8 +8,12 @@ namespace Service::SM {
class ServiceManager;
}
+namespace Core {
+class System;
+}
+
namespace Service::PlayReport {
-void InstallInterfaces(Core::System& system);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::PlayReport
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 906fdc415..f2c6fe9dc 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -198,50 +198,50 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
// here and pass it into the respective InstallInterfaces functions.
- auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system.CoreTiming());
+ auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system);
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
SM::ServiceManager::InstallInterfaces(sm);
Account::InstallInterfaces(system);
AM::InstallInterfaces(*sm, nv_flinger, system);
- AOC::InstallInterfaces(*sm);
+ AOC::InstallInterfaces(*sm, system);
APM::InstallInterfaces(system);
Audio::InstallInterfaces(*sm, system);
- BCAT::InstallInterfaces(*sm);
+ BCAT::InstallInterfaces(system);
BPC::InstallInterfaces(*sm);
- BtDrv::InstallInterfaces(*sm);
- BTM::InstallInterfaces(*sm);
+ BtDrv::InstallInterfaces(*sm, system);
+ BTM::InstallInterfaces(*sm, system);
Capture::InstallInterfaces(*sm);
ERPT::InstallInterfaces(*sm);
ES::InstallInterfaces(*sm);
EUPLD::InstallInterfaces(*sm);
- Fatal::InstallInterfaces(*sm);
+ Fatal::InstallInterfaces(*sm, system);
FGM::InstallInterfaces(*sm);
FileSystem::InstallInterfaces(system);
- Friend::InstallInterfaces(*sm);
+ Friend::InstallInterfaces(*sm, system);
Glue::InstallInterfaces(system);
GRC::InstallInterfaces(*sm);
- HID::InstallInterfaces(*sm);
+ HID::InstallInterfaces(*sm, system);
LBL::InstallInterfaces(*sm);
LDN::InstallInterfaces(*sm);
- LDR::InstallInterfaces(*sm);
+ LDR::InstallInterfaces(*sm, system);
LM::InstallInterfaces(*sm);
Migration::InstallInterfaces(*sm);
Mii::InstallInterfaces(*sm);
MM::InstallInterfaces(*sm);
NCM::InstallInterfaces(*sm);
NFC::InstallInterfaces(*sm);
- NFP::InstallInterfaces(*sm);
- NIFM::InstallInterfaces(*sm);
- NIM::InstallInterfaces(*sm);
+ NFP::InstallInterfaces(*sm, system);
+ NIFM::InstallInterfaces(*sm, system);
+ NIM::InstallInterfaces(*sm, system);
NPNS::InstallInterfaces(*sm);
- NS::InstallInterfaces(*sm, system.GetFileSystemController());
+ NS::InstallInterfaces(*sm, system);
Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
PCIe::InstallInterfaces(*sm);
PCTL::InstallInterfaces(*sm);
PCV::InstallInterfaces(*sm);
- PlayReport::InstallInterfaces(system);
+ PlayReport::InstallInterfaces(*sm, system);
PM::InstallInterfaces(system);
PSC::InstallInterfaces(*sm);
PSM::InstallInterfaces(*sm);
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index 1030185e0..9565e7de5 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -7,8 +7,8 @@
namespace Service::Time {
Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
- const char* name)
- : Module::Interface(std::move(time), std::move(shared_memory), name) {
+ Core::System& system, const char* name)
+ : Module::Interface(std::move(time), std::move(shared_memory), system, name) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h
index bdf0883e2..5c63a07f4 100644
--- a/src/core/hle/service/time/interface.h
+++ b/src/core/hle/service/time/interface.h
@@ -13,7 +13,7 @@ class SharedMemory;
class Time final : public Module::Interface {
public:
explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
- const char* name);
+ Core::System& system, const char* name);
~Time() override;
};
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index ae6446204..1b9ab8401 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -126,8 +126,8 @@ private:
class ISteadyClock final : public ServiceFramework<ISteadyClock> {
public:
- ISteadyClock(std::shared_ptr<SharedMemory> shared_memory)
- : ServiceFramework("ISteadyClock"), shared_memory(shared_memory) {
+ ISteadyClock(std::shared_ptr<SharedMemory> shared_memory, Core::System& system)
+ : ServiceFramework("ISteadyClock"), shared_memory(shared_memory), system(system) {
static const FunctionInfo functions[] = {
{0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
};
@@ -150,12 +150,13 @@ private:
}
SteadyClockTimePoint GetCurrentTimePoint() const {
- const auto& core_timing = Core::System::GetInstance().CoreTiming();
+ const auto& core_timing = system.CoreTiming();
const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
return {static_cast<u64_le>(ms.count() / 1000), {}};
}
std::shared_ptr<SharedMemory> shared_memory;
+ Core::System& system;
};
class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
@@ -290,7 +291,7 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISteadyClock>(shared_memory);
+ rb.PushIpcInterface<ISteadyClock>(shared_memory, system);
}
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
@@ -325,7 +326,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
return;
}
- const auto& core_timing = Core::System::GetInstance().CoreTiming();
+ const auto& core_timing = system.CoreTiming();
const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}};
@@ -407,8 +408,10 @@ void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled(
}
Module::Interface::Interface(std::shared_ptr<Module> time,
- std::shared_ptr<SharedMemory> shared_memory, const char* name)
- : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)) {}
+ std::shared_ptr<SharedMemory> shared_memory, Core::System& system,
+ const char* name)
+ : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)),
+ system(system) {}
Module::Interface::~Interface() = default;
@@ -416,9 +419,11 @@ void InstallInterfaces(Core::System& system) {
auto time = std::make_shared<Module>();
auto shared_mem = std::make_shared<SharedMemory>(system);
- std::make_shared<Time>(time, shared_mem, "time:a")->InstallAsService(system.ServiceManager());
- std::make_shared<Time>(time, shared_mem, "time:s")->InstallAsService(system.ServiceManager());
- std::make_shared<Time>(std::move(time), shared_mem, "time:u")
+ std::make_shared<Time>(time, shared_mem, system, "time:a")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<Time>(time, shared_mem, system, "time:s")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<Time>(std::move(time), shared_mem, system, "time:u")
->InstallAsService(system.ServiceManager());
}
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index e0708f856..c32d32860 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -80,7 +80,8 @@ public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(std::shared_ptr<Module> time,
- std::shared_ptr<SharedMemory> shared_memory, const char* name);
+ std::shared_ptr<SharedMemory> shared_memory, Core::System& system,
+ const char* name);
~Interface() override;
void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
@@ -97,6 +98,7 @@ public:
protected:
std::shared_ptr<Module> time;
std::shared_ptr<SharedMemory> shared_memory;
+ Core::System& system;
};
};
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index a8d088305..006a6d9ff 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -15,8 +15,8 @@
namespace Service::VI {
-Display::Display(u64 id, std::string name) : id{id}, name{std::move(name)} {
- auto& kernel = Core::System::GetInstance().Kernel();
+Display::Display(u64 id, std::string name, Core::System& system) : id{id}, name{std::move(name)} {
+ auto& kernel = system.Kernel();
vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
fmt::format("Display VSync Event {}", id));
}
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 2acd46ff8..f56b5badc 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -26,7 +26,7 @@ public:
/// @param id The unique ID for this display.
/// @param name The name for this display.
///
- Display(u64 id, std::string name);
+ Display(u64 id, std::string name, Core::System& system);
~Display();
Display(const Display&) = delete;
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/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/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 98a8b5337..7ff44f06d 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -29,8 +29,8 @@ void Fermi2D::CallMethod(const GPU::MethodCall& method_call) {
}
void Fermi2D::HandleSurfaceCopy() {
- LOG_WARNING(HW_GPU, "Requested a surface copy with operation {}",
- static_cast<u32>(regs.operation));
+ LOG_DEBUG(HW_GPU, "Requested a surface copy with operation {}",
+ static_cast<u32>(regs.operation));
// TODO(Subv): Only raw copies are implemented.
ASSERT(regs.operation == Operation::SrcCopy);
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index fb3d1112c..b318aedb8 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -92,6 +92,10 @@ void Maxwell3D::InitializeRegisterDefaults() {
// Some games (like Super Mario Odyssey) assume that SRGB is enabled.
regs.framebuffer_srgb = 1;
+ mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_end_gl)] = true;
+ mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)] = true;
+ mme_inline[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true;
+ mme_inline[MAXWELL3D_REG_INDEX(index_array.count)] = true;
}
#define DIRTY_REGS_POS(field_name) (offsetof(Maxwell3D::DirtyRegs, field_name))
@@ -256,6 +260,9 @@ void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u3
// Execute the current macro.
macro_interpreter.Execute(macro_positions[entry], num_parameters, parameters);
+ if (mme_draw.current_mode != MMEDrawMode::Undefined) {
+ FlushMMEInlineDraw();
+ }
}
void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
@@ -416,6 +423,97 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
}
}
+void Maxwell3D::StepInstance(const MMEDrawMode expected_mode, const u32 count) {
+ if (mme_draw.current_mode == MMEDrawMode::Undefined) {
+ if (mme_draw.gl_begin_consume) {
+ mme_draw.current_mode = expected_mode;
+ mme_draw.current_count = count;
+ mme_draw.instance_count = 1;
+ mme_draw.gl_begin_consume = false;
+ mme_draw.gl_end_count = 0;
+ }
+ return;
+ } else {
+ if (mme_draw.current_mode == expected_mode && count == mme_draw.current_count &&
+ mme_draw.instance_mode && mme_draw.gl_begin_consume) {
+ mme_draw.instance_count++;
+ mme_draw.gl_begin_consume = false;
+ return;
+ } else {
+ FlushMMEInlineDraw();
+ }
+ }
+ // Tail call in case it needs to retry.
+ StepInstance(expected_mode, count);
+}
+
+void Maxwell3D::CallMethodFromMME(const GPU::MethodCall& method_call) {
+ const u32 method = method_call.method;
+ if (mme_inline[method]) {
+ regs.reg_array[method] = method_call.argument;
+ if (method == MAXWELL3D_REG_INDEX(vertex_buffer.count) ||
+ method == MAXWELL3D_REG_INDEX(index_array.count)) {
+ const MMEDrawMode expected_mode = method == MAXWELL3D_REG_INDEX(vertex_buffer.count)
+ ? MMEDrawMode::Array
+ : MMEDrawMode::Indexed;
+ StepInstance(expected_mode, method_call.argument);
+ } else if (method == MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)) {
+ mme_draw.instance_mode =
+ (regs.draw.instance_next != 0) || (regs.draw.instance_cont != 0);
+ mme_draw.gl_begin_consume = true;
+ } else {
+ mme_draw.gl_end_count++;
+ }
+ } else {
+ if (mme_draw.current_mode != MMEDrawMode::Undefined) {
+ FlushMMEInlineDraw();
+ }
+ CallMethod(method_call);
+ }
+}
+
+void Maxwell3D::FlushMMEInlineDraw() {
+ LOG_DEBUG(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
+ regs.vertex_buffer.count);
+ ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
+ ASSERT(mme_draw.instance_count == mme_draw.gl_end_count);
+
+ auto debug_context = system.GetGPUDebugContext();
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
+ }
+
+ // Both instance configuration registers can not be set at the same time.
+ ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont,
+ "Illegal combination of instancing parameters");
+
+ const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed;
+ if (ShouldExecute()) {
+ rasterizer.DrawMultiBatch(is_indexed);
+ }
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
+ }
+
+ // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
+ // the game is trying to draw indexed or direct mode. This needs to be verified on HW still -
+ // it's possible that it is incorrect and that there is some other register used to specify the
+ // drawing mode.
+ if (is_indexed) {
+ regs.index_array.count = 0;
+ } else {
+ regs.vertex_buffer.count = 0;
+ }
+ mme_draw.current_mode = MMEDrawMode::Undefined;
+ mme_draw.current_count = 0;
+ mme_draw.instance_count = 0;
+ mme_draw.instance_mode = false;
+ mme_draw.gl_begin_consume = false;
+ mme_draw.gl_end_count = 0;
+}
+
void Maxwell3D::ProcessMacroUpload(u32 data) {
ASSERT_MSG(regs.macros.upload_address < macro_memory.size(),
"upload_address exceeded macro_memory size!");
@@ -564,7 +662,9 @@ void Maxwell3D::DrawArrays() {
}
const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
- rasterizer.AccelerateDrawBatch(is_indexed);
+ if (ShouldExecute()) {
+ rasterizer.DrawBatch(is_indexed);
+ }
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index e5ec90717..4c97759ed 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -811,8 +811,9 @@ public:
INSERT_PADDING_WORDS(0x21);
u32 vb_element_base;
+ u32 vb_base_instance;
- INSERT_PADDING_WORDS(0x36);
+ INSERT_PADDING_WORDS(0x35);
union {
BitField<0, 1, u32> c0;
@@ -1238,6 +1239,11 @@ public:
/// Write the value to the register identified by method.
void CallMethod(const GPU::MethodCall& method_call);
+ /// Write the value to the register identified by method.
+ void CallMethodFromMME(const GPU::MethodCall& method_call);
+
+ void FlushMMEInlineDraw();
+
/// Given a Texture Handle, returns the TSC and TIC entries.
Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
std::size_t offset) const;
@@ -1263,6 +1269,21 @@ public:
return execute_on;
}
+ enum class MMEDrawMode : u32 {
+ Undefined,
+ Array,
+ Indexed,
+ };
+
+ struct MMEDrawState {
+ MMEDrawMode current_mode{MMEDrawMode::Undefined};
+ u32 current_count{};
+ u32 instance_count{};
+ bool instance_mode{};
+ bool gl_begin_consume{};
+ u32 gl_end_count{};
+ } mme_draw;
+
private:
void InitializeRegisterDefaults();
@@ -1275,6 +1296,8 @@ private:
/// Start offsets of each macro in macro_memory
std::array<u32, 0x80> macro_positions = {};
+ std::array<bool, Regs::NUM_REGS> mme_inline{};
+
/// Memory for macro code
MacroMemory macro_memory;
@@ -1346,6 +1369,9 @@ private:
/// Handles a write to the VERTEX_END_GL register, triggering a draw.
void DrawArrays();
+
+ // Handles a instance drawcall from MME
+ void StepInstance(MMEDrawMode expected_mode, u32 count);
};
#define ASSERT_REG_POSITION(field_name, position) \
@@ -1402,6 +1428,7 @@ ASSERT_REG_POSITION(stencil_front_mask, 0x4E7);
ASSERT_REG_POSITION(frag_color_clamp, 0x4EA);
ASSERT_REG_POSITION(screen_y_control, 0x4EB);
ASSERT_REG_POSITION(vb_element_base, 0x50D);
+ASSERT_REG_POSITION(vb_base_instance, 0x50E);
ASSERT_REG_POSITION(clip_distance_enabled, 0x544);
ASSERT_REG_POSITION(point_size, 0x546);
ASSERT_REG_POSITION(zeta_enable, 0x54E);
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/gpu.cpp b/src/video_core/gpu.cpp
index 2c47541cb..76cfe8107 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -122,6 +122,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
case RenderTargetFormat::RGBA16_UINT:
case RenderTargetFormat::RGBA16_UNORM:
case RenderTargetFormat::RGBA16_FLOAT:
+ case RenderTargetFormat::RGBX16_FLOAT:
case RenderTargetFormat::RG32_FLOAT:
case RenderTargetFormat::RG32_UINT:
return 8;
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 78bc0601a..29fa8e95b 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -42,6 +42,7 @@ enum class RenderTargetFormat : u32 {
RGBA16_FLOAT = 0xCA,
RG32_FLOAT = 0xCB,
RG32_UINT = 0xCD,
+ RGBX16_FLOAT = 0xCE,
BGRA8_UNORM = 0xCF,
BGRA8_SRGB = 0xD0,
RGB10_A2_UNORM = 0xD1,
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 62afc0d11..dbaeac6db 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -257,7 +257,7 @@ void MacroInterpreter::SetMethodAddress(u32 address) {
}
void MacroInterpreter::Send(u32 value) {
- maxwell3d.CallMethod({method_address.address, value});
+ maxwell3d.CallMethodFromMME({method_address.address, value});
// Increment the method address by the method increment.
method_address.address.Assign(method_address.address.Value() +
method_address.increment.Value());
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
index 084f85e67..ab71870ab 100644
--- a/src/video_core/morton.cpp
+++ b/src/video_core/morton.cpp
@@ -83,6 +83,7 @@ static constexpr ConversionArray morton_to_linear_fns = {
MortonCopy<true, PixelFormat::RG8U>,
MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::RG32UI>,
+ MortonCopy<true, PixelFormat::RGBX16F>,
MortonCopy<true, PixelFormat::R32UI>,
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
@@ -151,6 +152,7 @@ static constexpr ConversionArray linear_to_morton_fns = {
MortonCopy<false, PixelFormat::RG8U>,
MortonCopy<false, PixelFormat::RG8S>,
MortonCopy<false, PixelFormat::RG32UI>,
+ MortonCopy<false, PixelFormat::RGBX16F>,
MortonCopy<false, PixelFormat::R32UI>,
nullptr,
nullptr,
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 6b3f2d50a..5b0eca9e2 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -29,7 +29,10 @@ public:
virtual ~RasterizerInterface() {}
/// Draw the current batch of vertex arrays
- virtual void DrawArrays() = 0;
+ virtual bool DrawBatch(bool is_indexed) = 0;
+
+ /// Draw the current batch of multiple instances of vertex arrays
+ virtual bool DrawMultiBatch(bool is_indexed) = 0;
/// Clear the current framebuffer
virtual void Clear() = 0;
@@ -69,10 +72,6 @@ public:
return false;
}
- virtual bool AccelerateDrawBatch(bool is_indexed) {
- return false;
- }
-
/// Increase/decrease the number of object in pages touching the specified region
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
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_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index a2c1473db..6a17bed72 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -49,40 +49,6 @@ MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100));
-struct DrawParameters {
- GLenum primitive_mode;
- GLsizei count;
- GLint current_instance;
- bool use_indexed;
-
- GLint vertex_first;
-
- GLenum index_format;
- GLint base_vertex;
- GLintptr index_buffer_offset;
-
- void DispatchDraw() const {
- if (use_indexed) {
- const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset);
- if (current_instance > 0) {
- glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format,
- index_buffer_ptr, 1, base_vertex,
- current_instance);
- } else {
- glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
- base_vertex);
- }
- } else {
- if (current_instance > 0) {
- glDrawArraysInstancedBaseInstance(primitive_mode, vertex_first, count, 1,
- current_instance);
- } else {
- glDrawArrays(primitive_mode, vertex_first, count);
- }
- }
- }
-};
-
static std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer,
const GLShader::ConstBufferEntry& entry) {
if (!entry.IsIndirect()) {
@@ -270,29 +236,6 @@ GLintptr RasterizerOpenGL::SetupIndexBuffer() {
return offset;
}
-DrawParameters RasterizerOpenGL::SetupDraw(GLintptr index_buffer_offset) {
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
- const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
-
- DrawParameters params{};
- params.current_instance = gpu.state.current_instance;
-
- params.use_indexed = is_indexed;
- params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
-
- if (is_indexed) {
- params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
- params.count = regs.index_array.count;
- params.index_buffer_offset = index_buffer_offset;
- params.base_vertex = static_cast<GLint>(regs.vb_element_base);
- } else {
- params.count = regs.vertex_buffer.count;
- params.vertex_first = regs.vertex_buffer.first;
- }
- return params;
-}
-
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
auto& gpu = system.GPU().Maxwell3D();
@@ -399,12 +342,6 @@ std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
}
-bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
- accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
- DrawArrays();
- return true;
-}
-
template <typename Map, typename Interval>
static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
@@ -640,17 +577,9 @@ void RasterizerOpenGL::Clear() {
}
}
-void RasterizerOpenGL::DrawArrays() {
- if (accelerate_draw == AccelDraw::Disabled)
- return;
-
- MICROPROFILE_SCOPE(OpenGL_Drawing);
+void RasterizerOpenGL::DrawPrelude() {
auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.ShouldExecute()) {
- return;
- }
-
SyncColorMask();
SyncFragmentColorClampState();
SyncMultiSampleState();
@@ -695,10 +624,7 @@ void RasterizerOpenGL::DrawArrays() {
// Upload vertex and index data.
SetupVertexBuffer(vao);
SetupVertexInstances(vao);
- const GLintptr index_buffer_offset = SetupIndexBuffer();
-
- // Setup draw parameters. It will automatically choose what glDraw* method to use.
- const DrawParameters params = SetupDraw(index_buffer_offset);
+ index_buffer_offset = SetupIndexBuffer();
// Prepare packed bindings.
bind_ubo_pushbuffer.Setup(0);
@@ -706,7 +632,8 @@ void RasterizerOpenGL::DrawArrays() {
// Setup shaders and their used resources.
texture_cache.GuardSamplers(true);
- SetupShaders(params.primitive_mode);
+ const auto primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology);
+ SetupShaders(primitive_mode);
texture_cache.GuardSamplers(false);
ConfigureFramebuffers();
@@ -730,11 +657,107 @@ void RasterizerOpenGL::DrawArrays() {
if (texture_cache.TextureBarrier()) {
glTextureBarrier();
}
+}
+
+struct DrawParams {
+ bool is_indexed{};
+ bool is_instanced{};
+ GLenum primitive_mode{};
+ GLint count{};
+ GLint base_vertex{};
+
+ // Indexed settings
+ GLenum index_format{};
+ GLintptr index_buffer_offset{};
+
+ // Instanced setting
+ GLint num_instances{};
+ GLint base_instance{};
+
+ void DispatchDraw() {
+ if (is_indexed) {
+ const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset);
+ if (is_instanced) {
+ glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format,
+ index_buffer_ptr, num_instances,
+ base_vertex, base_instance);
+ } else {
+ glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
+ base_vertex);
+ }
+ } else {
+ if (is_instanced) {
+ glDrawArraysInstancedBaseInstance(primitive_mode, base_vertex, count, num_instances,
+ base_instance);
+ } else {
+ glDrawArrays(primitive_mode, base_vertex, count);
+ }
+ }
+ }
+};
- params.DispatchDraw();
+bool RasterizerOpenGL::DrawBatch(bool is_indexed) {
+ accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
+
+ MICROPROFILE_SCOPE(OpenGL_Drawing);
+ DrawPrelude();
+
+ auto& maxwell3d = system.GPU().Maxwell3D();
+ const auto& regs = maxwell3d.regs;
+ const auto current_instance = maxwell3d.state.current_instance;
+ DrawParams draw_call{};
+ draw_call.is_indexed = is_indexed;
+ draw_call.num_instances = static_cast<GLint>(1);
+ draw_call.base_instance = static_cast<GLint>(current_instance);
+ draw_call.is_instanced = current_instance > 0;
+ draw_call.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
+ if (draw_call.is_indexed) {
+ draw_call.count = static_cast<GLint>(regs.index_array.count);
+ draw_call.base_vertex = static_cast<GLint>(regs.vb_element_base);
+ draw_call.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
+ draw_call.index_buffer_offset = index_buffer_offset;
+ } else {
+ draw_call.count = static_cast<GLint>(regs.vertex_buffer.count);
+ draw_call.base_vertex = static_cast<GLint>(regs.vertex_buffer.first);
+ }
+ draw_call.DispatchDraw();
+
+ maxwell3d.dirty.memory_general = false;
accelerate_draw = AccelDraw::Disabled;
- gpu.dirty.memory_general = false;
+ return true;
+}
+
+bool RasterizerOpenGL::DrawMultiBatch(bool is_indexed) {
+ accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
+
+ MICROPROFILE_SCOPE(OpenGL_Drawing);
+
+ DrawPrelude();
+
+ auto& maxwell3d = system.GPU().Maxwell3D();
+ const auto& regs = maxwell3d.regs;
+ const auto& draw_setup = maxwell3d.mme_draw;
+ DrawParams draw_call{};
+ draw_call.is_indexed = is_indexed;
+ draw_call.num_instances = static_cast<GLint>(draw_setup.instance_count);
+ draw_call.base_instance = static_cast<GLint>(regs.vb_base_instance);
+ draw_call.is_instanced = draw_setup.instance_count > 1;
+ draw_call.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
+ if (draw_call.is_indexed) {
+ draw_call.count = static_cast<GLint>(regs.index_array.count);
+ draw_call.base_vertex = static_cast<GLint>(regs.vb_element_base);
+ draw_call.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
+ draw_call.index_buffer_offset = index_buffer_offset;
+ } else {
+ draw_call.count = static_cast<GLint>(regs.vertex_buffer.count);
+ draw_call.base_vertex = static_cast<GLint>(regs.vertex_buffer.first);
+ }
+ draw_call.DispatchDraw();
+
+ maxwell3d.dirty.memory_general = false;
+ accelerate_draw = AccelDraw::Disabled;
+ return true;
}
void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 4f5c7f864..9c10ebda3 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -57,7 +57,8 @@ public:
ScreenInfo& info);
~RasterizerOpenGL() override;
- void DrawArrays() override;
+ bool DrawBatch(bool is_indexed) override;
+ bool DrawMultiBatch(bool is_indexed) override;
void Clear() override;
void DispatchCompute(GPUVAddr code_addr) override;
void FlushAll() override;
@@ -71,7 +72,6 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- bool AccelerateDrawBatch(bool is_indexed) override;
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override;
void LoadDiskResources(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
@@ -105,6 +105,9 @@ private:
void SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entry, GPUVAddr gpu_addr,
std::size_t size);
+ /// Syncs all the state, shaders, render targets and textures setting before a draw call.
+ void DrawPrelude();
+
/// Configures the current textures to use for the draw command. Returns shaders texture buffer
/// usage.
TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
@@ -216,7 +219,7 @@ private:
GLintptr SetupIndexBuffer();
- DrawParameters SetupDraw(GLintptr index_buffer_offset);
+ GLintptr index_buffer_offset;
void SetupShaders(GLenum primitive_mode);
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 76439e7ab..8fa9e6534 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -19,6 +19,7 @@
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
+#include "video_core/shader/node.h"
#include "video_core/shader/shader_ir.h"
namespace OpenGL::GLShader {
@@ -241,6 +242,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"};
@@ -398,8 +419,6 @@ 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;
}
@@ -462,6 +481,14 @@ private:
code.AddLine("float gl_PointSize;");
}
+ if (ir.UsesInstanceId()) {
+ code.AddLine("int gl_InstanceID;");
+ }
+
+ if (ir.UsesVertexId()) {
+ code.AddLine("int gl_VertexID;");
+ }
+
--code.scope;
code.AddLine("}};");
code.AddNewLine();
@@ -714,42 +741,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";
@@ -757,9 +748,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();
@@ -964,7 +956,7 @@ private:
switch (element) {
case 2:
// Config pack's first value is instance_id.
- return {"config_pack[0]", Type::Uint};
+ return {"gl_InstanceID", Type::Int};
case 3:
return {"gl_VertexID", Type::Int};
}
@@ -1226,28 +1218,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 += ", ";
}
@@ -1256,29 +1233,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];
@@ -1537,6 +1491,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) {
@@ -1801,6 +1757,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),
@@ -1808,31 +1777,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) {
@@ -2027,6 +1979,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";
@@ -2164,14 +2122,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,
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..6a7012b54 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -343,20 +343,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 +379,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 +426,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 +454,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;
}
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 4f135fe03..173b76c4e 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -97,6 +97,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
+ {GL_RGB16F, GL_RGBA16, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBX16F
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 0bbbf6851..3c5acda3e 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -143,6 +143,7 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8U
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8S
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG32UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBX16F
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R32UI
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X8
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X5
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index f7fbbb6e4..77fc58f25 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 {
@@ -939,22 +940,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 {};
}
@@ -1440,10 +1436,9 @@ private:
&SPIRVDecompiler::TextureQueryLod,
&SPIRVDecompiler::TexelFetch,
+ &SPIRVDecompiler::ImageLoad,
&SPIRVDecompiler::ImageStore,
&SPIRVDecompiler::AtomicImageAdd,
- &SPIRVDecompiler::AtomicImageMin,
- &SPIRVDecompiler::AtomicImageMax,
&SPIRVDecompiler::AtomicImageAnd,
&SPIRVDecompiler::AtomicImageOr,
&SPIRVDecompiler::AtomicImageXor,
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 &image;
}
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 bbbab0bca..2c357f310 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -114,6 +114,18 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
break;
}
}
+ if (index == Attribute::Index::TessCoordInstanceIDVertexID) {
+ switch (element) {
+ case 2:
+ uses_instance_id = true;
+ break;
+ case 3:
+ uses_vertex_id = true;
+ break;
+ default:
+ break;
+ }
+ }
if (index == Attribute::Index::ClipDistances0123 ||
index == Attribute::Index::ClipDistances4567) {
const auto clip_index =
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 6aed9bb84..6f666ee30 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -124,6 +124,14 @@ public:
return uses_point_size;
}
+ bool UsesInstanceId() const {
+ return uses_instance_id;
+ }
+
+ bool UsesVertexId() const {
+ return uses_vertex_id;
+ }
+
bool HasPhysicalAttributes() const {
return uses_physical_attributes;
}
@@ -276,16 +284,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);
@@ -373,6 +378,8 @@ private:
bool uses_viewport_index{};
bool uses_point_size{};
bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes
+ bool uses_instance_id{};
+ bool uses_vertex_id{};
Tegra::Shader::Header header;
};
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 53d0142cb..250afc6d6 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -159,6 +159,8 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
return PixelFormat::R32UI;
case Tegra::RenderTargetFormat::RG32_UINT:
return PixelFormat::RG32UI;
+ case Tegra::RenderTargetFormat::RGBX16_FLOAT:
+ return PixelFormat::RGBX16F;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
@@ -415,6 +417,7 @@ ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {
case Tegra::RenderTargetFormat::RG8_SNORM:
return ComponentType::SNorm;
case Tegra::RenderTargetFormat::RGBA16_FLOAT:
+ case Tegra::RenderTargetFormat::RGBX16_FLOAT:
case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
case Tegra::RenderTargetFormat::RGBA32_FLOAT:
case Tegra::RenderTargetFormat::RG32_FLOAT:
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 19268b7cd..1e1c432a5 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -57,36 +57,37 @@ enum class PixelFormat {
RG8U = 39,
RG8S = 40,
RG32UI = 41,
- R32UI = 42,
- ASTC_2D_8X8 = 43,
- ASTC_2D_8X5 = 44,
- ASTC_2D_5X4 = 45,
- BGRA8_SRGB = 46,
- DXT1_SRGB = 47,
- DXT23_SRGB = 48,
- DXT45_SRGB = 49,
- BC7U_SRGB = 50,
- ASTC_2D_4X4_SRGB = 51,
- ASTC_2D_8X8_SRGB = 52,
- ASTC_2D_8X5_SRGB = 53,
- ASTC_2D_5X4_SRGB = 54,
- ASTC_2D_5X5 = 55,
- ASTC_2D_5X5_SRGB = 56,
- ASTC_2D_10X8 = 57,
- ASTC_2D_10X8_SRGB = 58,
+ RGBX16F = 42,
+ R32UI = 43,
+ ASTC_2D_8X8 = 44,
+ ASTC_2D_8X5 = 45,
+ ASTC_2D_5X4 = 46,
+ BGRA8_SRGB = 47,
+ DXT1_SRGB = 48,
+ DXT23_SRGB = 49,
+ DXT45_SRGB = 50,
+ BC7U_SRGB = 51,
+ ASTC_2D_4X4_SRGB = 52,
+ ASTC_2D_8X8_SRGB = 53,
+ ASTC_2D_8X5_SRGB = 54,
+ ASTC_2D_5X4_SRGB = 55,
+ ASTC_2D_5X5 = 56,
+ ASTC_2D_5X5_SRGB = 57,
+ ASTC_2D_10X8 = 58,
+ ASTC_2D_10X8_SRGB = 59,
MaxColorFormat,
// Depth formats
- Z32F = 59,
- Z16 = 60,
+ Z32F = 60,
+ Z16 = 61,
MaxDepthFormat,
// DepthStencil formats
- Z24S8 = 61,
- S8Z24 = 62,
- Z32FS8 = 63,
+ Z24S8 = 62,
+ S8Z24 = 63,
+ Z32FS8 = 64,
MaxDepthStencilFormat,
@@ -166,6 +167,7 @@ constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{
0, // RG8U
0, // RG8S
0, // RG32UI
+ 0, // RGBX16F
0, // R32UI
2, // ASTC_2D_8X8
2, // ASTC_2D_8X5
@@ -249,6 +251,7 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
1, // RG8U
1, // RG8S
1, // RG32UI
+ 1, // RGBX16F
1, // R32UI
8, // ASTC_2D_8X8
8, // ASTC_2D_8X5
@@ -324,6 +327,7 @@ constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
1, // RG8U
1, // RG8S
1, // RG32UI
+ 1, // RGBX16F
1, // R32UI
8, // ASTC_2D_8X8
5, // ASTC_2D_8X5
@@ -399,6 +403,7 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
16, // RG8U
16, // RG8S
64, // RG32UI
+ 64, // RGBX16F
32, // R32UI
128, // ASTC_2D_8X8
128, // ASTC_2D_8X5
@@ -489,6 +494,7 @@ constexpr std::array<SurfaceCompression, MaxPixelFormat> compression_type_table
SurfaceCompression::None, // RG8U
SurfaceCompression::None, // RG8S
SurfaceCompression::None, // RG32UI
+ SurfaceCompression::None, // RGBX16F
SurfaceCompression::None, // R32UI
SurfaceCompression::Converted, // ASTC_2D_8X8
SurfaceCompression::Converted, // ASTC_2D_8X5
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_general.cpp b/src/yuzu/configuration/configure_general.cpp
index b49446be9..98bc9b391 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <QCheckBox>
#include <QSpinBox>
#include "core/core.h"
#include "core/settings.h"
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
new file mode 100644
index 000000000..81c9e933f
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -0,0 +1,136 @@
+// 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::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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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/main.cpp b/src/yuzu/main.cpp
index f147044d9..2d82df739 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -22,6 +22,8 @@
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/scope_acquire_window_context.h"
#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/am/applet_ae.h"
+#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
@@ -83,6 +85,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/submission_package.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
@@ -1674,6 +1677,11 @@ void GMainWindow::OnStartGame() {
}
void GMainWindow::OnPauseGame() {
+ Core::System& system{Core::System::GetInstance()};
+ if (system.GetExitLock() && !ConfirmForceLockedExit()) {
+ return;
+ }
+
emu_thread->SetRunning(false);
ui.action_Start->setEnabled(true);
@@ -1685,6 +1693,11 @@ void GMainWindow::OnPauseGame() {
}
void GMainWindow::OnStopGame() {
+ Core::System& system{Core::System::GetInstance()};
+ if (system.GetExitLock() && !ConfirmForceLockedExit()) {
+ return;
+ }
+
ShutdownGame();
}
@@ -2182,13 +2195,41 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
- auto answer = QMessageBox::question(
+ const auto answer = QMessageBox::question(
this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return answer != QMessageBox::No;
}
+bool GMainWindow::ConfirmForceLockedExit() {
+ if (emu_thread == nullptr)
+ return true;
+
+ const auto answer =
+ QMessageBox::question(this, tr("yuzu"),
+ tr("The currently running application has requested yuzu to not "
+ "exit.\n\nWould you like to bypass this and exit anyway?"),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+ return answer != QMessageBox::No;
+}
+
+void GMainWindow::RequestGameExit() {
+ auto& sm{Core::System::GetInstance().ServiceManager()};
+ auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
+ auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
+ bool has_signalled = false;
+
+ if (applet_oe != nullptr) {
+ applet_oe->GetMessageQueue()->RequestExit();
+ has_signalled = true;
+ }
+
+ if (applet_ae != nullptr && !has_signalled) {
+ applet_ae->GetMessageQueue()->RequestExit();
+ }
+}
+
void GMainWindow::filterBarSetChecked(bool state) {
ui.action_Show_Filter_Bar->setChecked(state);
emit(OnToggleFilterBar());
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7d16188cb..e942d1248 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -172,6 +172,8 @@ private:
*/
bool ConfirmClose();
bool ConfirmChangeGame();
+ bool ConfirmForceLockedExit();
+ void RequestGameExit();
void closeEvent(QCloseEvent* event) override;
private slots:
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 '|'):