summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
m---------externals/dynarmic0
m---------externals/nx_tzdb/tzdb_to_nx0
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt124
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt2
-rw-r--r--src/common/settings.cpp3
-rw-r--r--src/common/settings.h32
-rw-r--r--src/common/settings_common.h1
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/core/CMakeLists.txt42
-rw-r--r--src/core/file_sys/content_archive.cpp4
-rw-r--r--src/core/file_sys/content_archive.h1
-rw-r--r--src/core/file_sys/errors.h1
-rw-r--r--src/core/hle/kernel/k_process.cpp7
-rw-r--r--src/core/hle/service/am/am_types.h6
-rw-r--r--src/core/hle/service/am/applet.h2
-rw-r--r--src/core/hle/service/am/applet_data_broker.h2
-rw-r--r--src/core/hle/service/am/frontend/applet_software_keyboard.cpp12
-rw-r--r--src/core/hle/service/am/frontend/applet_software_keyboard.h2
-rw-r--r--src/core/hle/service/am/library_applet_creator.cpp81
-rw-r--r--src/core/hle/service/am/process.cpp23
-rw-r--r--src/core/hle/service/am/process.h2
-rw-r--r--src/core/hle/service/am/self_controller.cpp18
-rw-r--r--src/core/hle/service/am/self_controller.h2
-rw-r--r--src/core/hle/service/am/system_buffer_manager.cpp19
-rw-r--r--src/core/hle/service/am/system_buffer_manager.h3
-rw-r--r--src/core/hle/service/am/window_controller.cpp4
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp30
-rw-r--r--src/core/hle/service/bcat/backend/backend.h54
-rw-r--r--src/core/hle/service/bcat/bcat.cpp42
-rw-r--r--src/core/hle/service/bcat/bcat.h9
-rw-r--r--src/core/hle/service/bcat/bcat_module.cpp606
-rw-r--r--src/core/hle/service/bcat/bcat_module.h46
-rw-r--r--src/core/hle/service/bcat/bcat_result.h15
-rw-r--r--src/core/hle/service/bcat/bcat_service.cpp132
-rw-r--r--src/core/hle/service/bcat/bcat_service.h45
-rw-r--r--src/core/hle/service/bcat/bcat_types.h66
-rw-r--r--src/core/hle/service/bcat/bcat_util.h39
-rw-r--r--src/core/hle/service/bcat/delivery_cache_directory_service.cpp80
-rw-r--r--src/core/hle/service/bcat/delivery_cache_directory_service.h33
-rw-r--r--src/core/hle/service/bcat/delivery_cache_file_service.cpp82
-rw-r--r--src/core/hle/service/bcat/delivery_cache_file_service.h33
-rw-r--r--src/core/hle/service/bcat/delivery_cache_progress_service.cpp41
-rw-r--r--src/core/hle/service/bcat/delivery_cache_progress_service.h35
-rw-r--r--src/core/hle/service/bcat/delivery_cache_storage_service.cpp57
-rw-r--r--src/core/hle/service/bcat/delivery_cache_storage_service.h36
-rw-r--r--src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp34
-rw-r--r--src/core/hle/service/bcat/news/newly_arrived_event_holder.h33
-rw-r--r--src/core/hle/service/bcat/news/news_data_service.cpp25
-rw-r--r--src/core/hle/service/bcat/news/news_data_service.h20
-rw-r--r--src/core/hle/service/bcat/news/news_database_service.cpp53
-rw-r--r--src/core/hle/service/bcat/news/news_database_service.h32
-rw-r--r--src/core/hle/service/bcat/news/news_service.cpp57
-rw-r--r--src/core/hle/service/bcat/news/news_service.h28
-rw-r--r--src/core/hle/service/bcat/news/overwrite_event_holder.cpp33
-rw-r--r--src/core/hle/service/bcat/news/overwrite_event_holder.h33
-rw-r--r--src/core/hle/service/bcat/news/service_creator.cpp64
-rw-r--r--src/core/hle/service/bcat/news/service_creator.h35
-rw-r--r--src/core/hle/service/bcat/service_creator.cpp62
-rw-r--r--src/core/hle/service/bcat/service_creator.h40
-rw-r--r--src/core/hle/service/glue/time/worker.cpp130
-rw-r--r--src/core/hle/service/nvdrv/core/container.cpp7
-rw-r--r--src/core/hle/service/nvdrv/core/container.h1
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp17
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp176
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.h24
-rw-r--r--src/core/hle/service/nvnflinger/hardware_composer.cpp1
-rw-r--r--src/core/hle/service/nvnflinger/hwc_layer.h13
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp7
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h6
-rw-r--r--src/core/hle/service/os/event.cpp (renamed from src/core/hle/service/event.cpp)2
-rw-r--r--src/core/hle/service/os/event.h (renamed from src/core/hle/service/event.h)0
-rw-r--r--src/core/hle/service/os/multi_wait.cpp59
-rw-r--r--src/core/hle/service/os/multi_wait.h36
-rw-r--r--src/core/hle/service/os/multi_wait_holder.cpp25
-rw-r--r--src/core/hle/service/os/multi_wait_holder.h44
-rw-r--r--src/core/hle/service/os/multi_wait_utils.h109
-rw-r--r--src/core/hle/service/os/mutex.cpp (renamed from src/core/hle/service/mutex.cpp)2
-rw-r--r--src/core/hle/service/os/mutex.h (renamed from src/core/hle/service/mutex.h)0
-rw-r--r--src/core/hle/service/server_manager.cpp438
-rw-r--r--src/core/hle/service/server_manager.h59
-rw-r--r--src/core/hle/service/service.cpp2
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.cpp6
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.h13
-rw-r--r--src/frontend_common/config.cpp28
-rw-r--r--src/frontend_common/config.h2
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/capture.h36
-rw-r--r--src/video_core/framebuffer_config.h7
-rw-r--r--src/video_core/gpu.cpp15
-rw-r--r--src/video_core/gpu.h2
-rw-r--r--src/video_core/host_shaders/fidelityfx_fsr.frag21
-rw-r--r--src/video_core/host_shaders/fxaa.frag2
-rw-r--r--src/video_core/host_shaders/opengl_fidelityfx_fsr.frag19
-rw-r--r--src/video_core/host_shaders/opengl_present.frag2
-rw-r--r--src/video_core/host_shaders/present_bicubic.frag2
-rw-r--r--src/video_core/host_shaders/present_gaussian.frag14
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag1
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag1
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag1
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag1
-rw-r--r--src/video_core/host_shaders/vulkan_present.vert10
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag2
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag2
-rw-r--r--src/video_core/present.h37
-rw-r--r--src/video_core/renderer_base.h3
-rw-r--r--src/video_core/renderer_null/renderer_null.cpp5
-rw-r--r--src/video_core/renderer_null/renderer_null.h2
-rw-r--r--src/video_core/renderer_opengl/gl_blit_screen.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_blit_screen.h7
-rw-r--r--src/video_core/renderer_opengl/present/layer.cpp35
-rw-r--r--src/video_core/renderer_opengl/present/layer.h8
-rw-r--r--src/video_core/renderer_opengl/present/window_adapt_pass.cpp19
-rw-r--r--src/video_core/renderer_opengl/present/window_adapt_pass.h2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp89
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h9
-rw-r--r--src/video_core/renderer_vulkan/present/layer.cpp19
-rw-r--r--src/video_core/renderer_vulkan/present/layer.h6
-rw-r--r--src/video_core/renderer_vulkan/present/util.cpp92
-rw-r--r--src/video_core/renderer_vulkan/present/util.h9
-rw-r--r--src/video_core/renderer_vulkan/present/window_adapt_pass.cpp29
-rw-r--r--src/video_core/renderer_vulkan/present/window_adapt_pass.h6
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp114
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h11
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h5
-rw-r--r--src/video_core/texture_cache/texture_cache.h8
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/configuration/configure_applets.cpp86
-rw-r--r--src/yuzu/configuration/configure_applets.h48
-rw-r--r--src/yuzu/configuration/configure_applets.ui65
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp7
-rw-r--r--src/yuzu/configuration/configure_dialog.h2
-rw-r--r--src/yuzu/configuration/shared_translation.cpp180
-rw-r--r--src/yuzu/hotkeys.cpp6
136 files changed, 3300 insertions, 1425 deletions
diff --git a/externals/dynarmic b/externals/dynarmic
Subproject ca0e264f4f962e29baa23a3282ce484625866b9 Subproject ba8192d89078af51ae6f97c9352e3683612cdff
diff --git a/externals/nx_tzdb/tzdb_to_nx b/externals/nx_tzdb/tzdb_to_nx
Subproject 404d39004570a26c734a9d1fa29ab4d63089c59 Subproject 97929690234f2b4add36b33657fe3fe09bd57df
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
index d14b2c634..3ea5e16ca 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
@@ -243,7 +243,9 @@ class GamePropertiesFragment : Fragment() {
243 requireActivity(), 243 requireActivity(),
244 titleId = R.string.delete_save_data, 244 titleId = R.string.delete_save_data,
245 descriptionId = R.string.delete_save_data_warning_description, 245 descriptionId = R.string.delete_save_data_warning_description,
246 positiveAction = { 246 positiveButtonTitleId = android.R.string.cancel,
247 negativeButtonTitleId = android.R.string.ok,
248 negativeAction = {
247 File(args.game.saveDir).deleteRecursively() 249 File(args.game.saveDir).deleteRecursively()
248 Toast.makeText( 250 Toast.makeText(
249 YuzuApplication.appContext, 251 YuzuApplication.appContext,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
index 22b084b9a..685df0d59 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -4,7 +4,6 @@
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.app.Dialog 6import android.app.Dialog
7import android.content.DialogInterface
8import android.content.Intent 7import android.content.Intent
9import android.net.Uri 8import android.net.Uri
10import android.os.Bundle 9import android.os.Bundle
@@ -16,18 +15,52 @@ import androidx.lifecycle.ViewModelProvider
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 15import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import org.yuzu.yuzu_emu.R 16import org.yuzu.yuzu_emu.R
18import org.yuzu.yuzu_emu.model.MessageDialogViewModel 17import org.yuzu.yuzu_emu.model.MessageDialogViewModel
18import org.yuzu.yuzu_emu.utils.Log
19 19
20class MessageDialogFragment : DialogFragment() { 20class MessageDialogFragment : DialogFragment() {
21 private val messageDialogViewModel: MessageDialogViewModel by activityViewModels() 21 private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
22 22
23 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 23 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
24 val titleId = requireArguments().getInt(TITLE_ID) 24 val titleId = requireArguments().getInt(TITLE_ID)
25 val titleString = requireArguments().getString(TITLE_STRING)!! 25 val title = if (titleId != 0) {
26 getString(titleId)
27 } else {
28 requireArguments().getString(TITLE_STRING)!!
29 }
30
26 val descriptionId = requireArguments().getInt(DESCRIPTION_ID) 31 val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
27 val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!! 32 val description = if (descriptionId != 0) {
33 getString(descriptionId)
34 } else {
35 requireArguments().getString(DESCRIPTION_STRING)!!
36 }
37
38 val positiveButtonId = requireArguments().getInt(POSITIVE_BUTTON_TITLE_ID)
39 val positiveButtonString = requireArguments().getString(POSITIVE_BUTTON_TITLE_STRING)!!
40 val positiveButton = if (positiveButtonId != 0) {
41 getString(positiveButtonId)
42 } else if (positiveButtonString.isNotEmpty()) {
43 positiveButtonString
44 } else if (messageDialogViewModel.positiveAction != null) {
45 getString(R.string.close)
46 } else {
47 getString(android.R.string.ok)
48 }
49
50 val negativeButtonId = requireArguments().getInt(NEGATIVE_BUTTON_TITLE_ID)
51 val negativeButtonString = requireArguments().getString(NEGATIVE_BUTTON_TITLE_STRING)!!
52 val negativeButton = if (negativeButtonId != 0) {
53 getString(negativeButtonId)
54 } else if (negativeButtonString.isNotEmpty()) {
55 negativeButtonString
56 } else {
57 getString(android.R.string.cancel)
58 }
59
28 val helpLinkId = requireArguments().getInt(HELP_LINK) 60 val helpLinkId = requireArguments().getInt(HELP_LINK)
29 val dismissible = requireArguments().getBoolean(DISMISSIBLE) 61 val dismissible = requireArguments().getBoolean(DISMISSIBLE)
30 val clearPositiveAction = requireArguments().getBoolean(CLEAR_POSITIVE_ACTION) 62 val clearPositiveAction = requireArguments().getBoolean(CLEAR_ACTIONS)
63 val showNegativeButton = requireArguments().getBoolean(SHOW_NEGATIVE_BUTTON)
31 64
32 val builder = MaterialAlertDialogBuilder(requireContext()) 65 val builder = MaterialAlertDialogBuilder(requireContext())
33 66
@@ -35,21 +68,19 @@ class MessageDialogFragment : DialogFragment() {
35 messageDialogViewModel.positiveAction = null 68 messageDialogViewModel.positiveAction = null
36 } 69 }
37 70
38 if (messageDialogViewModel.positiveAction == null) { 71 builder.setPositiveButton(positiveButton) { _, _ ->
39 builder.setPositiveButton(R.string.close, null) 72 messageDialogViewModel.positiveAction?.invoke()
40 } else { 73 }
41 builder.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> 74 if (messageDialogViewModel.negativeAction != null || showNegativeButton) {
42 messageDialogViewModel.positiveAction?.invoke() 75 builder.setNegativeButton(negativeButton) { _, _ ->
43 }.setNegativeButton(android.R.string.cancel, null) 76 messageDialogViewModel.negativeAction?.invoke()
77 }
44 } 78 }
45 79
46 if (titleId != 0) builder.setTitle(titleId) 80 if (title.isNotEmpty()) builder.setTitle(title)
47 if (titleString.isNotEmpty()) builder.setTitle(titleString) 81 if (description.isNotEmpty()) {
48 82 builder.setMessage(Html.fromHtml(description, Html.FROM_HTML_MODE_LEGACY))
49 if (descriptionId != 0) {
50 builder.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
51 } 83 }
52 if (descriptionString.isNotEmpty()) builder.setMessage(descriptionString)
53 84
54 if (helpLinkId != 0) { 85 if (helpLinkId != 0) {
55 builder.setNeutralButton(R.string.learn_more) { _, _ -> 86 builder.setNeutralButton(R.string.learn_more) { _, _ ->
@@ -76,8 +107,41 @@ class MessageDialogFragment : DialogFragment() {
76 private const val DESCRIPTION_STRING = "DescriptionString" 107 private const val DESCRIPTION_STRING = "DescriptionString"
77 private const val HELP_LINK = "Link" 108 private const val HELP_LINK = "Link"
78 private const val DISMISSIBLE = "Dismissible" 109 private const val DISMISSIBLE = "Dismissible"
79 private const val CLEAR_POSITIVE_ACTION = "ClearPositiveAction" 110 private const val CLEAR_ACTIONS = "ClearActions"
80 111 private const val POSITIVE_BUTTON_TITLE_ID = "PositiveButtonTitleId"
112 private const val POSITIVE_BUTTON_TITLE_STRING = "PositiveButtonTitleString"
113 private const val SHOW_NEGATIVE_BUTTON = "ShowNegativeButton"
114 private const val NEGATIVE_BUTTON_TITLE_ID = "NegativeButtonTitleId"
115 private const val NEGATIVE_BUTTON_TITLE_STRING = "NegativeButtonTitleString"
116
117 /**
118 * Creates a new [MessageDialogFragment] instance.
119 * @param activity Activity that will hold a [MessageDialogViewModel] instance if using
120 * [positiveAction] or [negativeAction].
121 * @param titleId String resource ID that will be used for the title. [titleString] used if 0.
122 * @param titleString String that will be used for the title. No title is set if empty.
123 * @param descriptionId String resource ID that will be used for the description.
124 * [descriptionString] used if 0.
125 * @param descriptionString String that will be used for the description.
126 * No description is set if empty.
127 * @param helpLinkId String resource ID that contains a help link. Will be added as a neutral
128 * button with the title R.string.help.
129 * @param dismissible Whether the dialog is dismissible or not. Typically used to ensure that
130 * the user clicks on one of the dialog buttons before closing.
131 * @param positiveButtonTitleId String resource ID that will be used for the positive button.
132 * [positiveButtonTitleString] used if 0.
133 * @param positiveButtonTitleString String that will be used for the positive button.
134 * android.R.string.ok used if empty. android.R.string.close will be used if [positiveAction]
135 * is not null.
136 * @param positiveAction Lambda to run when the positive button is clicked.
137 * @param showNegativeButton Normally the negative button isn't shown if there is no
138 * [negativeAction] set. This can override that behavior to always show a button.
139 * @param negativeButtonTitleId String resource ID that will be used for the negative button.
140 * [negativeButtonTitleString] used if 0.
141 * @param negativeButtonTitleString String that will be used for the negative button.
142 * android.R.string.cancel used if empty.
143 * @param negativeAction Lambda to run when the negative button is clicked
144 */
81 fun newInstance( 145 fun newInstance(
82 activity: FragmentActivity? = null, 146 activity: FragmentActivity? = null,
83 titleId: Int = 0, 147 titleId: Int = 0,
@@ -86,16 +150,27 @@ class MessageDialogFragment : DialogFragment() {
86 descriptionString: String = "", 150 descriptionString: String = "",
87 helpLinkId: Int = 0, 151 helpLinkId: Int = 0,
88 dismissible: Boolean = true, 152 dismissible: Boolean = true,
89 positiveAction: (() -> Unit)? = null 153 positiveButtonTitleId: Int = 0,
154 positiveButtonTitleString: String = "",
155 positiveAction: (() -> Unit)? = null,
156 showNegativeButton: Boolean = false,
157 negativeButtonTitleId: Int = 0,
158 negativeButtonTitleString: String = "",
159 negativeAction: (() -> Unit)? = null
90 ): MessageDialogFragment { 160 ): MessageDialogFragment {
91 var clearPositiveAction = false 161 var clearActions = false
92 if (activity != null) { 162 if (activity != null) {
93 ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { 163 ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
94 clear() 164 clear()
95 this.positiveAction = positiveAction 165 this.positiveAction = positiveAction
166 this.negativeAction = negativeAction
96 } 167 }
97 } else { 168 } else {
98 clearPositiveAction = true 169 clearActions = true
170 }
171
172 if (activity == null && (positiveAction == null || negativeAction == null)) {
173 Log.warning("[$TAG] Tried to set action with no activity!")
99 } 174 }
100 175
101 val dialog = MessageDialogFragment() 176 val dialog = MessageDialogFragment()
@@ -106,7 +181,12 @@ class MessageDialogFragment : DialogFragment() {
106 putString(DESCRIPTION_STRING, descriptionString) 181 putString(DESCRIPTION_STRING, descriptionString)
107 putInt(HELP_LINK, helpLinkId) 182 putInt(HELP_LINK, helpLinkId)
108 putBoolean(DISMISSIBLE, dismissible) 183 putBoolean(DISMISSIBLE, dismissible)
109 putBoolean(CLEAR_POSITIVE_ACTION, clearPositiveAction) 184 putBoolean(CLEAR_ACTIONS, clearActions)
185 putInt(POSITIVE_BUTTON_TITLE_ID, positiveButtonTitleId)
186 putString(POSITIVE_BUTTON_TITLE_STRING, positiveButtonTitleString)
187 putBoolean(SHOW_NEGATIVE_BUTTON, showNegativeButton)
188 putInt(NEGATIVE_BUTTON_TITLE_ID, negativeButtonTitleId)
189 putString(NEGATIVE_BUTTON_TITLE_STRING, negativeButtonTitleString)
110 } 190 }
111 dialog.arguments = bundle 191 dialog.arguments = bundle
112 return dialog 192 return dialog
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
index 641c5cb17..2db005e49 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
@@ -7,8 +7,10 @@ import androidx.lifecycle.ViewModel
7 7
8class MessageDialogViewModel : ViewModel() { 8class MessageDialogViewModel : ViewModel() {
9 var positiveAction: (() -> Unit)? = null 9 var positiveAction: (() -> Unit)? = null
10 var negativeAction: (() -> Unit)? = null
10 11
11 fun clear() { 12 fun clear() {
12 positiveAction = null 13 positiveAction = null
14 negativeAction = null
13 } 15 }
14} 16}
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 07709d4e5..80d388fe8 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -30,6 +30,7 @@ namespace Settings {
30#define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED> 30#define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED>
31#define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED> 31#define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED>
32 32
33SETTING(AppletMode, false);
33SETTING(AudioEngine, false); 34SETTING(AudioEngine, false);
34SETTING(bool, false); 35SETTING(bool, false);
35SETTING(int, false); 36SETTING(int, false);
@@ -215,6 +216,8 @@ const char* TranslateCategory(Category category) {
215 return "Debugging"; 216 return "Debugging";
216 case Category::GpuDriver: 217 case Category::GpuDriver:
217 return "GpuDriver"; 218 return "GpuDriver";
219 case Category::LibraryApplet:
220 return "LibraryApplet";
218 case Category::Miscellaneous: 221 case Category::Miscellaneous:
219 return "Miscellaneous"; 222 return "Miscellaneous";
220 case Category::Network: 223 case Category::Network:
diff --git a/src/common/settings.h b/src/common/settings.h
index f1b1add56..aa054dc24 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -133,6 +133,38 @@ struct TouchFromButtonMap {
133struct Values { 133struct Values {
134 Linkage linkage{}; 134 Linkage linkage{};
135 135
136 // Applet
137 Setting<AppletMode> cabinet_applet_mode{linkage, AppletMode::LLE, "cabinet_applet_mode",
138 Category::LibraryApplet};
139 Setting<AppletMode> controller_applet_mode{linkage, AppletMode::HLE, "controller_applet_mode",
140 Category::LibraryApplet};
141 Setting<AppletMode> data_erase_applet_mode{linkage, AppletMode::HLE, "data_erase_applet_mode",
142 Category::LibraryApplet};
143 Setting<AppletMode> error_applet_mode{linkage, AppletMode::HLE, "error_applet_mode",
144 Category::LibraryApplet};
145 Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode",
146 Category::LibraryApplet};
147 Setting<AppletMode> player_select_applet_mode{
148 linkage, AppletMode::HLE, "player_select_applet_mode", Category::LibraryApplet};
149 Setting<AppletMode> swkbd_applet_mode{linkage, AppletMode::LLE, "swkbd_applet_mode",
150 Category::LibraryApplet};
151 Setting<AppletMode> mii_edit_applet_mode{linkage, AppletMode::LLE, "mii_edit_applet_mode",
152 Category::LibraryApplet};
153 Setting<AppletMode> web_applet_mode{linkage, AppletMode::HLE, "web_applet_mode",
154 Category::LibraryApplet};
155 Setting<AppletMode> shop_applet_mode{linkage, AppletMode::HLE, "shop_applet_mode",
156 Category::LibraryApplet};
157 Setting<AppletMode> photo_viewer_applet_mode{
158 linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
159 Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
160 Category::LibraryApplet};
161 Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
162 Category::LibraryApplet};
163 Setting<AppletMode> wifi_web_auth_applet_mode{
164 linkage, AppletMode::HLE, "wifi_web_auth_applet_mode", Category::LibraryApplet};
165 Setting<AppletMode> my_page_applet_mode{linkage, AppletMode::LLE, "my_page_applet_mode",
166 Category::LibraryApplet};
167
136 // Audio 168 // Audio
137 SwitchableSetting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine", 169 SwitchableSetting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine",
138 Category::Audio, Specialization::RuntimeList}; 170 Category::Audio, Specialization::RuntimeList};
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 987489e8a..2df3f0809 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -44,6 +44,7 @@ enum class Category : u32 {
44 Services, 44 Services,
45 Paths, 45 Paths,
46 Linux, 46 Linux,
47 LibraryApplet,
47 MaxEnum, 48 MaxEnum,
48}; 49};
49 50
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 617036588..f42367e67 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -151,6 +151,8 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
151 151
152ENUM(ConsoleMode, Handheld, Docked); 152ENUM(ConsoleMode, Handheld, Docked);
153 153
154ENUM(AppletMode, HLE, LLE);
155
154template <typename Type> 156template <typename Type>
155inline std::string CanonicalizeEnum(Type id) { 157inline std::string CanonicalizeEnum(Type id) {
156 const auto group = EnumMetadata<Type>::Canonicalizations(); 158 const auto group = EnumMetadata<Type>::Canonicalizations();
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index eb8f643a2..2d5490968 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -512,10 +512,35 @@ add_library(core STATIC
512 hle/service/audio/hwopus.h 512 hle/service/audio/hwopus.h
513 hle/service/bcat/backend/backend.cpp 513 hle/service/bcat/backend/backend.cpp
514 hle/service/bcat/backend/backend.h 514 hle/service/bcat/backend/backend.h
515 hle/service/bcat/news/newly_arrived_event_holder.cpp
516 hle/service/bcat/news/newly_arrived_event_holder.h
517 hle/service/bcat/news/news_data_service.cpp
518 hle/service/bcat/news/news_data_service.h
519 hle/service/bcat/news/news_database_service.cpp
520 hle/service/bcat/news/news_database_service.h
521 hle/service/bcat/news/news_service.cpp
522 hle/service/bcat/news/news_service.h
523 hle/service/bcat/news/overwrite_event_holder.cpp
524 hle/service/bcat/news/overwrite_event_holder.h
525 hle/service/bcat/news/service_creator.cpp
526 hle/service/bcat/news/service_creator.h
515 hle/service/bcat/bcat.cpp 527 hle/service/bcat/bcat.cpp
516 hle/service/bcat/bcat.h 528 hle/service/bcat/bcat.h
517 hle/service/bcat/bcat_module.cpp 529 hle/service/bcat/bcat_result.h
518 hle/service/bcat/bcat_module.h 530 hle/service/bcat/bcat_service.cpp
531 hle/service/bcat/bcat_service.h
532 hle/service/bcat/bcat_types.h
533 hle/service/bcat/bcat_util.h
534 hle/service/bcat/delivery_cache_directory_service.cpp
535 hle/service/bcat/delivery_cache_directory_service.h
536 hle/service/bcat/delivery_cache_file_service.cpp
537 hle/service/bcat/delivery_cache_file_service.h
538 hle/service/bcat/delivery_cache_progress_service.cpp
539 hle/service/bcat/delivery_cache_progress_service.h
540 hle/service/bcat/delivery_cache_storage_service.cpp
541 hle/service/bcat/delivery_cache_storage_service.h
542 hle/service/bcat/service_creator.cpp
543 hle/service/bcat/service_creator.h
519 hle/service/bpc/bpc.cpp 544 hle/service/bpc/bpc.cpp
520 hle/service/bpc/bpc.h 545 hle/service/bpc/bpc.h
521 hle/service/btdrv/btdrv.cpp 546 hle/service/btdrv/btdrv.cpp
@@ -548,8 +573,6 @@ add_library(core STATIC
548 hle/service/es/es.h 573 hle/service/es/es.h
549 hle/service/eupld/eupld.cpp 574 hle/service/eupld/eupld.cpp
550 hle/service/eupld/eupld.h 575 hle/service/eupld/eupld.h
551 hle/service/event.cpp
552 hle/service/event.h
553 hle/service/fatal/fatal.cpp 576 hle/service/fatal/fatal.cpp
554 hle/service/fatal/fatal.h 577 hle/service/fatal/fatal.h
555 hle/service/fatal/fatal_p.cpp 578 hle/service/fatal/fatal_p.cpp
@@ -676,8 +699,6 @@ add_library(core STATIC
676 hle/service/mm/mm_u.h 699 hle/service/mm/mm_u.h
677 hle/service/mnpp/mnpp_app.cpp 700 hle/service/mnpp/mnpp_app.cpp
678 hle/service/mnpp/mnpp_app.h 701 hle/service/mnpp/mnpp_app.h
679 hle/service/mutex.cpp
680 hle/service/mutex.h
681 hle/service/ncm/ncm.cpp 702 hle/service/ncm/ncm.cpp
682 hle/service/ncm/ncm.h 703 hle/service/ncm/ncm.h
683 hle/service/nfc/common/amiibo_crypto.cpp 704 hle/service/nfc/common/amiibo_crypto.cpp
@@ -790,6 +811,15 @@ add_library(core STATIC
790 hle/service/nvnflinger/window.h 811 hle/service/nvnflinger/window.h
791 hle/service/olsc/olsc.cpp 812 hle/service/olsc/olsc.cpp
792 hle/service/olsc/olsc.h 813 hle/service/olsc/olsc.h
814 hle/service/os/event.cpp
815 hle/service/os/event.h
816 hle/service/os/multi_wait_holder.cpp
817 hle/service/os/multi_wait_holder.h
818 hle/service/os/multi_wait_utils.h
819 hle/service/os/multi_wait.cpp
820 hle/service/os/multi_wait.h
821 hle/service/os/mutex.cpp
822 hle/service/os/mutex.h
793 hle/service/pcie/pcie.cpp 823 hle/service/pcie/pcie.cpp
794 hle/service/pcie/pcie.h 824 hle/service/pcie/pcie.h
795 hle/service/pctl/pctl.cpp 825 hle/service/pctl/pctl.cpp
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 285fe4db6..665252358 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -172,6 +172,10 @@ u32 NCA::GetSDKVersion() const {
172 return reader->GetSdkAddonVersion(); 172 return reader->GetSdkAddonVersion();
173} 173}
174 174
175u8 NCA::GetKeyGeneration() const {
176 return reader->GetKeyGeneration();
177}
178
175bool NCA::IsUpdate() const { 179bool NCA::IsUpdate() const {
176 return is_update; 180 return is_update;
177} 181}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index f68464eb0..8560617f5 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -77,6 +77,7 @@ public:
77 u64 GetTitleId() const; 77 u64 GetTitleId() const;
78 RightsId GetRightsId() const; 78 RightsId GetRightsId() const;
79 u32 GetSDKVersion() const; 79 u32 GetSDKVersion() const;
80 u8 GetKeyGeneration() const;
80 bool IsUpdate() const; 81 bool IsUpdate() const;
81 82
82 VirtualFile GetRomFS() const; 83 VirtualFile GetRomFS() const;
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index d4e0eb6f4..b22767bf5 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -91,6 +91,7 @@ constexpr Result ResultWriteNotPermitted{ErrorModule::FS, 6203};
91constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; 91constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
92constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; 92constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
93constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; 93constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
94constexpr Result ResultPermissionDenied{ErrorModule::FS, 6400};
94constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705}; 95constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
95 96
96} // namespace FileSys 97} // namespace FileSys
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 0b08e877e..1bcc42890 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -4,8 +4,9 @@
4#include <random> 4#include <random>
5#include "common/scope_exit.h" 5#include "common/scope_exit.h"
6#include "common/settings.h" 6#include "common/settings.h"
7#include "core/arm/dynarmic/arm_dynarmic.h"
8#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
7#include "core/core.h" 9#include "core/core.h"
8#include "core/gpu_dirty_memory_manager.h"
9#include "core/hle/kernel/k_process.h" 10#include "core/hle/kernel/k_process.h"
10#include "core/hle/kernel/k_scoped_resource_reservation.h" 11#include "core/hle/kernel/k_scoped_resource_reservation.h"
11#include "core/hle/kernel/k_shared_memory.h" 12#include "core/hle/kernel/k_shared_memory.h"
@@ -1258,6 +1259,10 @@ void KProcess::InitializeInterfaces() {
1258 1259
1259#ifdef HAS_NCE 1260#ifdef HAS_NCE
1260 if (this->IsApplication() && Settings::IsNceEnabled()) { 1261 if (this->IsApplication() && Settings::IsNceEnabled()) {
1262 // Register the scoped JIT handler before creating any NCE instances
1263 // so that its signal handler will appear first in the signal chain.
1264 Core::ScopedJitExecution::RegisterHandler();
1265
1261 for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { 1266 for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
1262 m_arm_interfaces[i] = std::make_unique<Core::ArmNce>(m_kernel.System(), true, i); 1267 m_arm_interfaces[i] = std::make_unique<Core::ArmNce>(m_kernel.System(), true, i);
1263 } 1268 }
diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h
index a2b852b12..8c33feb15 100644
--- a/src/core/hle/service/am/am_types.h
+++ b/src/core/hle/service/am/am_types.h
@@ -130,9 +130,9 @@ enum class AppletProgramId : u64 {
130 130
131enum class LibraryAppletMode : u32 { 131enum class LibraryAppletMode : u32 {
132 AllForeground = 0, 132 AllForeground = 0,
133 Background = 1, 133 PartialForeground = 1,
134 NoUI = 2, 134 NoUi = 2,
135 BackgroundIndirectDisplay = 3, 135 PartialForegroundIndirectDisplay = 3,
136 AllForegroundInitiallyHidden = 4, 136 AllForegroundInitiallyHidden = 4,
137}; 137};
138 138
diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h
index bce6f9050..b29ecdfed 100644
--- a/src/core/hle/service/am/applet.h
+++ b/src/core/hle/service/am/applet.h
@@ -9,8 +9,8 @@
9#include "common/math_util.h" 9#include "common/math_util.h"
10#include "core/hle/service/apm/apm_controller.h" 10#include "core/hle/service/apm/apm_controller.h"
11#include "core/hle/service/caps/caps_types.h" 11#include "core/hle/service/caps/caps_types.h"
12#include "core/hle/service/event.h"
13#include "core/hle/service/kernel_helpers.h" 12#include "core/hle/service/kernel_helpers.h"
13#include "core/hle/service/os/event.h"
14#include "core/hle/service/service.h" 14#include "core/hle/service/service.h"
15 15
16#include "core/hle/service/am/am_types.h" 16#include "core/hle/service/am/am_types.h"
diff --git a/src/core/hle/service/am/applet_data_broker.h b/src/core/hle/service/am/applet_data_broker.h
index 12326fd04..5a1d43c11 100644
--- a/src/core/hle/service/am/applet_data_broker.h
+++ b/src/core/hle/service/am/applet_data_broker.h
@@ -7,8 +7,8 @@
7#include <memory> 7#include <memory>
8#include <mutex> 8#include <mutex>
9 9
10#include "core/hle/service/event.h"
11#include "core/hle/service/kernel_helpers.h" 10#include "core/hle/service/kernel_helpers.h"
11#include "core/hle/service/os/event.h"
12 12
13union Result; 13union Result;
14 14
diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard.cpp b/src/core/hle/service/am/frontend/applet_software_keyboard.cpp
index fbf75d379..034c62f32 100644
--- a/src/core/hle/service/am/frontend/applet_software_keyboard.cpp
+++ b/src/core/hle/service/am/frontend/applet_software_keyboard.cpp
@@ -68,9 +68,9 @@ void SoftwareKeyboard::Initialize() {
68 case LibraryAppletMode::AllForeground: 68 case LibraryAppletMode::AllForeground:
69 InitializeForeground(); 69 InitializeForeground();
70 break; 70 break;
71 case LibraryAppletMode::Background: 71 case LibraryAppletMode::PartialForeground:
72 case LibraryAppletMode::BackgroundIndirectDisplay: 72 case LibraryAppletMode::PartialForegroundIndirectDisplay:
73 InitializeBackground(applet_mode); 73 InitializePartialForeground(applet_mode);
74 break; 74 break;
75 default: 75 default:
76 ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode); 76 ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode);
@@ -243,7 +243,7 @@ void SoftwareKeyboard::InitializeForeground() {
243 InitializeFrontendNormalKeyboard(); 243 InitializeFrontendNormalKeyboard();
244} 244}
245 245
246void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) { 246void SoftwareKeyboard::InitializePartialForeground(LibraryAppletMode library_applet_mode) {
247 LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet."); 247 LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet.");
248 248
249 is_background = true; 249 is_background = true;
@@ -258,9 +258,9 @@ void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mod
258 swkbd_inline_initialize_arg.size()); 258 swkbd_inline_initialize_arg.size());
259 259
260 if (swkbd_initialize_arg.library_applet_mode_flag) { 260 if (swkbd_initialize_arg.library_applet_mode_flag) {
261 ASSERT(library_applet_mode == LibraryAppletMode::Background); 261 ASSERT(library_applet_mode == LibraryAppletMode::PartialForeground);
262 } else { 262 } else {
263 ASSERT(library_applet_mode == LibraryAppletMode::BackgroundIndirectDisplay); 263 ASSERT(library_applet_mode == LibraryAppletMode::PartialForegroundIndirectDisplay);
264 } 264 }
265} 265}
266 266
diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard.h b/src/core/hle/service/am/frontend/applet_software_keyboard.h
index f464b7e15..2a7d01b96 100644
--- a/src/core/hle/service/am/frontend/applet_software_keyboard.h
+++ b/src/core/hle/service/am/frontend/applet_software_keyboard.h
@@ -62,7 +62,7 @@ private:
62 void InitializeForeground(); 62 void InitializeForeground();
63 63
64 /// Initializes the inline software keyboard. 64 /// Initializes the inline software keyboard.
65 void InitializeBackground(LibraryAppletMode library_applet_mode); 65 void InitializePartialForeground(LibraryAppletMode library_applet_mode);
66 66
67 /// Processes the text check sent by the application. 67 /// Processes the text check sent by the application.
68 void ProcessTextCheck(); 68 void ProcessTextCheck();
diff --git a/src/core/hle/service/am/library_applet_creator.cpp b/src/core/hle/service/am/library_applet_creator.cpp
index 47bab7528..00d5a0705 100644
--- a/src/core/hle/service/am/library_applet_creator.cpp
+++ b/src/core/hle/service/am/library_applet_creator.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/settings.h"
4#include "core/hle/kernel/k_transfer_memory.h" 5#include "core/hle/kernel/k_transfer_memory.h"
5#include "core/hle/service/am/applet_data_broker.h" 6#include "core/hle/service/am/applet_data_broker.h"
6#include "core/hle/service/am/applet_manager.h" 7#include "core/hle/service/am/applet_manager.h"
@@ -16,6 +17,34 @@ namespace Service::AM {
16 17
17namespace { 18namespace {
18 19
20bool ShouldCreateGuestApplet(AppletId applet_id) {
21#define X(Name, name) \
22 if (applet_id == AppletId::Name && \
23 Settings::values.name##_applet_mode.GetValue() != Settings::AppletMode::LLE) { \
24 return false; \
25 }
26
27 X(Cabinet, cabinet)
28 X(Controller, controller)
29 X(DataErase, data_erase)
30 X(Error, error)
31 X(NetConnect, net_connect)
32 X(ProfileSelect, player_select)
33 X(SoftwareKeyboard, swkbd)
34 X(MiiEdit, mii_edit)
35 X(Web, web)
36 X(Shop, shop)
37 X(PhotoViewer, photo_viewer)
38 X(OfflineWeb, offline_web)
39 X(LoginShare, login_share)
40 X(WebAuth, wifi_web_auth)
41 X(MyPage, my_page)
42
43#undef X
44
45 return true;
46}
47
19AppletProgramId AppletIdToProgramId(AppletId applet_id) { 48AppletProgramId AppletIdToProgramId(AppletId applet_id) {
20 switch (applet_id) { 49 switch (applet_id) {
21 case AppletId::OverlayDisplay: 50 case AppletId::OverlayDisplay:
@@ -63,17 +92,26 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
63 } 92 }
64} 93}
65 94
66[[maybe_unused]] std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet( 95std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
67 Core::System& system, std::shared_ptr<Applet> caller_applet, AppletId applet_id, 96 std::shared_ptr<Applet> caller_applet,
68 LibraryAppletMode mode) { 97 AppletId applet_id,
98 LibraryAppletMode mode) {
69 const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id)); 99 const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id));
70 if (program_id == 0) { 100 if (program_id == 0) {
71 // Unknown applet 101 // Unknown applet
72 return {}; 102 return {};
73 } 103 }
74 104
105 // TODO: enable other versions of applets
106 enum : u8 {
107 Firmware1400 = 14,
108 Firmware1500 = 15,
109 Firmware1600 = 16,
110 Firmware1700 = 17,
111 };
112
75 auto process = std::make_unique<Process>(system); 113 auto process = std::make_unique<Process>(system);
76 if (!process->Initialize(program_id)) { 114 if (!process->Initialize(program_id, Firmware1400, Firmware1700)) {
77 // Couldn't initialize the guest process 115 // Couldn't initialize the guest process
78 return {}; 116 return {};
79 } 117 }
@@ -87,24 +125,18 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
87 // Set focus state 125 // Set focus state
88 switch (mode) { 126 switch (mode) {
89 case LibraryAppletMode::AllForeground: 127 case LibraryAppletMode::AllForeground:
90 case LibraryAppletMode::NoUI: 128 case LibraryAppletMode::NoUi:
91 applet->focus_state = FocusState::InFocus; 129 case LibraryAppletMode::PartialForeground:
130 case LibraryAppletMode::PartialForegroundIndirectDisplay:
92 applet->hid_registration.EnableAppletToGetInput(true); 131 applet->hid_registration.EnableAppletToGetInput(true);
132 applet->focus_state = FocusState::InFocus;
93 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground); 133 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
94 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
95 break; 134 break;
96 case LibraryAppletMode::AllForegroundInitiallyHidden: 135 case LibraryAppletMode::AllForegroundInitiallyHidden:
97 applet->system_buffer_manager.SetWindowVisibility(false);
98 applet->focus_state = FocusState::NotInFocus;
99 applet->hid_registration.EnableAppletToGetInput(false); 136 applet->hid_registration.EnableAppletToGetInput(false);
100 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); 137 applet->focus_state = FocusState::NotInFocus;
101 break; 138 applet->system_buffer_manager.SetWindowVisibility(false);
102 case LibraryAppletMode::Background: 139 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
103 case LibraryAppletMode::BackgroundIndirectDisplay:
104 default:
105 applet->focus_state = FocusState::Background;
106 applet->hid_registration.EnableAppletToGetInput(true);
107 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
108 break; 140 break;
109 } 141 }
110 142
@@ -117,9 +149,10 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
117 return std::make_shared<ILibraryAppletAccessor>(system, broker, applet); 149 return std::make_shared<ILibraryAppletAccessor>(system, broker, applet);
118} 150}
119 151
120[[maybe_unused]] std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet( 152std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& system,
121 Core::System& system, std::shared_ptr<Applet> caller_applet, AppletId applet_id, 153 std::shared_ptr<Applet> caller_applet,
122 LibraryAppletMode mode) { 154 AppletId applet_id,
155 LibraryAppletMode mode) {
123 const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id)); 156 const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id));
124 157
125 auto process = std::make_unique<Process>(system); 158 auto process = std::make_unique<Process>(system);
@@ -163,7 +196,13 @@ void ILibraryAppletCreator::CreateLibraryApplet(HLERequestContext& ctx) {
163 LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id, 196 LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id,
164 applet_mode); 197 applet_mode);
165 198
166 auto library_applet = CreateFrontendApplet(system, applet, applet_id, applet_mode); 199 std::shared_ptr<ILibraryAppletAccessor> library_applet;
200 if (ShouldCreateGuestApplet(applet_id)) {
201 library_applet = CreateGuestApplet(system, applet, applet_id, applet_mode);
202 }
203 if (!library_applet) {
204 library_applet = CreateFrontendApplet(system, applet, applet_id, applet_mode);
205 }
167 if (!library_applet) { 206 if (!library_applet) {
168 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); 207 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
169 208
diff --git a/src/core/hle/service/am/process.cpp b/src/core/hle/service/am/process.cpp
index 16b685f86..992c50713 100644
--- a/src/core/hle/service/am/process.cpp
+++ b/src/core/hle/service/am/process.cpp
@@ -3,6 +3,7 @@
3 3
4#include "common/scope_exit.h" 4#include "common/scope_exit.h"
5 5
6#include "core/file_sys/content_archive.h"
6#include "core/file_sys/nca_metadata.h" 7#include "core/file_sys/nca_metadata.h"
7#include "core/file_sys/registered_cache.h" 8#include "core/file_sys/registered_cache.h"
8#include "core/hle/kernel/k_process.h" 9#include "core/hle/kernel/k_process.h"
@@ -20,7 +21,7 @@ Process::~Process() {
20 this->Finalize(); 21 this->Finalize();
21} 22}
22 23
23bool Process::Initialize(u64 program_id) { 24bool Process::Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) {
24 // First, ensure we are not holding another process. 25 // First, ensure we are not holding another process.
25 this->Finalize(); 26 this->Finalize();
26 27
@@ -29,21 +30,33 @@ bool Process::Initialize(u64 program_id) {
29 30
30 // Attempt to load program NCA. 31 // Attempt to load program NCA.
31 const FileSys::RegisteredCache* bis_system{}; 32 const FileSys::RegisteredCache* bis_system{};
32 FileSys::VirtualFile nca{}; 33 FileSys::VirtualFile nca_raw{};
33 34
34 // Get the program NCA from built-in storage. 35 // Get the program NCA from built-in storage.
35 bis_system = fsc.GetSystemNANDContents(); 36 bis_system = fsc.GetSystemNANDContents();
36 if (bis_system) { 37 if (bis_system) {
37 nca = bis_system->GetEntryRaw(program_id, FileSys::ContentRecordType::Program); 38 nca_raw = bis_system->GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
38 } 39 }
39 40
40 // Ensure we retrieved a program NCA. 41 // Ensure we retrieved a program NCA.
41 if (!nca) { 42 if (!nca_raw) {
42 return false; 43 return false;
43 } 44 }
44 45
46 // Ensure we have a suitable version.
47 if (minimum_key_generation > 0) {
48 FileSys::NCA nca(nca_raw);
49 if (nca.GetStatus() == Loader::ResultStatus::Success &&
50 (nca.GetKeyGeneration() < minimum_key_generation ||
51 nca.GetKeyGeneration() > maximum_key_generation)) {
52 LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
53 nca.GetKeyGeneration());
54 return false;
55 }
56 }
57
45 // Get the appropriate loader to parse this NCA. 58 // Get the appropriate loader to parse this NCA.
46 auto app_loader = Loader::GetLoader(m_system, nca, program_id, 0); 59 auto app_loader = Loader::GetLoader(m_system, nca_raw, program_id, 0);
47 60
48 // Ensure we have a loader which can parse the NCA. 61 // Ensure we have a loader which can parse the NCA.
49 if (!app_loader) { 62 if (!app_loader) {
diff --git a/src/core/hle/service/am/process.h b/src/core/hle/service/am/process.h
index 4b908ade4..4b8102fb6 100644
--- a/src/core/hle/service/am/process.h
+++ b/src/core/hle/service/am/process.h
@@ -21,7 +21,7 @@ public:
21 explicit Process(Core::System& system); 21 explicit Process(Core::System& system);
22 ~Process(); 22 ~Process();
23 23
24 bool Initialize(u64 program_id); 24 bool Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation);
25 void Finalize(); 25 void Finalize();
26 26
27 bool Run(); 27 bool Run();
diff --git a/src/core/hle/service/am/self_controller.cpp b/src/core/hle/service/am/self_controller.cpp
index 0289f5cf1..65e249c0c 100644
--- a/src/core/hle/service/am/self_controller.cpp
+++ b/src/core/hle/service/am/self_controller.cpp
@@ -1,10 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h"
5#include "core/hle/result.h"
4#include "core/hle/service/am/am_results.h" 6#include "core/hle/service/am/am_results.h"
5#include "core/hle/service/am/frontend/applets.h" 7#include "core/hle/service/am/frontend/applets.h"
6#include "core/hle/service/am/self_controller.h" 8#include "core/hle/service/am/self_controller.h"
7#include "core/hle/service/caps/caps_su.h" 9#include "core/hle/service/caps/caps_su.h"
10#include "core/hle/service/hle_ipc.h"
8#include "core/hle/service/ipc_helpers.h" 11#include "core/hle/service/ipc_helpers.h"
9#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" 12#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
10#include "core/hle/service/nvnflinger/nvnflinger.h" 13#include "core/hle/service/nvnflinger/nvnflinger.h"
@@ -47,7 +50,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
47 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, 50 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
48 {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"}, 51 {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
49 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, 52 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
50 {61, nullptr, "SetMediaPlaybackState"}, 53 {61, &ISelfController::SetMediaPlaybackState, "SetMediaPlaybackState"},
51 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, 54 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
52 {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, 55 {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
53 {64, nullptr, "SetInputDetectionSourceSet"}, 56 {64, nullptr, "SetInputDetectionSourceSet"},
@@ -288,7 +291,8 @@ void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
288} 291}
289 292
290Result ISelfController::EnsureBufferSharingEnabled(Kernel::KProcess* process) { 293Result ISelfController::EnsureBufferSharingEnabled(Kernel::KProcess* process) {
291 if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id)) { 294 if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id,
295 applet->library_applet_mode)) {
292 return ResultSuccess; 296 return ResultSuccess;
293 } 297 }
294 298
@@ -323,6 +327,16 @@ void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
323 rb.Push(ResultSuccess); 327 rb.Push(ResultSuccess);
324} 328}
325 329
330void ISelfController::SetMediaPlaybackState(HLERequestContext& ctx) {
331 IPC::RequestParser rp{ctx};
332 const u8 state = rp.Pop<u8>();
333
334 LOG_WARNING(Service_AM, "(STUBBED) called, state={}", state);
335
336 IPC::ResponseBuilder rb{ctx, 2};
337 rb.Push(ResultSuccess);
338}
339
326void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) { 340void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
327 IPC::RequestParser rp{ctx}; 341 IPC::RequestParser rp{ctx};
328 342
diff --git a/src/core/hle/service/am/self_controller.h b/src/core/hle/service/am/self_controller.h
index a63bc2e74..ab21a1881 100644
--- a/src/core/hle/service/am/self_controller.h
+++ b/src/core/hle/service/am/self_controller.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/service/hle_ipc.h"
6#include "core/hle/service/kernel_helpers.h" 7#include "core/hle/service/kernel_helpers.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8 9
@@ -38,6 +39,7 @@ private:
38 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx); 39 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
39 void SetHandlesRequestToDisplay(HLERequestContext& ctx); 40 void SetHandlesRequestToDisplay(HLERequestContext& ctx);
40 void ApproveToDisplay(HLERequestContext& ctx); 41 void ApproveToDisplay(HLERequestContext& ctx);
42 void SetMediaPlaybackState(HLERequestContext& ctx);
41 void SetIdleTimeDetectionExtension(HLERequestContext& ctx); 43 void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
42 void GetIdleTimeDetectionExtension(HLERequestContext& ctx); 44 void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
43 void ReportUserIsActive(HLERequestContext& ctx); 45 void ReportUserIsActive(HLERequestContext& ctx);
diff --git a/src/core/hle/service/am/system_buffer_manager.cpp b/src/core/hle/service/am/system_buffer_manager.cpp
index 60a9afc9d..48923fe41 100644
--- a/src/core/hle/service/am/system_buffer_manager.cpp
+++ b/src/core/hle/service/am/system_buffer_manager.cpp
@@ -17,11 +17,12 @@ SystemBufferManager::~SystemBufferManager() {
17 17
18 // Clean up shared layers. 18 // Clean up shared layers.
19 if (m_buffer_sharing_enabled) { 19 if (m_buffer_sharing_enabled) {
20 m_nvnflinger->GetSystemBufferManager().Finalize(m_process);
20 } 21 }
21} 22}
22 23
23bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process, 24bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process,
24 AppletId applet_id) { 25 AppletId applet_id, LibraryAppletMode mode) {
25 if (m_nvnflinger) { 26 if (m_nvnflinger) {
26 return m_buffer_sharing_enabled; 27 return m_buffer_sharing_enabled;
27 } 28 }
@@ -36,9 +37,15 @@ bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel:
36 return false; 37 return false;
37 } 38 }
38 39
40 Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
41 if (mode == LibraryAppletMode::PartialForeground ||
42 mode == LibraryAppletMode::PartialForegroundIndirectDisplay) {
43 blending = Nvnflinger::LayerBlending::Coverage;
44 }
45
39 const auto display_id = m_nvnflinger->OpenDisplay("Default").value(); 46 const auto display_id = m_nvnflinger->OpenDisplay("Default").value();
40 const auto res = m_nvnflinger->GetSystemBufferManager().Initialize( 47 const auto res = m_nvnflinger->GetSystemBufferManager().Initialize(
41 &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id); 48 m_process, &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id, blending);
42 49
43 if (res.IsSuccess()) { 50 if (res.IsSuccess()) {
44 m_buffer_sharing_enabled = true; 51 m_buffer_sharing_enabled = true;
@@ -62,8 +69,12 @@ void SystemBufferManager::SetWindowVisibility(bool visible) {
62 69
63Result SystemBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, 70Result SystemBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
64 s32* out_fbshare_layer_index) { 71 s32* out_fbshare_layer_index) {
65 // TODO 72 if (!m_buffer_sharing_enabled) {
66 R_SUCCEED(); 73 return VI::ResultPermissionDenied;
74 }
75
76 return m_nvnflinger->GetSystemBufferManager().WriteAppletCaptureBuffer(out_was_written,
77 out_fbshare_layer_index);
67} 78}
68 79
69} // namespace Service::AM 80} // namespace Service::AM
diff --git a/src/core/hle/service/am/system_buffer_manager.h b/src/core/hle/service/am/system_buffer_manager.h
index 98c3cf055..0690f68b6 100644
--- a/src/core/hle/service/am/system_buffer_manager.h
+++ b/src/core/hle/service/am/system_buffer_manager.h
@@ -27,7 +27,8 @@ public:
27 SystemBufferManager(); 27 SystemBufferManager();
28 ~SystemBufferManager(); 28 ~SystemBufferManager();
29 29
30 bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id); 30 bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id,
31 LibraryAppletMode mode);
31 32
32 void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id, 33 void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
33 u64* out_system_shared_layer_id) { 34 u64* out_system_shared_layer_id) {
diff --git a/src/core/hle/service/am/window_controller.cpp b/src/core/hle/service/am/window_controller.cpp
index f00957f83..c07ef228b 100644
--- a/src/core/hle/service/am/window_controller.cpp
+++ b/src/core/hle/service/am/window_controller.cpp
@@ -62,12 +62,12 @@ void IWindowController::SetAppletWindowVisibility(HLERequestContext& ctx) {
62 applet->hid_registration.EnableAppletToGetInput(visible); 62 applet->hid_registration.EnableAppletToGetInput(visible);
63 63
64 if (visible) { 64 if (visible) {
65 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
66 applet->focus_state = FocusState::InFocus; 65 applet->focus_state = FocusState::InFocus;
66 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
67 } else { 67 } else {
68 applet->focus_state = FocusState::NotInFocus; 68 applet->focus_state = FocusState::NotInFocus;
69 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
69 } 70 }
70 applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
71 71
72 IPC::ResponseBuilder rb{ctx, 2}; 72 IPC::ResponseBuilder rb{ctx, 2};
73 rb.Push(ResultSuccess); 73 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index 847f76987..1993493a9 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -33,18 +33,18 @@ void ProgressServiceBackend::SetTotalSize(u64 size) {
33} 33}
34 34
35void ProgressServiceBackend::StartConnecting() { 35void ProgressServiceBackend::StartConnecting() {
36 impl.status = DeliveryCacheProgressImpl::Status::Connecting; 36 impl.status = DeliveryCacheProgressStatus::Connecting;
37 SignalUpdate(); 37 SignalUpdate();
38} 38}
39 39
40void ProgressServiceBackend::StartProcessingDataList() { 40void ProgressServiceBackend::StartProcessingDataList() {
41 impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList; 41 impl.status = DeliveryCacheProgressStatus::ProcessingDataList;
42 SignalUpdate(); 42 SignalUpdate();
43} 43}
44 44
45void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name, 45void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
46 std::string_view file_name, u64 file_size) { 46 std::string_view file_name, u64 file_size) {
47 impl.status = DeliveryCacheProgressImpl::Status::Downloading; 47 impl.status = DeliveryCacheProgressStatus::Downloading;
48 impl.current_downloaded_bytes = 0; 48 impl.current_downloaded_bytes = 0;
49 impl.current_total_bytes = file_size; 49 impl.current_total_bytes = file_size;
50 std::memcpy(impl.current_directory.data(), dir_name.data(), 50 std::memcpy(impl.current_directory.data(), dir_name.data(),
@@ -65,7 +65,7 @@ void ProgressServiceBackend::FinishDownloadingFile() {
65} 65}
66 66
67void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) { 67void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
68 impl.status = DeliveryCacheProgressImpl::Status::Committing; 68 impl.status = DeliveryCacheProgressStatus::Committing;
69 impl.current_file.fill(0); 69 impl.current_file.fill(0);
70 impl.current_downloaded_bytes = 0; 70 impl.current_downloaded_bytes = 0;
71 impl.current_total_bytes = 0; 71 impl.current_total_bytes = 0;
@@ -76,7 +76,7 @@ void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
76 76
77void ProgressServiceBackend::FinishDownload(Result result) { 77void ProgressServiceBackend::FinishDownload(Result result) {
78 impl.total_downloaded_bytes = impl.total_bytes; 78 impl.total_downloaded_bytes = impl.total_bytes;
79 impl.status = DeliveryCacheProgressImpl::Status::Done; 79 impl.status = DeliveryCacheProgressStatus::Done;
80 impl.result = result; 80 impl.result = result;
81 SignalUpdate(); 81 SignalUpdate();
82} 82}
@@ -85,15 +85,15 @@ void ProgressServiceBackend::SignalUpdate() {
85 update_event->Signal(); 85 update_event->Signal();
86} 86}
87 87
88Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {} 88BcatBackend::BcatBackend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
89 89
90Backend::~Backend() = default; 90BcatBackend::~BcatBackend() = default;
91 91
92NullBackend::NullBackend(DirectoryGetter getter) : Backend(std::move(getter)) {} 92NullBcatBackend::NullBcatBackend(DirectoryGetter getter) : BcatBackend(std::move(getter)) {}
93 93
94NullBackend::~NullBackend() = default; 94NullBcatBackend::~NullBcatBackend() = default;
95 95
96bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { 96bool NullBcatBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
97 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, 97 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
98 title.build_id); 98 title.build_id);
99 99
@@ -101,8 +101,8 @@ bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& prog
101 return true; 101 return true;
102} 102}
103 103
104bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name, 104bool NullBcatBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
105 ProgressServiceBackend& progress) { 105 ProgressServiceBackend& progress) {
106 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id, 106 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
107 title.build_id, name); 107 title.build_id, name);
108 108
@@ -110,18 +110,18 @@ bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
110 return true; 110 return true;
111} 111}
112 112
113bool NullBackend::Clear(u64 title_id) { 113bool NullBcatBackend::Clear(u64 title_id) {
114 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); 114 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
115 115
116 return true; 116 return true;
117} 117}
118 118
119void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) { 119void NullBcatBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
120 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, 120 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
121 Common::HexToString(passphrase)); 121 Common::HexToString(passphrase));
122} 122}
123 123
124std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) { 124std::optional<std::vector<u8>> NullBcatBackend::GetLaunchParameter(TitleIDVersion title) {
125 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, 125 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
126 title.build_id); 126 title.build_id);
127 return std::nullopt; 127 return std::nullopt;
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index aa36d29d5..3680f6c9c 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -10,6 +10,7 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/file_sys/vfs/vfs_types.h" 11#include "core/file_sys/vfs/vfs_types.h"
12#include "core/hle/result.h" 12#include "core/hle/result.h"
13#include "core/hle/service/bcat/bcat_types.h"
13#include "core/hle/service/kernel_helpers.h" 14#include "core/hle/service/kernel_helpers.h"
14 15
15namespace Core { 16namespace Core {
@@ -24,44 +25,6 @@ class KReadableEvent;
24 25
25namespace Service::BCAT { 26namespace Service::BCAT {
26 27
27struct DeliveryCacheProgressImpl;
28
29using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
30using Passphrase = std::array<u8, 0x20>;
31
32struct TitleIDVersion {
33 u64 title_id;
34 u64 build_id;
35};
36
37using DirectoryName = std::array<char, 0x20>;
38using FileName = std::array<char, 0x20>;
39
40struct DeliveryCacheProgressImpl {
41 enum class Status : s32 {
42 None = 0x0,
43 Queued = 0x1,
44 Connecting = 0x2,
45 ProcessingDataList = 0x3,
46 Downloading = 0x4,
47 Committing = 0x5,
48 Done = 0x9,
49 };
50
51 Status status;
52 Result result = ResultSuccess;
53 DirectoryName current_directory;
54 FileName current_file;
55 s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
56 s64 current_total_bytes; ///< Bytes total on current file.
57 s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
58 s64 total_bytes; ///< Bytes total on overall download.
59 INSERT_PADDING_BYTES(
60 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
61};
62static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
63 "DeliveryCacheProgressImpl has incorrect size.");
64
65// A class to manage the signalling to the game about BCAT download progress. 28// A class to manage the signalling to the game about BCAT download progress.
66// Some of this class is implemented in module.cpp to avoid exposing the implementation structure. 29// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
67class ProgressServiceBackend { 30class ProgressServiceBackend {
@@ -107,10 +70,10 @@ private:
107}; 70};
108 71
109// A class representing an abstract backend for BCAT functionality. 72// A class representing an abstract backend for BCAT functionality.
110class Backend { 73class BcatBackend {
111public: 74public:
112 explicit Backend(DirectoryGetter getter); 75 explicit BcatBackend(DirectoryGetter getter);
113 virtual ~Backend(); 76 virtual ~BcatBackend();
114 77
115 // Called when the backend is needed to synchronize the data for the game with title ID and 78 // Called when the backend is needed to synchronize the data for the game with title ID and
116 // version in title. A ProgressServiceBackend object is provided to alert the application of 79 // version in title. A ProgressServiceBackend object is provided to alert the application of
@@ -135,10 +98,10 @@ protected:
135}; 98};
136 99
137// A backend of BCAT that provides no operation. 100// A backend of BCAT that provides no operation.
138class NullBackend : public Backend { 101class NullBcatBackend : public BcatBackend {
139public: 102public:
140 explicit NullBackend(DirectoryGetter getter); 103 explicit NullBcatBackend(DirectoryGetter getter);
141 ~NullBackend() override; 104 ~NullBcatBackend() override;
142 105
143 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override; 106 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
144 bool SynchronizeDirectory(TitleIDVersion title, std::string name, 107 bool SynchronizeDirectory(TitleIDVersion title, std::string name,
@@ -151,6 +114,7 @@ public:
151 std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; 114 std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
152}; 115};
153 116
154std::unique_ptr<Backend> CreateBackendFromSettings(Core::System& system, DirectoryGetter getter); 117std::unique_ptr<BcatBackend> CreateBackendFromSettings(Core::System& system,
118 DirectoryGetter getter);
155 119
156} // namespace Service::BCAT 120} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index d0ac17324..ea8b15998 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -1,24 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/hle/service/bcat/backend/backend.h"
4#include "core/hle/service/bcat/bcat.h" 5#include "core/hle/service/bcat/bcat.h"
6#include "core/hle/service/bcat/news/service_creator.h"
7#include "core/hle/service/bcat/service_creator.h"
8#include "core/hle/service/server_manager.h"
5 9
6namespace Service::BCAT { 10namespace Service::BCAT {
7 11
8BCAT::BCAT(Core::System& system_, std::shared_ptr<Module> module_, 12void LoopProcess(Core::System& system) {
9 FileSystem::FileSystemController& fsc_, const char* name_) 13 auto server_manager = std::make_unique<ServerManager>(system);
10 : Interface(system_, std::move(module_), fsc_, name_) { 14
11 // clang-format off 15 server_manager->RegisterNamedService("bcat:a",
12 static const FunctionInfo functions[] = { 16 std::make_shared<IServiceCreator>(system, "bcat:a"));
13 {0, &BCAT::CreateBcatService, "CreateBcatService"}, 17 server_manager->RegisterNamedService("bcat:m",
14 {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"}, 18 std::make_shared<IServiceCreator>(system, "bcat:m"));
15 {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"}, 19 server_manager->RegisterNamedService("bcat:u",
16 {3, nullptr, "CreateDeliveryCacheProgressService"}, 20 std::make_shared<IServiceCreator>(system, "bcat:u"));
17 {4, nullptr, "CreateDeliveryCacheProgressServiceWithApplicationId"}, 21 server_manager->RegisterNamedService("bcat:s",
18 }; 22 std::make_shared<IServiceCreator>(system, "bcat:s"));
19 // clang-format on 23
20 RegisterHandlers(functions); 24 server_manager->RegisterNamedService(
25 "news:a", std::make_shared<News::IServiceCreator>(system, 0xffffffff, "news:a"));
26 server_manager->RegisterNamedService(
27 "news:p", std::make_shared<News::IServiceCreator>(system, 0x1, "news:p"));
28 server_manager->RegisterNamedService(
29 "news:c", std::make_shared<News::IServiceCreator>(system, 0x2, "news:c"));
30 server_manager->RegisterNamedService(
31 "news:v", std::make_shared<News::IServiceCreator>(system, 0x4, "news:v"));
32 server_manager->RegisterNamedService(
33 "news:m", std::make_shared<News::IServiceCreator>(system, 0xd, "news:m"));
34
35 ServerManager::RunServer(std::move(server_manager));
21} 36}
22 37
23BCAT::~BCAT() = default;
24} // namespace Service::BCAT 38} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index db9d3c8c5..2aaffc693 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.h
@@ -3,7 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/service/bcat/bcat_module.h" 6#include "core/hle/service/service.h"
7 7
8namespace Core { 8namespace Core {
9class System; 9class System;
@@ -11,11 +11,6 @@ class System;
11 11
12namespace Service::BCAT { 12namespace Service::BCAT {
13 13
14class BCAT final : public Module::Interface { 14void LoopProcess(Core::System& system);
15public:
16 explicit BCAT(Core::System& system_, std::shared_ptr<Module> module_,
17 FileSystem::FileSystemController& fsc_, const char* name_);
18 ~BCAT() override;
19};
20 15
21} // namespace Service::BCAT 16} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp
deleted file mode 100644
index 76d7bb139..000000000
--- a/src/core/hle/service/bcat/bcat_module.cpp
+++ /dev/null
@@ -1,606 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cctype>
5#include <mbedtls/md5.h>
6#include "common/hex_util.h"
7#include "common/logging/log.h"
8#include "common/settings.h"
9#include "common/string_util.h"
10#include "core/core.h"
11#include "core/file_sys/vfs/vfs.h"
12#include "core/hle/kernel/k_readable_event.h"
13#include "core/hle/service/bcat/backend/backend.h"
14#include "core/hle/service/bcat/bcat.h"
15#include "core/hle/service/bcat/bcat_module.h"
16#include "core/hle/service/filesystem/filesystem.h"
17#include "core/hle/service/ipc_helpers.h"
18#include "core/hle/service/server_manager.h"
19
20namespace Service::BCAT {
21
22constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
23constexpr Result ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
24constexpr Result ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
25constexpr Result ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
26
27// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
28// and if any of them have a non-zero result it just forwards that result. This is the FS error code
29// for permission denied, which is the closest approximation of this scenario.
30constexpr Result ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
31
32using BCATDigest = std::array<u8, 0x10>;
33
34namespace {
35
36u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
37 u64 out{};
38 std::memcpy(&out, id.data(), sizeof(u64));
39 return out;
40}
41
42// The digest is only used to determine if a file is unique compared to others of the same name.
43// Since the algorithm isn't ever checked in game, MD5 is safe.
44BCATDigest DigestFile(const FileSys::VirtualFile& file) {
45 BCATDigest out{};
46 const auto bytes = file->ReadAllBytes();
47 mbedtls_md5_ret(bytes.data(), bytes.size(), out.data());
48 return out;
49}
50
51// For a name to be valid it must be non-empty, must have a null terminating character as the final
52// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
53// file.
54bool VerifyNameValidInternal(HLERequestContext& ctx, std::array<char, 0x20> name, char match_char) {
55 const auto null_chars = std::count(name.begin(), name.end(), 0);
56 const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
57 return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
58 });
59 if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
60 LOG_ERROR(Service_BCAT, "Name passed was invalid!");
61 IPC::ResponseBuilder rb{ctx, 2};
62 rb.Push(ERROR_INVALID_ARGUMENT);
63 return false;
64 }
65
66 return true;
67}
68
69bool VerifyNameValidDir(HLERequestContext& ctx, DirectoryName name) {
70 return VerifyNameValidInternal(ctx, name, '-');
71}
72
73bool VerifyNameValidFile(HLERequestContext& ctx, FileName name) {
74 return VerifyNameValidInternal(ctx, name, '.');
75}
76
77} // Anonymous namespace
78
79struct DeliveryCacheDirectoryEntry {
80 FileName name;
81 u64 size;
82 BCATDigest digest;
83};
84
85class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
86public:
87 explicit IDeliveryCacheProgressService(Core::System& system_, Kernel::KReadableEvent& event_,
88 const DeliveryCacheProgressImpl& impl_)
89 : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{event_}, impl{impl_} {
90 // clang-format off
91 static const FunctionInfo functions[] = {
92 {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
93 {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
94 };
95 // clang-format on
96
97 RegisterHandlers(functions);
98 }
99
100private:
101 void GetEvent(HLERequestContext& ctx) {
102 LOG_DEBUG(Service_BCAT, "called");
103
104 IPC::ResponseBuilder rb{ctx, 2, 1};
105 rb.Push(ResultSuccess);
106 rb.PushCopyObjects(event);
107 }
108
109 void GetImpl(HLERequestContext& ctx) {
110 LOG_DEBUG(Service_BCAT, "called");
111
112 ctx.WriteBuffer(impl);
113
114 IPC::ResponseBuilder rb{ctx, 2};
115 rb.Push(ResultSuccess);
116 }
117
118 Kernel::KReadableEvent& event;
119 const DeliveryCacheProgressImpl& impl;
120};
121
122class IBcatService final : public ServiceFramework<IBcatService> {
123public:
124 explicit IBcatService(Core::System& system_, Backend& backend_)
125 : ServiceFramework{system_, "IBcatService"}, backend{backend_},
126 progress{{
127 ProgressServiceBackend{system_, "Normal"},
128 ProgressServiceBackend{system_, "Directory"},
129 }} {
130 // clang-format off
131 static const FunctionInfo functions[] = {
132 {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
133 {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
134 {10200, nullptr, "CancelSyncDeliveryCacheRequest"},
135 {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
136 {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
137 {20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"},
138 {20301, nullptr, "RequestSuspendDeliveryTask"},
139 {20400, nullptr, "RegisterSystemApplicationDeliveryTask"},
140 {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
141 {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
142 {30100, &IBcatService::SetPassphrase, "SetPassphrase"},
143 {30101, nullptr, "Unknown30101"},
144 {30102, nullptr, "Unknown30102"},
145 {30200, nullptr, "RegisterBackgroundDeliveryTask"},
146 {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
147 {30202, nullptr, "BlockDeliveryTask"},
148 {30203, nullptr, "UnblockDeliveryTask"},
149 {30210, nullptr, "SetDeliveryTaskTimer"},
150 {30300, nullptr, "RegisterSystemApplicationDeliveryTasks"},
151 {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
152 {90101, nullptr, "Unknown90101"},
153 {90200, nullptr, "GetDeliveryList"},
154 {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
155 {90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"},
156 {90300, nullptr, "GetPushNotificationLog"},
157 {90301, nullptr, "Unknown90301"},
158 };
159 // clang-format on
160 RegisterHandlers(functions);
161 }
162
163private:
164 enum class SyncType {
165 Normal,
166 Directory,
167 Count,
168 };
169
170 std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
171 auto& progress_backend{GetProgressBackend(type)};
172 return std::make_shared<IDeliveryCacheProgressService>(system, progress_backend.GetEvent(),
173 progress_backend.GetImpl());
174 }
175
176 void RequestSyncDeliveryCache(HLERequestContext& ctx) {
177 LOG_DEBUG(Service_BCAT, "called");
178
179 backend.Synchronize({system.GetApplicationProcessProgramID(),
180 GetCurrentBuildID(system.GetApplicationProcessBuildID())},
181 GetProgressBackend(SyncType::Normal));
182
183 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
184 rb.Push(ResultSuccess);
185 rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
186 }
187
188 void RequestSyncDeliveryCacheWithDirectoryName(HLERequestContext& ctx) {
189 IPC::RequestParser rp{ctx};
190 const auto name_raw = rp.PopRaw<DirectoryName>();
191 const auto name =
192 Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
193
194 LOG_DEBUG(Service_BCAT, "called, name={}", name);
195
196 backend.SynchronizeDirectory({system.GetApplicationProcessProgramID(),
197 GetCurrentBuildID(system.GetApplicationProcessBuildID())},
198 name, GetProgressBackend(SyncType::Directory));
199
200 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
201 rb.Push(ResultSuccess);
202 rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
203 }
204
205 void SetPassphrase(HLERequestContext& ctx) {
206 IPC::RequestParser rp{ctx};
207 const auto title_id = rp.PopRaw<u64>();
208
209 const auto passphrase_raw = ctx.ReadBuffer();
210
211 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
212 Common::HexToString(passphrase_raw));
213
214 if (title_id == 0) {
215 LOG_ERROR(Service_BCAT, "Invalid title ID!");
216 IPC::ResponseBuilder rb{ctx, 2};
217 rb.Push(ERROR_INVALID_ARGUMENT);
218 }
219
220 if (passphrase_raw.size() > 0x40) {
221 LOG_ERROR(Service_BCAT, "Passphrase too large!");
222 IPC::ResponseBuilder rb{ctx, 2};
223 rb.Push(ERROR_INVALID_ARGUMENT);
224 return;
225 }
226
227 Passphrase passphrase{};
228 std::memcpy(passphrase.data(), passphrase_raw.data(),
229 std::min(passphrase.size(), passphrase_raw.size()));
230
231 backend.SetPassphrase(title_id, passphrase);
232
233 IPC::ResponseBuilder rb{ctx, 2};
234 rb.Push(ResultSuccess);
235 }
236
237 void ClearDeliveryCacheStorage(HLERequestContext& ctx) {
238 IPC::RequestParser rp{ctx};
239 const auto title_id = rp.PopRaw<u64>();
240
241 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
242
243 if (title_id == 0) {
244 LOG_ERROR(Service_BCAT, "Invalid title ID!");
245 IPC::ResponseBuilder rb{ctx, 2};
246 rb.Push(ERROR_INVALID_ARGUMENT);
247 return;
248 }
249
250 if (!backend.Clear(title_id)) {
251 LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
252 IPC::ResponseBuilder rb{ctx, 2};
253 rb.Push(ERROR_FAILED_CLEAR_CACHE);
254 return;
255 }
256
257 IPC::ResponseBuilder rb{ctx, 2};
258 rb.Push(ResultSuccess);
259 }
260
261 ProgressServiceBackend& GetProgressBackend(SyncType type) {
262 return progress.at(static_cast<size_t>(type));
263 }
264
265 const ProgressServiceBackend& GetProgressBackend(SyncType type) const {
266 return progress.at(static_cast<size_t>(type));
267 }
268
269 Backend& backend;
270 std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress;
271};
272
273void Module::Interface::CreateBcatService(HLERequestContext& ctx) {
274 LOG_DEBUG(Service_BCAT, "called");
275
276 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
277 rb.Push(ResultSuccess);
278 rb.PushIpcInterface<IBcatService>(system, *backend);
279}
280
281class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
282public:
283 explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_)
284 : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) {
285 // clang-format off
286 static const FunctionInfo functions[] = {
287 {0, &IDeliveryCacheFileService::Open, "Open"},
288 {1, &IDeliveryCacheFileService::Read, "Read"},
289 {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
290 {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
291 };
292 // clang-format on
293
294 RegisterHandlers(functions);
295 }
296
297private:
298 void Open(HLERequestContext& ctx) {
299 IPC::RequestParser rp{ctx};
300 const auto dir_name_raw = rp.PopRaw<DirectoryName>();
301 const auto file_name_raw = rp.PopRaw<FileName>();
302
303 const auto dir_name =
304 Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
305 const auto file_name =
306 Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
307
308 LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
309
310 if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
311 return;
312
313 if (current_file != nullptr) {
314 LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
315 IPC::ResponseBuilder rb{ctx, 2};
316 rb.Push(ERROR_ENTITY_ALREADY_OPEN);
317 return;
318 }
319
320 const auto dir = root->GetSubdirectory(dir_name);
321
322 if (dir == nullptr) {
323 LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
324 IPC::ResponseBuilder rb{ctx, 2};
325 rb.Push(ERROR_FAILED_OPEN_ENTITY);
326 return;
327 }
328
329 current_file = dir->GetFile(file_name);
330
331 if (current_file == nullptr) {
332 LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
333 IPC::ResponseBuilder rb{ctx, 2};
334 rb.Push(ERROR_FAILED_OPEN_ENTITY);
335 return;
336 }
337
338 IPC::ResponseBuilder rb{ctx, 2};
339 rb.Push(ResultSuccess);
340 }
341
342 void Read(HLERequestContext& ctx) {
343 IPC::RequestParser rp{ctx};
344 const auto offset{rp.PopRaw<u64>()};
345
346 auto size = ctx.GetWriteBufferSize();
347
348 LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
349
350 if (current_file == nullptr) {
351 LOG_ERROR(Service_BCAT, "There is no file currently open!");
352 IPC::ResponseBuilder rb{ctx, 2};
353 rb.Push(ERROR_NO_OPEN_ENTITY);
354 }
355
356 size = std::min<u64>(current_file->GetSize() - offset, size);
357 const auto buffer = current_file->ReadBytes(size, offset);
358 ctx.WriteBuffer(buffer);
359
360 IPC::ResponseBuilder rb{ctx, 4};
361 rb.Push(ResultSuccess);
362 rb.Push<u64>(buffer.size());
363 }
364
365 void GetSize(HLERequestContext& ctx) {
366 LOG_DEBUG(Service_BCAT, "called");
367
368 if (current_file == nullptr) {
369 LOG_ERROR(Service_BCAT, "There is no file currently open!");
370 IPC::ResponseBuilder rb{ctx, 2};
371 rb.Push(ERROR_NO_OPEN_ENTITY);
372 }
373
374 IPC::ResponseBuilder rb{ctx, 4};
375 rb.Push(ResultSuccess);
376 rb.Push<u64>(current_file->GetSize());
377 }
378
379 void GetDigest(HLERequestContext& ctx) {
380 LOG_DEBUG(Service_BCAT, "called");
381
382 if (current_file == nullptr) {
383 LOG_ERROR(Service_BCAT, "There is no file currently open!");
384 IPC::ResponseBuilder rb{ctx, 2};
385 rb.Push(ERROR_NO_OPEN_ENTITY);
386 }
387
388 IPC::ResponseBuilder rb{ctx, 6};
389 rb.Push(ResultSuccess);
390 rb.PushRaw(DigestFile(current_file));
391 }
392
393 FileSys::VirtualDir root;
394 FileSys::VirtualFile current_file;
395};
396
397class IDeliveryCacheDirectoryService final
398 : public ServiceFramework<IDeliveryCacheDirectoryService> {
399public:
400 explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_)
401 : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
402 // clang-format off
403 static const FunctionInfo functions[] = {
404 {0, &IDeliveryCacheDirectoryService::Open, "Open"},
405 {1, &IDeliveryCacheDirectoryService::Read, "Read"},
406 {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
407 };
408 // clang-format on
409
410 RegisterHandlers(functions);
411 }
412
413private:
414 void Open(HLERequestContext& ctx) {
415 IPC::RequestParser rp{ctx};
416 const auto name_raw = rp.PopRaw<DirectoryName>();
417 const auto name =
418 Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
419
420 LOG_DEBUG(Service_BCAT, "called, name={}", name);
421
422 if (!VerifyNameValidDir(ctx, name_raw))
423 return;
424
425 if (current_dir != nullptr) {
426 LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
427 IPC::ResponseBuilder rb{ctx, 2};
428 rb.Push(ERROR_ENTITY_ALREADY_OPEN);
429 return;
430 }
431
432 current_dir = root->GetSubdirectory(name);
433
434 if (current_dir == nullptr) {
435 LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
436 IPC::ResponseBuilder rb{ctx, 2};
437 rb.Push(ERROR_FAILED_OPEN_ENTITY);
438 return;
439 }
440
441 IPC::ResponseBuilder rb{ctx, 2};
442 rb.Push(ResultSuccess);
443 }
444
445 void Read(HLERequestContext& ctx) {
446 auto write_size = ctx.GetWriteBufferNumElements<DeliveryCacheDirectoryEntry>();
447
448 LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
449
450 if (current_dir == nullptr) {
451 LOG_ERROR(Service_BCAT, "There is no open directory!");
452 IPC::ResponseBuilder rb{ctx, 2};
453 rb.Push(ERROR_NO_OPEN_ENTITY);
454 return;
455 }
456
457 const auto files = current_dir->GetFiles();
458 write_size = std::min<u64>(write_size, files.size());
459 std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
460 std::transform(
461 files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
462 FileName name{};
463 std::memcpy(name.data(), file->GetName().data(),
464 std::min(file->GetName().size(), name.size()));
465 return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
466 });
467
468 ctx.WriteBuffer(entries);
469
470 IPC::ResponseBuilder rb{ctx, 3};
471 rb.Push(ResultSuccess);
472 rb.Push(static_cast<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry)));
473 }
474
475 void GetCount(HLERequestContext& ctx) {
476 LOG_DEBUG(Service_BCAT, "called");
477
478 if (current_dir == nullptr) {
479 LOG_ERROR(Service_BCAT, "There is no open directory!");
480 IPC::ResponseBuilder rb{ctx, 2};
481 rb.Push(ERROR_NO_OPEN_ENTITY);
482 return;
483 }
484
485 const auto files = current_dir->GetFiles();
486
487 IPC::ResponseBuilder rb{ctx, 3};
488 rb.Push(ResultSuccess);
489 rb.Push(static_cast<u32>(files.size()));
490 }
491
492 FileSys::VirtualDir root;
493 FileSys::VirtualDir current_dir;
494};
495
496class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
497public:
498 explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_)
499 : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) {
500 // clang-format off
501 static const FunctionInfo functions[] = {
502 {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
503 {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
504 {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
505 };
506 // clang-format on
507
508 RegisterHandlers(functions);
509
510 for (const auto& subdir : root->GetSubdirectories()) {
511 DirectoryName name{};
512 std::memcpy(name.data(), subdir->GetName().data(),
513 std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
514 entries.push_back(name);
515 }
516 }
517
518private:
519 void CreateFileService(HLERequestContext& ctx) {
520 LOG_DEBUG(Service_BCAT, "called");
521
522 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
523 rb.Push(ResultSuccess);
524 rb.PushIpcInterface<IDeliveryCacheFileService>(system, root);
525 }
526
527 void CreateDirectoryService(HLERequestContext& ctx) {
528 LOG_DEBUG(Service_BCAT, "called");
529
530 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
531 rb.Push(ResultSuccess);
532 rb.PushIpcInterface<IDeliveryCacheDirectoryService>(system, root);
533 }
534
535 void EnumerateDeliveryCacheDirectory(HLERequestContext& ctx) {
536 auto size = ctx.GetWriteBufferNumElements<DirectoryName>();
537
538 LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
539
540 size = std::min<u64>(size, entries.size() - next_read_index);
541 ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
542 next_read_index += size;
543
544 IPC::ResponseBuilder rb{ctx, 3};
545 rb.Push(ResultSuccess);
546 rb.Push(static_cast<u32>(size));
547 }
548
549 FileSys::VirtualDir root;
550 std::vector<DirectoryName> entries;
551 u64 next_read_index = 0;
552};
553
554void Module::Interface::CreateDeliveryCacheStorageService(HLERequestContext& ctx) {
555 LOG_DEBUG(Service_BCAT, "called");
556
557 const auto title_id = system.GetApplicationProcessProgramID();
558 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
559 rb.Push(ResultSuccess);
560 rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
561}
562
563void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(HLERequestContext& ctx) {
564 IPC::RequestParser rp{ctx};
565 const auto title_id = rp.PopRaw<u64>();
566
567 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
568
569 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
570 rb.Push(ResultSuccess);
571 rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
572}
573
574std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
575 DirectoryGetter getter) {
576 return std::make_unique<NullBackend>(std::move(getter));
577}
578
579Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
580 FileSystem::FileSystemController& fsc_, const char* name)
581 : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)},
582 backend{CreateBackendFromSettings(system_,
583 [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {}
584
585Module::Interface::~Interface() = default;
586
587void LoopProcess(Core::System& system) {
588 auto server_manager = std::make_unique<ServerManager>(system);
589 auto module = std::make_shared<Module>();
590
591 server_manager->RegisterNamedService(
592 "bcat:a",
593 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:a"));
594 server_manager->RegisterNamedService(
595 "bcat:m",
596 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m"));
597 server_manager->RegisterNamedService(
598 "bcat:u",
599 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u"));
600 server_manager->RegisterNamedService(
601 "bcat:s",
602 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s"));
603 ServerManager::RunServer(std::move(server_manager));
604}
605
606} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat_module.h b/src/core/hle/service/bcat/bcat_module.h
deleted file mode 100644
index 87576288b..000000000
--- a/src/core/hle/service/bcat/bcat_module.h
+++ /dev/null
@@ -1,46 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/service.h"
7
8namespace Core {
9class System;
10}
11
12namespace Service {
13
14namespace FileSystem {
15class FileSystemController;
16} // namespace FileSystem
17
18namespace BCAT {
19
20class Backend;
21
22class Module final {
23public:
24 class Interface : public ServiceFramework<Interface> {
25 public:
26 explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
27 FileSystem::FileSystemController& fsc_, const char* name);
28 ~Interface() override;
29
30 void CreateBcatService(HLERequestContext& ctx);
31 void CreateDeliveryCacheStorageService(HLERequestContext& ctx);
32 void CreateDeliveryCacheStorageServiceWithApplicationId(HLERequestContext& ctx);
33
34 protected:
35 FileSystem::FileSystemController& fsc;
36
37 std::shared_ptr<Module> module;
38 std::unique_ptr<Backend> backend;
39 };
40};
41
42void LoopProcess(Core::System& system);
43
44} // namespace BCAT
45
46} // namespace Service
diff --git a/src/core/hle/service/bcat/bcat_result.h b/src/core/hle/service/bcat/bcat_result.h
new file mode 100644
index 000000000..edf8a6564
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat_result.h
@@ -0,0 +1,15 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::BCAT {
9
10constexpr Result ResultInvalidArgument{ErrorModule::BCAT, 1};
11constexpr Result ResultFailedOpenEntity{ErrorModule::BCAT, 2};
12constexpr Result ResultEntityAlreadyOpen{ErrorModule::BCAT, 6};
13constexpr Result ResultNoOpenEntry{ErrorModule::BCAT, 7};
14
15} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat_service.cpp b/src/core/hle/service/bcat/bcat_service.cpp
new file mode 100644
index 000000000..63b1072d2
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat_service.cpp
@@ -0,0 +1,132 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/hex_util.h"
5#include "common/string_util.h"
6#include "core/core.h"
7#include "core/file_sys/errors.h"
8#include "core/hle/service/bcat/backend/backend.h"
9#include "core/hle/service/bcat/bcat_result.h"
10#include "core/hle/service/bcat/bcat_service.h"
11#include "core/hle/service/bcat/bcat_util.h"
12#include "core/hle/service/bcat/delivery_cache_progress_service.h"
13#include "core/hle/service/bcat/delivery_cache_storage_service.h"
14#include "core/hle/service/cmif_serialization.h"
15
16namespace Service::BCAT {
17
18static u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
19 u64 out{};
20 std::memcpy(&out, id.data(), sizeof(u64));
21 return out;
22}
23
24IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_)
25 : ServiceFramework{system_, "IBcatService"}, backend{backend_},
26 progress{{
27 ProgressServiceBackend{system_, "Normal"},
28 ProgressServiceBackend{system_, "Directory"},
29 }} {
30 // clang-format off
31 static const FunctionInfo functions[] = {
32 {10100, D<&IBcatService::RequestSyncDeliveryCache>, "RequestSyncDeliveryCache"},
33 {10101, D<&IBcatService::RequestSyncDeliveryCacheWithDirectoryName>, "RequestSyncDeliveryCacheWithDirectoryName"},
34 {10200, nullptr, "CancelSyncDeliveryCacheRequest"},
35 {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
36 {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
37 {20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"},
38 {20301, nullptr, "RequestSuspendDeliveryTask"},
39 {20400, nullptr, "RegisterSystemApplicationDeliveryTask"},
40 {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
41 {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
42 {30100, D<&IBcatService::SetPassphrase>, "SetPassphrase"},
43 {30101, nullptr, "Unknown30101"},
44 {30102, nullptr, "Unknown30102"},
45 {30200, nullptr, "RegisterBackgroundDeliveryTask"},
46 {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
47 {30202, nullptr, "BlockDeliveryTask"},
48 {30203, nullptr, "UnblockDeliveryTask"},
49 {30210, nullptr, "SetDeliveryTaskTimer"},
50 {30300, D<&IBcatService::RegisterSystemApplicationDeliveryTasks>, "RegisterSystemApplicationDeliveryTasks"},
51 {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
52 {90101, nullptr, "Unknown90101"},
53 {90200, nullptr, "GetDeliveryList"},
54 {90201, D<&IBcatService::ClearDeliveryCacheStorage>, "ClearDeliveryCacheStorage"},
55 {90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"},
56 {90300, nullptr, "GetPushNotificationLog"},
57 {90301, nullptr, "Unknown90301"},
58 };
59 // clang-format on
60 RegisterHandlers(functions);
61}
62
63IBcatService::~IBcatService() = default;
64
65Result IBcatService::RequestSyncDeliveryCache(
66 OutInterface<IDeliveryCacheProgressService> out_interface) {
67 LOG_DEBUG(Service_BCAT, "called");
68
69 auto& progress_backend{GetProgressBackend(SyncType::Normal)};
70 backend.Synchronize({system.GetApplicationProcessProgramID(),
71 GetCurrentBuildID(system.GetApplicationProcessBuildID())},
72 GetProgressBackend(SyncType::Normal));
73
74 *out_interface = std::make_shared<IDeliveryCacheProgressService>(
75 system, progress_backend.GetEvent(), progress_backend.GetImpl());
76 R_SUCCEED();
77}
78
79Result IBcatService::RequestSyncDeliveryCacheWithDirectoryName(
80 const DirectoryName& name_raw, OutInterface<IDeliveryCacheProgressService> out_interface) {
81 const auto name = Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
82
83 LOG_DEBUG(Service_BCAT, "called, name={}", name);
84
85 auto& progress_backend{GetProgressBackend(SyncType::Directory)};
86 backend.SynchronizeDirectory({system.GetApplicationProcessProgramID(),
87 GetCurrentBuildID(system.GetApplicationProcessBuildID())},
88 name, progress_backend);
89
90 *out_interface = std::make_shared<IDeliveryCacheProgressService>(
91 system, progress_backend.GetEvent(), progress_backend.GetImpl());
92 R_SUCCEED();
93}
94
95Result IBcatService::SetPassphrase(u64 application_id,
96 InBuffer<BufferAttr_HipcPointer> passphrase_buffer) {
97 LOG_DEBUG(Service_BCAT, "called, application_id={:016X}, passphrase={}", application_id,
98 Common::HexToString(passphrase_buffer));
99
100 R_UNLESS(application_id != 0, ResultInvalidArgument);
101 R_UNLESS(passphrase_buffer.size() <= 0x40, ResultInvalidArgument);
102
103 Passphrase passphrase{};
104 std::memcpy(passphrase.data(), passphrase_buffer.data(),
105 std::min(passphrase.size(), passphrase_buffer.size()));
106
107 backend.SetPassphrase(application_id, passphrase);
108 R_SUCCEED();
109}
110
111Result IBcatService::RegisterSystemApplicationDeliveryTasks() {
112 LOG_WARNING(Service_BCAT, "(STUBBED) called");
113 R_SUCCEED();
114}
115
116Result IBcatService::ClearDeliveryCacheStorage(u64 application_id) {
117 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", application_id);
118
119 R_UNLESS(application_id != 0, ResultInvalidArgument);
120 R_UNLESS(backend.Clear(application_id), FileSys::ResultPermissionDenied);
121 R_SUCCEED();
122}
123
124ProgressServiceBackend& IBcatService::GetProgressBackend(SyncType type) {
125 return progress.at(static_cast<size_t>(type));
126}
127
128const ProgressServiceBackend& IBcatService::GetProgressBackend(SyncType type) const {
129 return progress.at(static_cast<size_t>(type));
130}
131
132} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat_service.h b/src/core/hle/service/bcat/bcat_service.h
new file mode 100644
index 000000000..dda5a2d5f
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat_service.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/bcat/backend/backend.h"
7#include "core/hle/service/bcat/bcat_types.h"
8#include "core/hle/service/cmif_types.h"
9#include "core/hle/service/service.h"
10
11namespace Core {
12class System;
13}
14
15namespace Service::BCAT {
16class BcatBackend;
17class IDeliveryCacheStorageService;
18class IDeliveryCacheProgressService;
19
20class IBcatService final : public ServiceFramework<IBcatService> {
21public:
22 explicit IBcatService(Core::System& system_, BcatBackend& backend_);
23 ~IBcatService() override;
24
25private:
26 Result RequestSyncDeliveryCache(OutInterface<IDeliveryCacheProgressService> out_interface);
27
28 Result RequestSyncDeliveryCacheWithDirectoryName(
29 const DirectoryName& name, OutInterface<IDeliveryCacheProgressService> out_interface);
30
31 Result SetPassphrase(u64 application_id, InBuffer<BufferAttr_HipcPointer> passphrase_buffer);
32
33 Result RegisterSystemApplicationDeliveryTasks();
34
35 Result ClearDeliveryCacheStorage(u64 application_id);
36
37private:
38 ProgressServiceBackend& GetProgressBackend(SyncType type);
39 const ProgressServiceBackend& GetProgressBackend(SyncType type) const;
40
41 BcatBackend& backend;
42 std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress;
43};
44
45} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat_types.h b/src/core/hle/service/bcat/bcat_types.h
new file mode 100644
index 000000000..b35dab7c5
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat_types.h
@@ -0,0 +1,66 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7#include <functional>
8
9#include "common/common_funcs.h"
10#include "common/common_types.h"
11#include "core/file_sys/vfs/vfs_types.h"
12#include "core/hle/result.h"
13
14namespace Service::BCAT {
15
16using DirectoryName = std::array<char, 0x20>;
17using FileName = std::array<char, 0x20>;
18using BcatDigest = std::array<u8, 0x10>;
19using Passphrase = std::array<u8, 0x20>;
20using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
21
22enum class SyncType {
23 Normal,
24 Directory,
25 Count,
26};
27
28enum class DeliveryCacheProgressStatus : s32 {
29 None = 0x0,
30 Queued = 0x1,
31 Connecting = 0x2,
32 ProcessingDataList = 0x3,
33 Downloading = 0x4,
34 Committing = 0x5,
35 Done = 0x9,
36};
37
38struct DeliveryCacheDirectoryEntry {
39 FileName name;
40 u64 size;
41 BcatDigest digest;
42};
43
44struct TitleIDVersion {
45 u64 title_id;
46 u64 build_id;
47};
48
49struct DeliveryCacheProgressImpl {
50 DeliveryCacheProgressStatus status;
51 Result result;
52 DirectoryName current_directory;
53 FileName current_file;
54 s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
55 s64 current_total_bytes; ///< Bytes total on current file.
56 s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
57 s64 total_bytes; ///< Bytes total on overall download.
58 INSERT_PADDING_BYTES_NOINIT(
59 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
60};
61static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
62 "DeliveryCacheProgressImpl has incorrect size.");
63static_assert(std::is_trivial_v<DeliveryCacheProgressImpl>,
64 "DeliveryCacheProgressImpl type must be trivially copyable.");
65
66} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat_util.h b/src/core/hle/service/bcat/bcat_util.h
new file mode 100644
index 000000000..6bf2657ee
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat_util.h
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7#include <cctype>
8#include <mbedtls/md5.h>
9
10#include "core/hle/service/bcat/bcat_result.h"
11#include "core/hle/service/bcat/bcat_types.h"
12
13namespace Service::BCAT {
14
15// For a name to be valid it must be non-empty, must have a null terminating character as the final
16// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
17// file.
18constexpr Result VerifyNameValidInternal(std::array<char, 0x20> name, char match_char) {
19 const auto null_chars = std::count(name.begin(), name.end(), 0);
20 const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
21 return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
22 });
23 if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
24 LOG_ERROR(Service_BCAT, "Name passed was invalid!");
25 return ResultInvalidArgument;
26 }
27
28 return ResultSuccess;
29}
30
31constexpr Result VerifyNameValidDir(DirectoryName name) {
32 return VerifyNameValidInternal(name, '-');
33}
34
35constexpr Result VerifyNameValidFile(FileName name) {
36 return VerifyNameValidInternal(name, '.');
37}
38
39} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_directory_service.cpp b/src/core/hle/service/bcat/delivery_cache_directory_service.cpp
new file mode 100644
index 000000000..01f08a2fc
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_directory_service.cpp
@@ -0,0 +1,80 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "common/string_util.h"
5#include "core/file_sys/vfs/vfs_types.h"
6#include "core/hle/service/bcat/bcat_result.h"
7#include "core/hle/service/bcat/bcat_util.h"
8#include "core/hle/service/bcat/delivery_cache_directory_service.h"
9#include "core/hle/service/cmif_serialization.h"
10
11namespace Service::BCAT {
12
13// The digest is only used to determine if a file is unique compared to others of the same name.
14// Since the algorithm isn't ever checked in game, MD5 is safe.
15static BcatDigest DigestFile(const FileSys::VirtualFile& file) {
16 BcatDigest out{};
17 const auto bytes = file->ReadAllBytes();
18 mbedtls_md5_ret(bytes.data(), bytes.size(), out.data());
19 return out;
20}
21
22IDeliveryCacheDirectoryService::IDeliveryCacheDirectoryService(Core::System& system_,
23 FileSys::VirtualDir root_)
24 : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
25 // clang-format off
26 static const FunctionInfo functions[] = {
27 {0, D<&IDeliveryCacheDirectoryService::Open>, "Open"},
28 {1, D<&IDeliveryCacheDirectoryService::Read>, "Read"},
29 {2, D<&IDeliveryCacheDirectoryService::GetCount>, "GetCount"},
30 };
31 // clang-format on
32
33 RegisterHandlers(functions);
34}
35
36IDeliveryCacheDirectoryService::~IDeliveryCacheDirectoryService() = default;
37
38Result IDeliveryCacheDirectoryService::Open(const DirectoryName& dir_name_raw) {
39 const auto dir_name =
40 Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
41
42 LOG_DEBUG(Service_BCAT, "called, dir_name={}", dir_name);
43
44 R_TRY(VerifyNameValidDir(dir_name_raw));
45 R_UNLESS(current_dir == nullptr, ResultEntityAlreadyOpen);
46
47 const auto dir = root->GetSubdirectory(dir_name);
48 R_UNLESS(dir != nullptr, ResultFailedOpenEntity);
49
50 R_SUCCEED();
51}
52
53Result IDeliveryCacheDirectoryService::Read(
54 Out<s32> out_count, OutArray<DeliveryCacheDirectoryEntry, BufferAttr_HipcMapAlias> out_buffer) {
55 LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", out_buffer.size());
56
57 R_UNLESS(current_dir != nullptr, ResultNoOpenEntry);
58
59 const auto files = current_dir->GetFiles();
60 *out_count = static_cast<s32>(std::min(files.size(), out_buffer.size()));
61 std::transform(files.begin(), files.begin() + *out_count, out_buffer.begin(),
62 [](const auto& file) {
63 FileName name{};
64 std::memcpy(name.data(), file->GetName().data(),
65 std::min(file->GetName().size(), name.size()));
66 return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
67 });
68 R_SUCCEED();
69}
70
71Result IDeliveryCacheDirectoryService::GetCount(Out<s32> out_count) {
72 LOG_DEBUG(Service_BCAT, "called");
73
74 R_UNLESS(current_dir != nullptr, ResultNoOpenEntry);
75
76 *out_count = static_cast<s32>(current_dir->GetFiles().size());
77 R_SUCCEED();
78}
79
80} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_directory_service.h b/src/core/hle/service/bcat/delivery_cache_directory_service.h
new file mode 100644
index 000000000..b902c6495
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_directory_service.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/file_sys/vfs/vfs.h"
7#include "core/hle/service/bcat/bcat_types.h"
8#include "core/hle/service/cmif_types.h"
9#include "core/hle/service/service.h"
10
11namespace Core {
12class System;
13}
14
15namespace Service::BCAT {
16
17class IDeliveryCacheDirectoryService final
18 : public ServiceFramework<IDeliveryCacheDirectoryService> {
19public:
20 explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_);
21 ~IDeliveryCacheDirectoryService() override;
22
23private:
24 Result Open(const DirectoryName& dir_name_raw);
25 Result Read(Out<s32> out_count,
26 OutArray<DeliveryCacheDirectoryEntry, BufferAttr_HipcMapAlias> out_buffer);
27 Result GetCount(Out<s32> out_count);
28
29 FileSys::VirtualDir root;
30 FileSys::VirtualDir current_dir;
31};
32
33} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_file_service.cpp b/src/core/hle/service/bcat/delivery_cache_file_service.cpp
new file mode 100644
index 000000000..b75fac4bf
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_file_service.cpp
@@ -0,0 +1,82 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "common/string_util.h"
5#include "core/hle/service/bcat/bcat_result.h"
6#include "core/hle/service/bcat/bcat_util.h"
7#include "core/hle/service/bcat/delivery_cache_file_service.h"
8#include "core/hle/service/cmif_serialization.h"
9
10namespace Service::BCAT {
11
12IDeliveryCacheFileService::IDeliveryCacheFileService(Core::System& system_,
13 FileSys::VirtualDir root_)
14 : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) {
15 // clang-format off
16 static const FunctionInfo functions[] = {
17 {0, D<&IDeliveryCacheFileService::Open>, "Open"},
18 {1, D<&IDeliveryCacheFileService::Read>, "Read"},
19 {2, D<&IDeliveryCacheFileService::GetSize>, "GetSize"},
20 {3, D<&IDeliveryCacheFileService::GetDigest>, "GetDigest"},
21 };
22 // clang-format on
23
24 RegisterHandlers(functions);
25}
26
27IDeliveryCacheFileService::~IDeliveryCacheFileService() = default;
28
29Result IDeliveryCacheFileService::Open(const DirectoryName& dir_name_raw,
30 const FileName& file_name_raw) {
31 const auto dir_name =
32 Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
33 const auto file_name =
34 Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
35
36 LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
37
38 R_TRY(VerifyNameValidDir(dir_name_raw));
39 R_TRY(VerifyNameValidDir(file_name_raw));
40 R_UNLESS(current_file == nullptr, ResultEntityAlreadyOpen);
41
42 const auto dir = root->GetSubdirectory(dir_name);
43 R_UNLESS(dir != nullptr, ResultFailedOpenEntity);
44
45 current_file = dir->GetFile(file_name);
46 R_UNLESS(current_file != nullptr, ResultFailedOpenEntity);
47
48 R_SUCCEED();
49}
50
51Result IDeliveryCacheFileService::Read(Out<u64> out_buffer_size, u64 offset,
52 OutBuffer<BufferAttr_HipcMapAlias> out_buffer) {
53 LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, out_buffer.size());
54
55 R_UNLESS(current_file != nullptr, ResultNoOpenEntry);
56
57 *out_buffer_size = std::min<u64>(current_file->GetSize() - offset, out_buffer.size());
58 const auto buffer = current_file->ReadBytes(*out_buffer_size, offset);
59 memcpy(out_buffer.data(), buffer.data(), buffer.size());
60
61 R_SUCCEED();
62}
63
64Result IDeliveryCacheFileService::GetSize(Out<u64> out_size) {
65 LOG_DEBUG(Service_BCAT, "called");
66
67 R_UNLESS(current_file != nullptr, ResultNoOpenEntry);
68
69 *out_size = current_file->GetSize();
70 R_SUCCEED();
71}
72
73Result IDeliveryCacheFileService::GetDigest(Out<BcatDigest> out_digest) {
74 LOG_DEBUG(Service_BCAT, "called");
75
76 R_UNLESS(current_file != nullptr, ResultNoOpenEntry);
77
78 //*out_digest = DigestFile(current_file);
79 R_SUCCEED();
80}
81
82} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_file_service.h b/src/core/hle/service/bcat/delivery_cache_file_service.h
new file mode 100644
index 000000000..e1012e687
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_file_service.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/file_sys/vfs/vfs.h"
7#include "core/hle/service/bcat/bcat_types.h"
8#include "core/hle/service/cmif_types.h"
9#include "core/hle/service/service.h"
10
11namespace Core {
12class System;
13}
14
15namespace Service::BCAT {
16
17class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
18public:
19 explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_);
20 ~IDeliveryCacheFileService() override;
21
22private:
23 Result Open(const DirectoryName& dir_name_raw, const FileName& file_name_raw);
24 Result Read(Out<u64> out_buffer_size, u64 offset,
25 OutBuffer<BufferAttr_HipcMapAlias> out_buffer);
26 Result GetSize(Out<u64> out_size);
27 Result GetDigest(Out<BcatDigest> out_digest);
28
29 FileSys::VirtualDir root;
30 FileSys::VirtualFile current_file;
31};
32
33} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_progress_service.cpp b/src/core/hle/service/bcat/delivery_cache_progress_service.cpp
new file mode 100644
index 000000000..79e7e0d95
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_progress_service.cpp
@@ -0,0 +1,41 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/bcat_types.h"
5#include "core/hle/service/bcat/delivery_cache_progress_service.h"
6#include "core/hle/service/cmif_serialization.h"
7
8namespace Service::BCAT {
9
10IDeliveryCacheProgressService::IDeliveryCacheProgressService(Core::System& system_,
11 Kernel::KReadableEvent& event_,
12 const DeliveryCacheProgressImpl& impl_)
13 : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{event_}, impl{impl_} {
14 // clang-format off
15 static const FunctionInfo functions[] = {
16 {0, D<&IDeliveryCacheProgressService::GetEvent>, "Get"},
17 {1, D<&IDeliveryCacheProgressService::GetImpl>, "Get"},
18 };
19 // clang-format on
20
21 RegisterHandlers(functions);
22}
23
24IDeliveryCacheProgressService::~IDeliveryCacheProgressService() = default;
25
26Result IDeliveryCacheProgressService::GetEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
27 LOG_DEBUG(Service_BCAT, "called");
28
29 *out_event = &event;
30 R_SUCCEED();
31}
32
33Result IDeliveryCacheProgressService::GetImpl(
34 OutLargeData<DeliveryCacheProgressImpl, BufferAttr_HipcPointer> out_impl) {
35 LOG_DEBUG(Service_BCAT, "called");
36
37 *out_impl = impl;
38 R_SUCCEED();
39}
40
41} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_progress_service.h b/src/core/hle/service/bcat/delivery_cache_progress_service.h
new file mode 100644
index 000000000..f81a13980
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_progress_service.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/service.h"
8
9namespace Core {
10class System;
11}
12
13namespace Kernel {
14class KEvent;
15class KReadableEvent;
16} // namespace Kernel
17
18namespace Service::BCAT {
19struct DeliveryCacheProgressImpl;
20
21class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
22public:
23 explicit IDeliveryCacheProgressService(Core::System& system_, Kernel::KReadableEvent& event_,
24 const DeliveryCacheProgressImpl& impl_);
25 ~IDeliveryCacheProgressService() override;
26
27private:
28 Result GetEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
29 Result GetImpl(OutLargeData<DeliveryCacheProgressImpl, BufferAttr_HipcPointer> out_impl);
30
31 Kernel::KReadableEvent& event;
32 const DeliveryCacheProgressImpl& impl;
33};
34
35} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_storage_service.cpp b/src/core/hle/service/bcat/delivery_cache_storage_service.cpp
new file mode 100644
index 000000000..4c79d71f4
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_storage_service.cpp
@@ -0,0 +1,57 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/bcat_result.h"
5#include "core/hle/service/bcat/delivery_cache_directory_service.h"
6#include "core/hle/service/bcat/delivery_cache_file_service.h"
7#include "core/hle/service/bcat/delivery_cache_storage_service.h"
8#include "core/hle/service/cmif_serialization.h"
9
10namespace Service::BCAT {
11
12IDeliveryCacheStorageService::IDeliveryCacheStorageService(Core::System& system_,
13 FileSys::VirtualDir root_)
14 : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) {
15 // clang-format off
16 static const FunctionInfo functions[] = {
17 {0, D<&IDeliveryCacheStorageService::CreateFileService>, "CreateFileService"},
18 {1, D<&IDeliveryCacheStorageService::CreateDirectoryService>, "CreateDirectoryService"},
19 {10, D<&IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory>, "EnumerateDeliveryCacheDirectory"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24}
25
26IDeliveryCacheStorageService::~IDeliveryCacheStorageService() = default;
27
28Result IDeliveryCacheStorageService::CreateFileService(
29 OutInterface<IDeliveryCacheFileService> out_interface) {
30 LOG_DEBUG(Service_BCAT, "called");
31
32 *out_interface = std::make_shared<IDeliveryCacheFileService>(system, root);
33 R_SUCCEED();
34}
35
36Result IDeliveryCacheStorageService::CreateDirectoryService(
37 OutInterface<IDeliveryCacheDirectoryService> out_interface) {
38 LOG_DEBUG(Service_BCAT, "called");
39
40 *out_interface = std::make_shared<IDeliveryCacheDirectoryService>(system, root);
41 R_SUCCEED();
42}
43
44Result IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory(
45 Out<s32> out_directory_count,
46 OutArray<DirectoryName, BufferAttr_HipcMapAlias> out_directories) {
47 LOG_DEBUG(Service_BCAT, "called, size={:016X}", out_directories.size());
48
49 *out_directory_count =
50 static_cast<s32>(std::min(out_directories.size(), entries.size() - next_read_index));
51 memcpy(out_directories.data(), entries.data() + next_read_index,
52 *out_directory_count * sizeof(DirectoryName));
53 next_read_index += *out_directory_count;
54 R_SUCCEED();
55}
56
57} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/delivery_cache_storage_service.h b/src/core/hle/service/bcat/delivery_cache_storage_service.h
new file mode 100644
index 000000000..3b8dfb1a3
--- /dev/null
+++ b/src/core/hle/service/bcat/delivery_cache_storage_service.h
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/file_sys/vfs/vfs.h"
7#include "core/hle/service/bcat/bcat_types.h"
8#include "core/hle/service/cmif_types.h"
9#include "core/hle/service/service.h"
10
11namespace Core {
12class System;
13}
14
15namespace Service::BCAT {
16class IDeliveryCacheFileService;
17class IDeliveryCacheDirectoryService;
18
19class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
20public:
21 explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_);
22 ~IDeliveryCacheStorageService() override;
23
24private:
25 Result CreateFileService(OutInterface<IDeliveryCacheFileService> out_interface);
26 Result CreateDirectoryService(OutInterface<IDeliveryCacheDirectoryService> out_interface);
27 Result EnumerateDeliveryCacheDirectory(
28 Out<s32> out_directory_count,
29 OutArray<DirectoryName, BufferAttr_HipcMapAlias> out_directories);
30
31 FileSys::VirtualDir root;
32 std::vector<DirectoryName> entries;
33 std::size_t next_read_index = 0;
34};
35
36} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp b/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp
new file mode 100644
index 000000000..ed393f7a2
--- /dev/null
+++ b/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp
@@ -0,0 +1,34 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/news/newly_arrived_event_holder.h"
5#include "core/hle/service/cmif_serialization.h"
6
7namespace Service::News {
8
9INewlyArrivedEventHolder::INewlyArrivedEventHolder(Core::System& system_)
10 : ServiceFramework{system_, "INewlyArrivedEventHolder"}, service_context{
11 system_,
12 "INewlyArrivedEventHolder"} {
13 // clang-format off
14 static const FunctionInfo functions[] = {
15 {0, D<&INewlyArrivedEventHolder::Get>, "Get"},
16 };
17 // clang-format on
18
19 RegisterHandlers(functions);
20 arrived_event = service_context.CreateEvent("INewlyArrivedEventHolder::ArrivedEvent");
21}
22
23INewlyArrivedEventHolder::~INewlyArrivedEventHolder() {
24 service_context.CloseEvent(arrived_event);
25}
26
27Result INewlyArrivedEventHolder::Get(OutCopyHandle<Kernel::KReadableEvent> out_event) {
28 LOG_INFO(Service_BCAT, "called");
29
30 *out_event = &arrived_event->GetReadableEvent();
31 R_SUCCEED();
32}
33
34} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/newly_arrived_event_holder.h b/src/core/hle/service/bcat/news/newly_arrived_event_holder.h
new file mode 100644
index 000000000..6cc9ae099
--- /dev/null
+++ b/src/core/hle/service/bcat/news/newly_arrived_event_holder.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/kernel_helpers.h"
8#include "core/hle/service/service.h"
9
10namespace Core {
11class System;
12}
13
14namespace Kernel {
15class KEvent;
16class KReadableEvent;
17} // namespace Kernel
18
19namespace Service::News {
20
21class INewlyArrivedEventHolder final : public ServiceFramework<INewlyArrivedEventHolder> {
22public:
23 explicit INewlyArrivedEventHolder(Core::System& system_);
24 ~INewlyArrivedEventHolder() override;
25
26private:
27 Result Get(OutCopyHandle<Kernel::KReadableEvent> out_event);
28
29 Kernel::KEvent* arrived_event;
30 KernelHelpers::ServiceContext service_context;
31};
32
33} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/news_data_service.cpp b/src/core/hle/service/bcat/news/news_data_service.cpp
new file mode 100644
index 000000000..08103c9c3
--- /dev/null
+++ b/src/core/hle/service/bcat/news/news_data_service.cpp
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/news/news_data_service.h"
5
6namespace Service::News {
7
8INewsDataService::INewsDataService(Core::System& system_)
9 : ServiceFramework{system_, "INewsDataService"} {
10 // clang-format off
11 static const FunctionInfo functions[] = {
12 {0, nullptr, "Open"},
13 {1, nullptr, "OpenWithNewsRecordV1"},
14 {2, nullptr, "Read"},
15 {3, nullptr, "GetSize"},
16 {1001, nullptr, "OpenWithNewsRecord"},
17 };
18 // clang-format on
19
20 RegisterHandlers(functions);
21}
22
23INewsDataService::~INewsDataService() = default;
24
25} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/news_data_service.h b/src/core/hle/service/bcat/news/news_data_service.h
new file mode 100644
index 000000000..12082ada4
--- /dev/null
+++ b/src/core/hle/service/bcat/news/news_data_service.h
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/service.h"
7
8namespace Core {
9class System;
10}
11
12namespace Service::News {
13
14class INewsDataService final : public ServiceFramework<INewsDataService> {
15public:
16 explicit INewsDataService(Core::System& system_);
17 ~INewsDataService() override;
18};
19
20} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/news_database_service.cpp b/src/core/hle/service/bcat/news/news_database_service.cpp
new file mode 100644
index 000000000..b94ef0636
--- /dev/null
+++ b/src/core/hle/service/bcat/news/news_database_service.cpp
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/news/news_database_service.h"
5#include "core/hle/service/cmif_serialization.h"
6
7namespace Service::News {
8
9INewsDatabaseService::INewsDatabaseService(Core::System& system_)
10 : ServiceFramework{system_, "INewsDatabaseService"} {
11 // clang-format off
12 static const FunctionInfo functions[] = {
13 {0, nullptr, "GetListV1"},
14 {1, D<&INewsDatabaseService::Count>, "Count"},
15 {2, nullptr, "CountWithKey"},
16 {3, nullptr, "UpdateIntegerValue"},
17 {4, D<&INewsDatabaseService::UpdateIntegerValueWithAddition>, "UpdateIntegerValueWithAddition"},
18 {5, nullptr, "UpdateStringValue"},
19 {1000, D<&INewsDatabaseService::GetList>, "GetList"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24}
25
26INewsDatabaseService::~INewsDatabaseService() = default;
27
28Result INewsDatabaseService::Count(Out<s32> out_count,
29 InBuffer<BufferAttr_HipcPointer> buffer_data) {
30 LOG_WARNING(Service_BCAT, "(STUBBED) called, buffer_size={}", buffer_data.size());
31 *out_count = 0;
32 R_SUCCEED();
33}
34
35Result INewsDatabaseService::UpdateIntegerValueWithAddition(
36 u32 value, InBuffer<BufferAttr_HipcPointer> buffer_data_1,
37 InBuffer<BufferAttr_HipcPointer> buffer_data_2) {
38 LOG_WARNING(Service_BCAT, "(STUBBED) called, value={}, buffer_size_1={}, buffer_data_2={}",
39 value, buffer_data_1.size(), buffer_data_2.size());
40 R_SUCCEED();
41}
42
43Result INewsDatabaseService::GetList(Out<s32> out_count, u32 value,
44 OutBuffer<BufferAttr_HipcMapAlias> out_buffer_data,
45 InBuffer<BufferAttr_HipcPointer> buffer_data_1,
46 InBuffer<BufferAttr_HipcPointer> buffer_data_2) {
47 LOG_WARNING(Service_BCAT, "(STUBBED) called, value={}, buffer_size_1={}, buffer_data_2={}",
48 value, buffer_data_1.size(), buffer_data_2.size());
49 *out_count = 0;
50 R_SUCCEED();
51}
52
53} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/news_database_service.h b/src/core/hle/service/bcat/news/news_database_service.h
new file mode 100644
index 000000000..860b7074c
--- /dev/null
+++ b/src/core/hle/service/bcat/news/news_database_service.h
@@ -0,0 +1,32 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/service.h"
8
9namespace Core {
10class System;
11}
12
13namespace Service::News {
14
15class INewsDatabaseService final : public ServiceFramework<INewsDatabaseService> {
16public:
17 explicit INewsDatabaseService(Core::System& system_);
18 ~INewsDatabaseService() override;
19
20private:
21 Result Count(Out<s32> out_count, InBuffer<BufferAttr_HipcPointer> buffer_data);
22
23 Result UpdateIntegerValueWithAddition(u32 value, InBuffer<BufferAttr_HipcPointer> buffer_data_1,
24 InBuffer<BufferAttr_HipcPointer> buffer_data_2);
25
26 Result GetList(Out<s32> out_count, u32 value,
27 OutBuffer<BufferAttr_HipcMapAlias> out_buffer_data,
28 InBuffer<BufferAttr_HipcPointer> buffer_data_1,
29 InBuffer<BufferAttr_HipcPointer> buffer_data_2);
30};
31
32} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/news_service.cpp b/src/core/hle/service/bcat/news/news_service.cpp
new file mode 100644
index 000000000..bc6c2afd2
--- /dev/null
+++ b/src/core/hle/service/bcat/news/news_service.cpp
@@ -0,0 +1,57 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/news/news_service.h"
5#include "core/hle/service/cmif_serialization.h"
6
7namespace Service::News {
8
9INewsService::INewsService(Core::System& system_) : ServiceFramework{system_, "INewsService"} {
10 // clang-format off
11 static const FunctionInfo functions[] = {
12 {10100, nullptr, "PostLocalNews"},
13 {20100, nullptr, "SetPassphrase"},
14 {30100, D<&INewsService::GetSubscriptionStatus>, "GetSubscriptionStatus"},
15 {30101, nullptr, "GetTopicList"},
16 {30110, nullptr, "Unknown30110"},
17 {30200, D<&INewsService::IsSystemUpdateRequired>, "IsSystemUpdateRequired"},
18 {30201, nullptr, "Unknown30201"},
19 {30210, nullptr, "Unknown30210"},
20 {30300, nullptr, "RequestImmediateReception"},
21 {30400, nullptr, "DecodeArchiveFile"},
22 {30500, nullptr, "Unknown30500"},
23 {30900, nullptr, "Unknown30900"},
24 {30901, nullptr, "Unknown30901"},
25 {30902, nullptr, "Unknown30902"},
26 {40100, nullptr, "SetSubscriptionStatus"},
27 {40101, D<&INewsService::RequestAutoSubscription>, "RequestAutoSubscription"},
28 {40200, nullptr, "ClearStorage"},
29 {40201, nullptr, "ClearSubscriptionStatusAll"},
30 {90100, nullptr, "GetNewsDatabaseDump"},
31 };
32 // clang-format on
33
34 RegisterHandlers(functions);
35}
36
37INewsService::~INewsService() = default;
38
39Result INewsService::GetSubscriptionStatus(Out<u32> out_status,
40 InBuffer<BufferAttr_HipcPointer> buffer_data) {
41 LOG_WARNING(Service_BCAT, "(STUBBED) called, buffer_size={}", buffer_data.size());
42 *out_status = 0;
43 R_SUCCEED();
44}
45
46Result INewsService::IsSystemUpdateRequired(Out<bool> out_is_system_update_required) {
47 LOG_WARNING(Service_BCAT, "(STUBBED) called");
48 *out_is_system_update_required = false;
49 R_SUCCEED();
50}
51
52Result INewsService::RequestAutoSubscription(u64 value) {
53 LOG_WARNING(Service_BCAT, "(STUBBED) called");
54 R_SUCCEED();
55}
56
57} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/news_service.h b/src/core/hle/service/bcat/news/news_service.h
new file mode 100644
index 000000000..f1716a302
--- /dev/null
+++ b/src/core/hle/service/bcat/news/news_service.h
@@ -0,0 +1,28 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/service.h"
8
9namespace Core {
10class System;
11}
12
13namespace Service::News {
14
15class INewsService final : public ServiceFramework<INewsService> {
16public:
17 explicit INewsService(Core::System& system_);
18 ~INewsService() override;
19
20private:
21 Result GetSubscriptionStatus(Out<u32> out_status, InBuffer<BufferAttr_HipcPointer> buffer_data);
22
23 Result IsSystemUpdateRequired(Out<bool> out_is_system_update_required);
24
25 Result RequestAutoSubscription(u64 value);
26};
27
28} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/overwrite_event_holder.cpp b/src/core/hle/service/bcat/news/overwrite_event_holder.cpp
new file mode 100644
index 000000000..1712971e4
--- /dev/null
+++ b/src/core/hle/service/bcat/news/overwrite_event_holder.cpp
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/news/overwrite_event_holder.h"
5#include "core/hle/service/cmif_serialization.h"
6
7namespace Service::News {
8
9IOverwriteEventHolder::IOverwriteEventHolder(Core::System& system_)
10 : ServiceFramework{system_, "IOverwriteEventHolder"}, service_context{system_,
11 "IOverwriteEventHolder"} {
12 // clang-format off
13 static const FunctionInfo functions[] = {
14 {0, D<&IOverwriteEventHolder::Get>, "Get"},
15 };
16 // clang-format on
17
18 RegisterHandlers(functions);
19 overwrite_event = service_context.CreateEvent("IOverwriteEventHolder::OverwriteEvent");
20}
21
22IOverwriteEventHolder::~IOverwriteEventHolder() {
23 service_context.CloseEvent(overwrite_event);
24}
25
26Result IOverwriteEventHolder::Get(OutCopyHandle<Kernel::KReadableEvent> out_event) {
27 LOG_INFO(Service_BCAT, "called");
28
29 *out_event = &overwrite_event->GetReadableEvent();
30 R_SUCCEED();
31}
32
33} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/overwrite_event_holder.h b/src/core/hle/service/bcat/news/overwrite_event_holder.h
new file mode 100644
index 000000000..cdc87d782
--- /dev/null
+++ b/src/core/hle/service/bcat/news/overwrite_event_holder.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/kernel_helpers.h"
8#include "core/hle/service/service.h"
9
10namespace Core {
11class System;
12}
13
14namespace Kernel {
15class KEvent;
16class KReadableEvent;
17} // namespace Kernel
18
19namespace Service::News {
20
21class IOverwriteEventHolder final : public ServiceFramework<IOverwriteEventHolder> {
22public:
23 explicit IOverwriteEventHolder(Core::System& system_);
24 ~IOverwriteEventHolder() override;
25
26private:
27 Result Get(OutCopyHandle<Kernel::KReadableEvent> out_event);
28
29 Kernel::KEvent* overwrite_event;
30 KernelHelpers::ServiceContext service_context;
31};
32
33} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/service_creator.cpp b/src/core/hle/service/bcat/news/service_creator.cpp
new file mode 100644
index 000000000..a1b22c004
--- /dev/null
+++ b/src/core/hle/service/bcat/news/service_creator.cpp
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/bcat/news/newly_arrived_event_holder.h"
5#include "core/hle/service/bcat/news/news_data_service.h"
6#include "core/hle/service/bcat/news/news_database_service.h"
7#include "core/hle/service/bcat/news/news_service.h"
8#include "core/hle/service/bcat/news/overwrite_event_holder.h"
9#include "core/hle/service/bcat/news/service_creator.h"
10#include "core/hle/service/cmif_serialization.h"
11
12namespace Service::News {
13
14IServiceCreator::IServiceCreator(Core::System& system_, u32 permissions_, const char* name_)
15 : ServiceFramework{system_, name_}, permissions{permissions_} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, D<&IServiceCreator::CreateNewsService>, "CreateNewsService"},
19 {1, D<&IServiceCreator::CreateNewlyArrivedEventHolder>, "CreateNewlyArrivedEventHolder"},
20 {2, D<&IServiceCreator::CreateNewsDataService>, "CreateNewsDataService"},
21 {3, D<&IServiceCreator::CreateNewsDatabaseService>, "CreateNewsDatabaseService"},
22 {4, D<&IServiceCreator::CreateOverwriteEventHolder>, "CreateOverwriteEventHolder"},
23 };
24 // clang-format on
25
26 RegisterHandlers(functions);
27}
28
29IServiceCreator::~IServiceCreator() = default;
30
31Result IServiceCreator::CreateNewsService(OutInterface<INewsService> out_interface) {
32 LOG_INFO(Service_BCAT, "called");
33 *out_interface = std::make_shared<INewsService>(system);
34 R_SUCCEED();
35}
36
37Result IServiceCreator::CreateNewlyArrivedEventHolder(
38 OutInterface<INewlyArrivedEventHolder> out_interface) {
39 LOG_INFO(Service_BCAT, "called");
40 *out_interface = std::make_shared<INewlyArrivedEventHolder>(system);
41 R_SUCCEED();
42}
43
44Result IServiceCreator::CreateNewsDataService(OutInterface<INewsDataService> out_interface) {
45 LOG_INFO(Service_BCAT, "called");
46 *out_interface = std::make_shared<INewsDataService>(system);
47 R_SUCCEED();
48}
49
50Result IServiceCreator::CreateNewsDatabaseService(
51 OutInterface<INewsDatabaseService> out_interface) {
52 LOG_INFO(Service_BCAT, "called");
53 *out_interface = std::make_shared<INewsDatabaseService>(system);
54 R_SUCCEED();
55}
56
57Result IServiceCreator::CreateOverwriteEventHolder(
58 OutInterface<IOverwriteEventHolder> out_interface) {
59 LOG_INFO(Service_BCAT, "called");
60 *out_interface = std::make_shared<IOverwriteEventHolder>(system);
61 R_SUCCEED();
62}
63
64} // namespace Service::News
diff --git a/src/core/hle/service/bcat/news/service_creator.h b/src/core/hle/service/bcat/news/service_creator.h
new file mode 100644
index 000000000..5a62e7c1a
--- /dev/null
+++ b/src/core/hle/service/bcat/news/service_creator.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/service.h"
8
9namespace Core {
10class System;
11}
12
13namespace Service::News {
14class INewsService;
15class INewlyArrivedEventHolder;
16class INewsDataService;
17class INewsDatabaseService;
18class IOverwriteEventHolder;
19
20class IServiceCreator final : public ServiceFramework<IServiceCreator> {
21public:
22 explicit IServiceCreator(Core::System& system_, u32 permissions_, const char* name_);
23 ~IServiceCreator() override;
24
25private:
26 Result CreateNewsService(OutInterface<INewsService> out_interface);
27 Result CreateNewlyArrivedEventHolder(OutInterface<INewlyArrivedEventHolder> out_interface);
28 Result CreateNewsDataService(OutInterface<INewsDataService> out_interface);
29 Result CreateNewsDatabaseService(OutInterface<INewsDatabaseService> out_interface);
30 Result CreateOverwriteEventHolder(OutInterface<IOverwriteEventHolder> out_interface);
31
32 u32 permissions;
33};
34
35} // namespace Service::News
diff --git a/src/core/hle/service/bcat/service_creator.cpp b/src/core/hle/service/bcat/service_creator.cpp
new file mode 100644
index 000000000..ca339e5a6
--- /dev/null
+++ b/src/core/hle/service/bcat/service_creator.cpp
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/bcat/bcat_service.h"
5#include "core/hle/service/bcat/delivery_cache_storage_service.h"
6#include "core/hle/service/bcat/service_creator.h"
7#include "core/hle/service/cmif_serialization.h"
8#include "core/hle/service/filesystem/filesystem.h"
9
10namespace Service::BCAT {
11
12std::unique_ptr<BcatBackend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
13 DirectoryGetter getter) {
14 return std::make_unique<NullBcatBackend>(std::move(getter));
15}
16
17IServiceCreator::IServiceCreator(Core::System& system_, const char* name_)
18 : ServiceFramework{system_, name_}, fsc{system.GetFileSystemController()} {
19 // clang-format off
20 static const FunctionInfo functions[] = {
21 {0, D<&IServiceCreator::CreateBcatService>, "CreateBcatService"},
22 {1, D<&IServiceCreator::CreateDeliveryCacheStorageService>, "CreateDeliveryCacheStorageService"},
23 {2, D<&IServiceCreator::CreateDeliveryCacheStorageServiceWithApplicationId>, "CreateDeliveryCacheStorageServiceWithApplicationId"},
24 {3, nullptr, "CreateDeliveryCacheProgressService"},
25 {4, nullptr, "CreateDeliveryCacheProgressServiceWithApplicationId"},
26 };
27 // clang-format on
28
29 RegisterHandlers(functions);
30
31 backend =
32 CreateBackendFromSettings(system_, [this](u64 tid) { return fsc.GetBCATDirectory(tid); });
33}
34
35IServiceCreator::~IServiceCreator() = default;
36
37Result IServiceCreator::CreateBcatService(ClientProcessId process_id,
38 OutInterface<IBcatService> out_interface) {
39 LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
40 *out_interface = std::make_shared<IBcatService>(system, *backend);
41 R_SUCCEED();
42}
43
44Result IServiceCreator::CreateDeliveryCacheStorageService(
45 ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface) {
46 LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
47
48 const auto title_id = system.GetApplicationProcessProgramID();
49 *out_interface =
50 std::make_shared<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
51 R_SUCCEED();
52}
53
54Result IServiceCreator::CreateDeliveryCacheStorageServiceWithApplicationId(
55 u64 application_id, OutInterface<IDeliveryCacheStorageService> out_interface) {
56 LOG_DEBUG(Service_BCAT, "called, application_id={:016X}", application_id);
57 *out_interface = std::make_shared<IDeliveryCacheStorageService>(
58 system, fsc.GetBCATDirectory(application_id));
59 R_SUCCEED();
60}
61
62} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/service_creator.h b/src/core/hle/service/bcat/service_creator.h
new file mode 100644
index 000000000..50e663324
--- /dev/null
+++ b/src/core/hle/service/bcat/service_creator.h
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/service/cmif_types.h"
7#include "core/hle/service/service.h"
8
9namespace Core {
10class System;
11}
12
13namespace Service::FileSystem {
14class FileSystemController;
15}
16
17namespace Service::BCAT {
18class BcatBackend;
19class IBcatService;
20class IDeliveryCacheStorageService;
21
22class IServiceCreator final : public ServiceFramework<IServiceCreator> {
23public:
24 explicit IServiceCreator(Core::System& system_, const char* name_);
25 ~IServiceCreator() override;
26
27private:
28 Result CreateBcatService(ClientProcessId process_id, OutInterface<IBcatService> out_interface);
29
30 Result CreateDeliveryCacheStorageService(
31 ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface);
32
33 Result CreateDeliveryCacheStorageServiceWithApplicationId(
34 u64 application_id, OutInterface<IDeliveryCacheStorageService> out_interface);
35
36 std::unique_ptr<BcatBackend> backend;
37 Service::FileSystem::FileSystemController& fsc;
38};
39
40} // namespace Service::BCAT
diff --git a/src/core/hle/service/glue/time/worker.cpp b/src/core/hle/service/glue/time/worker.cpp
index f44f3077e..8787f2dcd 100644
--- a/src/core/hle/service/glue/time/worker.cpp
+++ b/src/core/hle/service/glue/time/worker.cpp
@@ -7,6 +7,7 @@
7#include "core/hle/service/glue/time/file_timestamp_worker.h" 7#include "core/hle/service/glue/time/file_timestamp_worker.h"
8#include "core/hle/service/glue/time/standard_steady_clock_resource.h" 8#include "core/hle/service/glue/time/standard_steady_clock_resource.h"
9#include "core/hle/service/glue/time/worker.h" 9#include "core/hle/service/glue/time/worker.h"
10#include "core/hle/service/os/multi_wait_utils.h"
10#include "core/hle/service/psc/time/common.h" 11#include "core/hle/service/psc/time/common.h"
11#include "core/hle/service/psc/time/service_manager.h" 12#include "core/hle/service/psc/time/service_manager.h"
12#include "core/hle/service/psc/time/static.h" 13#include "core/hle/service/psc/time/static.h"
@@ -143,82 +144,46 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
143 Common::SetCurrentThreadName("TimeWorker"); 144 Common::SetCurrentThreadName("TimeWorker");
144 Common::SetCurrentThreadPriority(Common::ThreadPriority::Low); 145 Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
145 146
146 enum class EventType {
147 Exit = 0,
148 IpmModuleService_GetEvent = 1,
149 PowerStateChange = 2,
150 SignalAlarms = 3,
151 UpdateLocalSystemClock = 4,
152 UpdateNetworkSystemClock = 5,
153 UpdateEphemeralSystemClock = 6,
154 UpdateSteadyClock = 7,
155 UpdateFileTimestamp = 8,
156 AutoCorrect = 9,
157 Max = 10,
158 };
159
160 s32 num_objs{};
161 std::array<Kernel::KSynchronizationObject*, static_cast<u32>(EventType::Max)> wait_objs{};
162 std::array<EventType, static_cast<u32>(EventType::Max)> wait_indices{};
163
164 const auto AddWaiter{
165 [&](Kernel::KSynchronizationObject* synchronization_object, EventType type) {
166 // Open a new reference to the object.
167 synchronization_object->Open();
168
169 // Insert into the list.
170 wait_indices[num_objs] = type;
171 wait_objs[num_objs++] = synchronization_object;
172 }};
173
174 while (!stop_token.stop_requested()) { 147 while (!stop_token.stop_requested()) {
175 SCOPE_EXIT({ 148 enum class EventType : s32 {
176 for (s32 i = 0; i < num_objs; i++) { 149 Exit = 0,
177 wait_objs[i]->Close(); 150 PowerStateChange = 1,
178 } 151 SignalAlarms = 2,
179 }); 152 UpdateLocalSystemClock = 3,
153 UpdateNetworkSystemClock = 4,
154 UpdateEphemeralSystemClock = 5,
155 UpdateSteadyClock = 6,
156 UpdateFileTimestamp = 7,
157 AutoCorrect = 8,
158 };
159
160 s32 index{};
180 161
181 num_objs = {};
182 wait_objs = {};
183 if (m_pm_state_change_handler.m_priority != 0) { 162 if (m_pm_state_change_handler.m_priority != 0) {
184 AddWaiter(&m_event->GetReadableEvent(), EventType::Exit); 163 // TODO: gIPmModuleService::GetEvent() 1
185 // TODO 164 index = WaitAny(m_system.Kernel(),
186 // AddWaiter(gIPmModuleService::GetEvent(), 1); 165 &m_event->GetReadableEvent(), // 0
187 AddWaiter(&m_alarm_worker.GetEvent(), EventType::PowerStateChange); 166 &m_alarm_worker.GetEvent() // 1
167 );
188 } else { 168 } else {
189 AddWaiter(&m_event->GetReadableEvent(), EventType::Exit); 169 // TODO: gIPmModuleService::GetEvent() 1
190 // TODO 170 index = WaitAny(m_system.Kernel(),
191 // AddWaiter(gIPmModuleService::GetEvent(), 1); 171 &m_event->GetReadableEvent(), // 0
192 AddWaiter(&m_alarm_worker.GetEvent(), EventType::PowerStateChange); 172 &m_alarm_worker.GetEvent(), // 1
193 AddWaiter(&m_alarm_worker.GetTimerEvent().GetReadableEvent(), EventType::SignalAlarms); 173 &m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
194 AddWaiter(m_local_clock_event, EventType::UpdateLocalSystemClock); 174 m_local_clock_event, // 3
195 AddWaiter(m_network_clock_event, EventType::UpdateNetworkSystemClock); 175 m_network_clock_event, // 4
196 AddWaiter(m_ephemeral_clock_event, EventType::UpdateEphemeralSystemClock); 176 m_ephemeral_clock_event, // 5
197 AddWaiter(&m_timer_steady_clock->GetReadableEvent(), EventType::UpdateSteadyClock); 177 &m_timer_steady_clock->GetReadableEvent(), // 6
198 AddWaiter(&m_timer_file_system->GetReadableEvent(), EventType::UpdateFileTimestamp); 178 &m_timer_file_system->GetReadableEvent(), // 7
199 AddWaiter(m_standard_user_auto_correct_clock_event, EventType::AutoCorrect); 179 m_standard_user_auto_correct_clock_event // 8
180 );
200 } 181 }
201 182
202 s32 out_index{-1}; 183 switch (static_cast<EventType>(index)) {
203 Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index, wait_objs.data(),
204 num_objs, -1);
205 ASSERT(out_index >= 0 && out_index < num_objs);
206
207 if (stop_token.stop_requested()) {
208 return;
209 }
210
211 switch (wait_indices[out_index]) {
212 case EventType::Exit: 184 case EventType::Exit:
213 return; 185 return;
214 186
215 case EventType::IpmModuleService_GetEvent:
216 // TODO
217 // IPmModuleService::GetEvent()
218 // clear the event
219 // Handle power state change event
220 break;
221
222 case EventType::PowerStateChange: 187 case EventType::PowerStateChange:
223 m_alarm_worker.GetEvent().Clear(); 188 m_alarm_worker.GetEvent().Clear();
224 if (m_pm_state_change_handler.m_priority <= 1) { 189 if (m_pm_state_change_handler.m_priority <= 1) {
@@ -235,19 +200,19 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
235 m_local_clock_event->Clear(); 200 m_local_clock_event->Clear();
236 201
237 Service::PSC::Time::SystemClockContext context{}; 202 Service::PSC::Time::SystemClockContext context{};
238 auto res = m_local_clock->GetSystemClockContext(&context); 203 R_ASSERT(m_local_clock->GetSystemClockContext(&context));
239 ASSERT(res == ResultSuccess);
240 204
241 m_set_sys->SetUserSystemClockContext(context); 205 m_set_sys->SetUserSystemClockContext(context);
242
243 m_file_timestamp_worker.SetFilesystemPosixTime(); 206 m_file_timestamp_worker.SetFilesystemPosixTime();
244 } break; 207 break;
208 }
245 209
246 case EventType::UpdateNetworkSystemClock: { 210 case EventType::UpdateNetworkSystemClock: {
247 m_network_clock_event->Clear(); 211 m_network_clock_event->Clear();
212
248 Service::PSC::Time::SystemClockContext context{}; 213 Service::PSC::Time::SystemClockContext context{};
249 auto res = m_network_clock->GetSystemClockContext(&context); 214 R_ASSERT(m_network_clock->GetSystemClockContext(&context));
250 ASSERT(res == ResultSuccess); 215
251 m_set_sys->SetNetworkSystemClockContext(context); 216 m_set_sys->SetNetworkSystemClockContext(context);
252 217
253 s64 time{}; 218 s64 time{};
@@ -267,7 +232,8 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
267 } 232 }
268 233
269 m_file_timestamp_worker.SetFilesystemPosixTime(); 234 m_file_timestamp_worker.SetFilesystemPosixTime();
270 } break; 235 break;
236 }
271 237
272 case EventType::UpdateEphemeralSystemClock: { 238 case EventType::UpdateEphemeralSystemClock: {
273 m_ephemeral_clock_event->Clear(); 239 m_ephemeral_clock_event->Clear();
@@ -295,7 +261,8 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
295 if (!g_ig_report_ephemeral_clock_context_set) { 261 if (!g_ig_report_ephemeral_clock_context_set) {
296 g_ig_report_ephemeral_clock_context_set = true; 262 g_ig_report_ephemeral_clock_context_set = true;
297 } 263 }
298 } break; 264 break;
265 }
299 266
300 case EventType::UpdateSteadyClock: 267 case EventType::UpdateSteadyClock:
301 m_timer_steady_clock->Clear(); 268 m_timer_steady_clock->Clear();
@@ -314,21 +281,20 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
314 m_standard_user_auto_correct_clock_event->Clear(); 281 m_standard_user_auto_correct_clock_event->Clear();
315 282
316 bool automatic_correction{}; 283 bool automatic_correction{};
317 auto res = m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled( 284 R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
318 &automatic_correction); 285 &automatic_correction));
319 ASSERT(res == ResultSuccess);
320 286
321 Service::PSC::Time::SteadyClockTimePoint time_point{}; 287 Service::PSC::Time::SteadyClockTimePoint time_point{};
322 res = m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point); 288 R_ASSERT(
323 ASSERT(res == ResultSuccess); 289 m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
324 290
325 m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction); 291 m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
326 m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point); 292 m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
327 } break; 293 break;
294 }
328 295
329 default: 296 default:
330 UNREACHABLE(); 297 UNREACHABLE();
331 break;
332 } 298 }
333 } 299 }
334} 300}
diff --git a/src/core/hle/service/nvdrv/core/container.cpp b/src/core/hle/service/nvdrv/core/container.cpp
index e89cca6f2..9edce03f6 100644
--- a/src/core/hle/service/nvdrv/core/container.cpp
+++ b/src/core/hle/service/nvdrv/core/container.cpp
@@ -49,6 +49,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
49 continue; 49 continue;
50 } 50 }
51 if (session.process == process) { 51 if (session.process == process) {
52 session.ref_count++;
52 return session.id; 53 return session.id;
53 } 54 }
54 } 55 }
@@ -66,6 +67,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
66 } 67 }
67 auto& session = impl->sessions[new_id]; 68 auto& session = impl->sessions[new_id];
68 session.is_active = true; 69 session.is_active = true;
70 session.ref_count = 1;
69 // Optimization 71 // Optimization
70 if (process->IsApplication()) { 72 if (process->IsApplication()) {
71 auto& page_table = process->GetPageTable().GetBasePageTable(); 73 auto& page_table = process->GetPageTable().GetBasePageTable();
@@ -114,8 +116,11 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
114 116
115void Container::CloseSession(SessionId session_id) { 117void Container::CloseSession(SessionId session_id) {
116 std::scoped_lock lk(impl->session_guard); 118 std::scoped_lock lk(impl->session_guard);
117 impl->file.UnmapAllHandles(session_id);
118 auto& session = impl->sessions[session_id.id]; 119 auto& session = impl->sessions[session_id.id];
120 if (--session.ref_count > 0) {
121 return;
122 }
123 impl->file.UnmapAllHandles(session_id);
119 auto& smmu = impl->host1x.MemoryManager(); 124 auto& smmu = impl->host1x.MemoryManager();
120 if (session.has_preallocated_area) { 125 if (session.has_preallocated_area) {
121 const DAddr region_start = session.mapper->GetRegionStart(); 126 const DAddr region_start = session.mapper->GetRegionStart();
diff --git a/src/core/hle/service/nvdrv/core/container.h b/src/core/hle/service/nvdrv/core/container.h
index b4d3938a8..f159ced09 100644
--- a/src/core/hle/service/nvdrv/core/container.h
+++ b/src/core/hle/service/nvdrv/core/container.h
@@ -46,6 +46,7 @@ struct Session {
46 bool has_preallocated_area{}; 46 bool has_preallocated_area{};
47 std::unique_ptr<HeapMapper> mapper{}; 47 std::unique_ptr<HeapMapper> mapper{};
48 bool is_active{}; 48 bool is_active{};
49 s32 ref_count{};
49}; 50};
50 51
51class Container { 52class Container {
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp
index bc1c033c6..453cb5831 100644
--- a/src/core/hle/service/nvdrv/core/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/core/nvmap.cpp
@@ -333,9 +333,13 @@ void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
333 }(); 333 }();
334 334
335 for (auto& [id, handle] : handles_copy) { 335 for (auto& [id, handle] : handles_copy) {
336 if (handle->session_id.id == session_id.id) { 336 {
337 FreeHandle(id, false); 337 std::scoped_lock lk{handle->mutex};
338 if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
339 continue;
340 }
338 } 341 }
342 FreeHandle(id, false);
339 } 343 }
340} 344}
341 345
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index abe95303e..995646e25 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -15,6 +15,22 @@
15 15
16namespace Service::Nvidia::Devices { 16namespace Service::Nvidia::Devices {
17 17
18namespace {
19
20Tegra::BlendMode ConvertBlending(Service::Nvnflinger::LayerBlending blending) {
21 switch (blending) {
22 case Service::Nvnflinger::LayerBlending::None:
23 default:
24 return Tegra::BlendMode::Opaque;
25 case Service::Nvnflinger::LayerBlending::Premultiplied:
26 return Tegra::BlendMode::Premultiplied;
27 case Service::Nvnflinger::LayerBlending::Coverage:
28 return Tegra::BlendMode::Coverage;
29 }
30}
31
32} // namespace
33
18nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core) 34nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
19 : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {} 35 : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
20nvdisp_disp0::~nvdisp_disp0() = default; 36nvdisp_disp0::~nvdisp_disp0() = default;
@@ -56,6 +72,7 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
56 .pixel_format = layer.format, 72 .pixel_format = layer.format,
57 .transform_flags = layer.transform, 73 .transform_flags = layer.transform,
58 .crop_rect = layer.crop_rect, 74 .crop_rect = layer.crop_rect,
75 .blending = ConvertBlending(layer.blending),
59 }); 76 });
60 77
61 for (size_t i = 0; i < layer.acquire_fence.num_fences; i++) { 78 for (size_t i = 0; i < layer.acquire_fence.num_fences; i++) {
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
index e71652cdf..90f7248a0 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -14,24 +14,20 @@
14#include "core/hle/service/nvnflinger/ui/graphic_buffer.h" 14#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
15#include "core/hle/service/vi/layer/vi_layer.h" 15#include "core/hle/service/vi/layer/vi_layer.h"
16#include "core/hle/service/vi/vi_results.h" 16#include "core/hle/service/vi/vi_results.h"
17#include "video_core/gpu.h"
18#include "video_core/host1x/host1x.h"
17 19
18namespace Service::Nvnflinger { 20namespace Service::Nvnflinger {
19 21
20namespace { 22namespace {
21 23
22Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address, 24Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_group,
23 std::unique_ptr<Kernel::KPageGroup>* out_page_group, 25 Core::System& system, u32 size) {
24 Core::System& system, u32 size) {
25 using Core::Memory::YUZU_PAGESIZE; 26 using Core::Memory::YUZU_PAGESIZE;
26 27
27 // Allocate memory for the system shared buffer. 28 // Allocate memory for the system shared buffer.
28 // FIXME: Because the gmmu can only point to cpu addresses, we need
29 // to map this in the application space to allow it to be used.
30 // FIXME: Add proper smmu emulation.
31 // FIXME: This memory belongs to vi's .data section. 29 // FIXME: This memory belongs to vi's .data section.
32 auto& kernel = system.Kernel(); 30 auto& kernel = system.Kernel();
33 auto* process = system.ApplicationProcess();
34 auto& page_table = process->GetPageTable();
35 31
36 // Hold a temporary page group reference while we try to map it. 32 // Hold a temporary page group reference while we try to map it.
37 auto pg = std::make_unique<Kernel::KPageGroup>( 33 auto pg = std::make_unique<Kernel::KPageGroup>(
@@ -43,6 +39,30 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
43 Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure, 39 Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
44 Kernel::KMemoryManager::Direction::FromBack))); 40 Kernel::KMemoryManager::Direction::FromBack)));
45 41
42 // Fill the output data with red.
43 for (auto& block : *pg) {
44 u32* start = system.DeviceMemory().GetPointer<u32>(block.GetAddress());
45 u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
46
47 for (; start < end; start++) {
48 *start = 0xFF0000FF;
49 }
50 }
51
52 // Return the mapped page group.
53 *out_page_group = std::move(pg);
54
55 // We succeeded.
56 R_SUCCEED();
57}
58
59Result MapSharedBufferIntoProcessAddressSpace(Common::ProcessAddress* out_map_address,
60 std::unique_ptr<Kernel::KPageGroup>& pg,
61 Kernel::KProcess* process, Core::System& system) {
62 using Core::Memory::YUZU_PAGESIZE;
63
64 auto& page_table = process->GetPageTable();
65
46 // Get bounds of where mapping is possible. 66 // Get bounds of where mapping is possible.
47 const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart()); 67 const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
48 const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE; 68 const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
@@ -64,9 +84,6 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
64 // Return failure, if necessary 84 // Return failure, if necessary
65 R_UNLESS(i < 64, res); 85 R_UNLESS(i < 64, res);
66 86
67 // Return the mapped page group.
68 *out_page_group = std::move(pg);
69
70 // We succeeded. 87 // We succeeded.
71 R_SUCCEED(); 88 R_SUCCEED();
72} 89}
@@ -135,6 +152,13 @@ Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv, Nvidia::D
135 R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size, nvmap_fd)); 152 R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size, nvmap_fd));
136} 153}
137 154
155void FreeHandle(u32 handle, Nvidia::Module& nvdrv, Nvidia::DeviceFD nvmap_fd) {
156 auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
157 ASSERT(nvmap != nullptr);
158
159 R_ASSERT(FreeNvMapHandle(*nvmap, handle, nvmap_fd));
160}
161
138constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888; 162constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
139constexpr u32 SharedBufferBlockLinearBpp = 4; 163constexpr u32 SharedBufferBlockLinearBpp = 4;
140 164
@@ -186,53 +210,97 @@ FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& fli
186 210
187FbShareBufferManager::~FbShareBufferManager() = default; 211FbShareBufferManager::~FbShareBufferManager() = default;
188 212
189Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) { 213Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id,
214 u64* out_layer_handle, u64 display_id,
215 LayerBlending blending) {
190 std::scoped_lock lk{m_guard}; 216 std::scoped_lock lk{m_guard};
191 217
192 // Ensure we have not already created a buffer. 218 // Ensure we haven't already created.
193 R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed); 219 const u64 aruid = owner_process->GetProcessId();
220 R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied);
221
222 // Allocate memory for the shared buffer if needed.
223 if (!m_buffer_page_group) {
224 R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system,
225 SharedBufferSize));
226
227 // Record buffer id.
228 m_buffer_id = m_next_buffer_id++;
229
230 // Record display id.
231 m_display_id = display_id;
232 }
233
234 // Map into process.
235 Common::ProcessAddress map_address{};
236 R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
237 owner_process, m_system));
194 238
195 // Allocate memory and space for the shared buffer. 239 // Create new session.
196 Common::ProcessAddress map_address; 240 auto [it, was_emplaced] = m_sessions.emplace(aruid, FbShareSession{});
197 R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address), 241 auto& session = it->second;
198 std::addressof(m_buffer_page_group), m_system,
199 SharedBufferSize));
200 242
201 auto& container = m_nvdrv->GetContainer(); 243 auto& container = m_nvdrv->GetContainer();
202 m_session_id = container.OpenSession(m_system.ApplicationProcess()); 244 session.session_id = container.OpenSession(owner_process);
203 m_nvmap_fd = m_nvdrv->Open("/dev/nvmap", m_session_id); 245 session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id);
204 246
205 // Create an nvmap handle for the buffer and assign the memory to it. 247 // Create an nvmap handle for the buffer and assign the memory to it.
206 R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, m_nvmap_fd, 248 R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv,
207 map_address, SharedBufferSize)); 249 session.nvmap_fd, map_address, SharedBufferSize));
208
209 // Record the display id.
210 m_display_id = display_id;
211 250
212 // Create and open a layer for the display. 251 // Create and open a layer for the display.
213 m_layer_id = m_flinger.CreateLayer(m_display_id).value(); 252 session.layer_id = m_flinger.CreateLayer(m_display_id, blending).value();
214 m_flinger.OpenLayer(m_layer_id); 253 m_flinger.OpenLayer(session.layer_id);
215
216 // Set up the buffer.
217 m_buffer_id = m_next_buffer_id++;
218 254
219 // Get the layer. 255 // Get the layer.
220 VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id); 256 VI::Layer* layer = m_flinger.FindLayer(m_display_id, session.layer_id);
221 ASSERT(layer != nullptr); 257 ASSERT(layer != nullptr);
222 258
223 // Get the producer and set preallocated buffers. 259 // Get the producer and set preallocated buffers.
224 auto& producer = layer->GetBufferQueue(); 260 auto& producer = layer->GetBufferQueue();
225 MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle); 261 MakeGraphicBuffer(producer, 0, session.buffer_nvmap_handle);
226 MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle); 262 MakeGraphicBuffer(producer, 1, session.buffer_nvmap_handle);
227 263
228 // Assign outputs. 264 // Assign outputs.
229 *out_buffer_id = m_buffer_id; 265 *out_buffer_id = m_buffer_id;
230 *out_layer_id = m_layer_id; 266 *out_layer_handle = session.layer_id;
231 267
232 // We succeeded. 268 // We succeeded.
233 R_SUCCEED(); 269 R_SUCCEED();
234} 270}
235 271
272void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
273 std::scoped_lock lk{m_guard};
274
275 if (m_buffer_id == 0) {
276 return;
277 }
278
279 const u64 aruid = owner_process->GetProcessId();
280 const auto it = m_sessions.find(aruid);
281 if (it == m_sessions.end()) {
282 return;
283 }
284
285 auto& session = it->second;
286
287 // Destroy the layer.
288 m_flinger.DestroyLayer(session.layer_id);
289
290 // Close nvmap handle.
291 FreeHandle(session.buffer_nvmap_handle, *m_nvdrv, session.nvmap_fd);
292
293 // Close nvmap device.
294 m_nvdrv->Close(session.nvmap_fd);
295
296 // Close session.
297 auto& container = m_nvdrv->GetContainer();
298 container.CloseSession(session.session_id);
299
300 // Erase.
301 m_sessions.erase(it);
302}
303
236Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size, 304Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
237 s32* out_nvmap_handle, 305 s32* out_nvmap_handle,
238 SharedMemoryPoolLayout* out_pool_layout, 306 SharedMemoryPoolLayout* out_pool_layout,
@@ -242,17 +310,18 @@ Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
242 310
243 R_UNLESS(m_buffer_id > 0, VI::ResultNotFound); 311 R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
244 R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound); 312 R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
313 R_UNLESS(m_sessions.contains(applet_resource_user_id), VI::ResultNotFound);
245 314
246 *out_pool_layout = SharedBufferPoolLayout; 315 *out_pool_layout = SharedBufferPoolLayout;
247 *out_buffer_size = SharedBufferSize; 316 *out_buffer_size = SharedBufferSize;
248 *out_nvmap_handle = m_buffer_nvmap_handle; 317 *out_nvmap_handle = m_sessions[applet_resource_user_id].buffer_nvmap_handle;
249 318
250 R_SUCCEED(); 319 R_SUCCEED();
251} 320}
252 321
253Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) { 322Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
254 // Ensure the layer id is valid. 323 // Ensure the layer id is valid.
255 R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound); 324 R_UNLESS(layer_id > 0, VI::ResultNotFound);
256 325
257 // Get the layer. 326 // Get the layer.
258 VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id); 327 VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
@@ -309,6 +378,10 @@ Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
309 android::Status::NoError, 378 android::Status::NoError,
310 VI::ResultOperationFailed); 379 VI::ResultOperationFailed);
311 380
381 ON_RESULT_FAILURE {
382 producer.CancelBuffer(static_cast<s32>(slot), fence);
383 };
384
312 // Queue the buffer to the producer. 385 // Queue the buffer to the producer.
313 android::QueueBufferInput input{}; 386 android::QueueBufferInput input{};
314 android::QueueBufferOutput output{}; 387 android::QueueBufferOutput output{};
@@ -342,4 +415,33 @@ Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadab
342 R_SUCCEED(); 415 R_SUCCEED();
343} 416}
344 417
418Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) {
419 std::vector<u8> capture_buffer(m_system.GPU().GetAppletCaptureBuffer());
420 Common::ScratchBuffer<u32> scratch;
421
422 // TODO: this could be optimized
423 s64 e = -1280 * 768 * 4;
424 for (auto& block : *m_buffer_page_group) {
425 u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
426 u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
427
428 for (; start < end; start++) {
429 *start = 0;
430
431 if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
432 *start = capture_buffer[e];
433 }
434 e++;
435 }
436
437 m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
438 m_system.GPU().InvalidateRegion(addr, end - start);
439 });
440 }
441
442 *out_was_written = true;
443 *out_layer_index = 1;
444 R_SUCCEED();
445}
446
345} // namespace Service::Nvnflinger 447} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
index 033bf4bbe..b79a7d23a 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
@@ -3,9 +3,12 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <map>
7
6#include "common/math_util.h" 8#include "common/math_util.h"
7#include "core/hle/service/nvdrv/core/container.h" 9#include "core/hle/service/nvdrv/core/container.h"
8#include "core/hle/service/nvdrv/nvdata.h" 10#include "core/hle/service/nvdrv/nvdata.h"
11#include "core/hle/service/nvnflinger/hwc_layer.h"
9#include "core/hle/service/nvnflinger/nvnflinger.h" 12#include "core/hle/service/nvnflinger/nvnflinger.h"
10#include "core/hle/service/nvnflinger/ui/fence.h" 13#include "core/hle/service/nvnflinger/ui/fence.h"
11 14
@@ -29,13 +32,18 @@ struct SharedMemoryPoolLayout {
29}; 32};
30static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size"); 33static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
31 34
35struct FbShareSession;
36
32class FbShareBufferManager final { 37class FbShareBufferManager final {
33public: 38public:
34 explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger, 39 explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
35 std::shared_ptr<Nvidia::Module> nvdrv); 40 std::shared_ptr<Nvidia::Module> nvdrv);
36 ~FbShareBufferManager(); 41 ~FbShareBufferManager();
37 42
38 Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id); 43 Result Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle,
44 u64 display_id, LayerBlending blending);
45 void Finalize(Kernel::KProcess* owner_process);
46
39 Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle, 47 Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
40 SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id, 48 SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
41 u64 applet_resource_user_id); 49 u64 applet_resource_user_id);
@@ -45,6 +53,8 @@ public:
45 u32 transform, s32 swap_interval, u64 layer_id, s64 slot); 53 u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
46 Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id); 54 Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
47 55
56 Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index);
57
48private: 58private:
49 Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id); 59 Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
50 60
@@ -52,11 +62,8 @@ private:
52 u64 m_next_buffer_id = 1; 62 u64 m_next_buffer_id = 1;
53 u64 m_display_id = 0; 63 u64 m_display_id = 0;
54 u64 m_buffer_id = 0; 64 u64 m_buffer_id = 0;
55 u64 m_layer_id = 0;
56 u32 m_buffer_nvmap_handle = 0;
57 SharedMemoryPoolLayout m_pool_layout = {}; 65 SharedMemoryPoolLayout m_pool_layout = {};
58 Nvidia::DeviceFD m_nvmap_fd = {}; 66 std::map<u64, FbShareSession> m_sessions;
59 Nvidia::NvCore::SessionId m_session_id = {};
60 std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group; 67 std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
61 68
62 std::mutex m_guard; 69 std::mutex m_guard;
@@ -65,4 +72,11 @@ private:
65 std::shared_ptr<Nvidia::Module> m_nvdrv; 72 std::shared_ptr<Nvidia::Module> m_nvdrv;
66}; 73};
67 74
75struct FbShareSession {
76 Nvidia::DeviceFD nvmap_fd = {};
77 Nvidia::NvCore::SessionId session_id = {};
78 u64 layer_id = {};
79 u32 buffer_nvmap_handle = 0;
80};
81
68} // namespace Service::Nvnflinger 82} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp
index ba2b5c28c..be7eb97a3 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.cpp
+++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp
@@ -86,6 +86,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
86 .height = igbp_buffer.Height(), 86 .height = igbp_buffer.Height(),
87 .stride = igbp_buffer.Stride(), 87 .stride = igbp_buffer.Stride(),
88 .z_index = 0, 88 .z_index = 0,
89 .blending = layer.GetBlending(),
89 .transform = static_cast<android::BufferTransformFlags>(item.transform), 90 .transform = static_cast<android::BufferTransformFlags>(item.transform),
90 .crop_rect = item.crop, 91 .crop_rect = item.crop,
91 .acquire_fence = item.fence, 92 .acquire_fence = item.fence,
diff --git a/src/core/hle/service/nvnflinger/hwc_layer.h b/src/core/hle/service/nvnflinger/hwc_layer.h
index 3af668a25..f71a5d822 100644
--- a/src/core/hle/service/nvnflinger/hwc_layer.h
+++ b/src/core/hle/service/nvnflinger/hwc_layer.h
@@ -11,6 +11,18 @@
11 11
12namespace Service::Nvnflinger { 12namespace Service::Nvnflinger {
13 13
14// hwc_layer_t::blending values
15enum class LayerBlending : u32 {
16 // No blending
17 None = 0x100,
18
19 // ONE / ONE_MINUS_SRC_ALPHA
20 Premultiplied = 0x105,
21
22 // SRC_ALPHA / ONE_MINUS_SRC_ALPHA
23 Coverage = 0x405,
24};
25
14struct HwcLayer { 26struct HwcLayer {
15 u32 buffer_handle; 27 u32 buffer_handle;
16 u32 offset; 28 u32 offset;
@@ -19,6 +31,7 @@ struct HwcLayer {
19 u32 height; 31 u32 height;
20 u32 stride; 32 u32 stride;
21 s32 z_index; 33 s32 z_index;
34 LayerBlending blending;
22 android::BufferTransformFlags transform; 35 android::BufferTransformFlags transform;
23 Common::Rectangle<int> crop_rect; 36 Common::Rectangle<int> crop_rect;
24 android::Fence acquire_fence; 37 android::Fence acquire_fence;
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index d8ba89d43..687ccc9f9 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -157,7 +157,7 @@ bool Nvnflinger::CloseDisplay(u64 display_id) {
157 return true; 157 return true;
158} 158}
159 159
160std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) { 160std::optional<u64> Nvnflinger::CreateLayer(u64 display_id, LayerBlending blending) {
161 const auto lock_guard = Lock(); 161 const auto lock_guard = Lock();
162 auto* const display = FindDisplay(display_id); 162 auto* const display = FindDisplay(display_id);
163 163
@@ -166,13 +166,14 @@ std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
166 } 166 }
167 167
168 const u64 layer_id = next_layer_id++; 168 const u64 layer_id = next_layer_id++;
169 CreateLayerAtId(*display, layer_id); 169 CreateLayerAtId(*display, layer_id, blending);
170 return layer_id; 170 return layer_id;
171} 171}
172 172
173void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { 173void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending) {
174 const auto buffer_id = next_buffer_queue_id++; 174 const auto buffer_id = next_buffer_queue_id++;
175 display.CreateLayer(layer_id, buffer_id, nvdrv->container); 175 display.CreateLayer(layer_id, buffer_id, nvdrv->container);
176 display.FindLayer(layer_id)->SetBlending(blending);
176} 177}
177 178
178bool Nvnflinger::OpenLayer(u64 layer_id) { 179bool Nvnflinger::OpenLayer(u64 layer_id) {
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index c984d55a0..4cf4f069d 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -15,6 +15,7 @@
15#include "common/thread.h" 15#include "common/thread.h"
16#include "core/hle/result.h" 16#include "core/hle/result.h"
17#include "core/hle/service/kernel_helpers.h" 17#include "core/hle/service/kernel_helpers.h"
18#include "core/hle/service/nvnflinger/hwc_layer.h"
18 19
19namespace Common { 20namespace Common {
20class Event; 21class Event;
@@ -72,7 +73,8 @@ public:
72 /// Creates a layer on the specified display and returns the layer ID. 73 /// Creates a layer on the specified display and returns the layer ID.
73 /// 74 ///
74 /// If an invalid display ID is specified, then an empty optional is returned. 75 /// If an invalid display ID is specified, then an empty optional is returned.
75 [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id); 76 [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id,
77 LayerBlending blending = LayerBlending::None);
76 78
77 /// Opens a layer on all displays for the given layer ID. 79 /// Opens a layer on all displays for the given layer ID.
78 bool OpenLayer(u64 layer_id); 80 bool OpenLayer(u64 layer_id);
@@ -128,7 +130,7 @@ private:
128 [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id); 130 [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
129 131
130 /// Creates a layer with the specified layer ID in the desired display. 132 /// Creates a layer with the specified layer ID in the desired display.
131 void CreateLayerAtId(VI::Display& display, u64 layer_id); 133 void CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending);
132 134
133 void SplitVSync(std::stop_token stop_token); 135 void SplitVSync(std::stop_token stop_token);
134 136
diff --git a/src/core/hle/service/event.cpp b/src/core/hle/service/os/event.cpp
index 375660d72..ec52c17fd 100644
--- a/src/core/hle/service/event.cpp
+++ b/src/core/hle/service/os/event.cpp
@@ -2,8 +2,8 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/hle/kernel/k_event.h" 4#include "core/hle/kernel/k_event.h"
5#include "core/hle/service/event.h"
6#include "core/hle/service/kernel_helpers.h" 5#include "core/hle/service/kernel_helpers.h"
6#include "core/hle/service/os/event.h"
7 7
8namespace Service { 8namespace Service {
9 9
diff --git a/src/core/hle/service/event.h b/src/core/hle/service/os/event.h
index cdbc4635a..cdbc4635a 100644
--- a/src/core/hle/service/event.h
+++ b/src/core/hle/service/os/event.h
diff --git a/src/core/hle/service/os/multi_wait.cpp b/src/core/hle/service/os/multi_wait.cpp
new file mode 100644
index 000000000..7b80d28be
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait.cpp
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/kernel/k_hardware_timer.h"
5#include "core/hle/kernel/k_synchronization_object.h"
6#include "core/hle/kernel/kernel.h"
7#include "core/hle/kernel/svc_common.h"
8#include "core/hle/service/os/multi_wait.h"
9
10namespace Service {
11
12MultiWait::MultiWait() = default;
13MultiWait::~MultiWait() = default;
14
15MultiWaitHolder* MultiWait::WaitAny(Kernel::KernelCore& kernel) {
16 return this->TimedWaitImpl(kernel, -1);
17}
18
19MultiWaitHolder* MultiWait::TryWaitAny(Kernel::KernelCore& kernel) {
20 return this->TimedWaitImpl(kernel, 0);
21}
22
23MultiWaitHolder* MultiWait::TimedWaitAny(Kernel::KernelCore& kernel, s64 timeout_ns) {
24 return this->TimedWaitImpl(kernel, kernel.HardwareTimer().GetTick() + timeout_ns);
25}
26
27MultiWaitHolder* MultiWait::TimedWaitImpl(Kernel::KernelCore& kernel, s64 timeout_tick) {
28 std::array<MultiWaitHolder*, Kernel::Svc::ArgumentHandleCountMax> holders{};
29 std::array<Kernel::KSynchronizationObject*, Kernel::Svc::ArgumentHandleCountMax> objects{};
30
31 s32 out_index = -1;
32 s32 num_objects = 0;
33
34 for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it++) {
35 ASSERT(num_objects < Kernel::Svc::ArgumentHandleCountMax);
36 holders[num_objects] = std::addressof(*it);
37 objects[num_objects] = it->GetNativeHandle();
38 num_objects++;
39 }
40
41 Kernel::KSynchronizationObject::Wait(kernel, std::addressof(out_index), objects.data(),
42 num_objects, timeout_tick);
43
44 if (out_index == -1) {
45 return nullptr;
46 } else {
47 return holders[out_index];
48 }
49}
50
51void MultiWait::MoveAll(MultiWait* other) {
52 while (!other->m_wait_list.empty()) {
53 MultiWaitHolder& holder = other->m_wait_list.front();
54 holder.UnlinkFromMultiWait();
55 holder.LinkToMultiWait(this);
56 }
57}
58
59} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait.h b/src/core/hle/service/os/multi_wait.h
new file mode 100644
index 000000000..340c611b5
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait.h
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/os/multi_wait_holder.h"
7
8namespace Kernel {
9class KernelCore;
10}
11
12namespace Service {
13
14class MultiWait final {
15public:
16 explicit MultiWait();
17 ~MultiWait();
18
19public:
20 MultiWaitHolder* WaitAny(Kernel::KernelCore& kernel);
21 MultiWaitHolder* TryWaitAny(Kernel::KernelCore& kernel);
22 MultiWaitHolder* TimedWaitAny(Kernel::KernelCore& kernel, s64 timeout_ns);
23 // TODO: SdkReplyAndReceive?
24
25 void MoveAll(MultiWait* other);
26
27private:
28 MultiWaitHolder* TimedWaitImpl(Kernel::KernelCore& kernel, s64 timeout_tick);
29
30private:
31 friend class MultiWaitHolder;
32 using ListType = Common::IntrusiveListMemberTraits<&MultiWaitHolder::m_list_node>::ListType;
33 ListType m_wait_list{};
34};
35
36} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait_holder.cpp b/src/core/hle/service/os/multi_wait_holder.cpp
new file mode 100644
index 000000000..01efa045b
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait_holder.cpp
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/os/multi_wait.h"
5#include "core/hle/service/os/multi_wait_holder.h"
6
7namespace Service {
8
9void MultiWaitHolder::LinkToMultiWait(MultiWait* multi_wait) {
10 if (m_multi_wait != nullptr) {
11 UNREACHABLE();
12 }
13
14 m_multi_wait = multi_wait;
15 m_multi_wait->m_wait_list.push_back(*this);
16}
17
18void MultiWaitHolder::UnlinkFromMultiWait() {
19 if (m_multi_wait) {
20 m_multi_wait->m_wait_list.erase(m_multi_wait->m_wait_list.iterator_to(*this));
21 m_multi_wait = nullptr;
22 }
23}
24
25} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait_holder.h b/src/core/hle/service/os/multi_wait_holder.h
new file mode 100644
index 000000000..646395a3f
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait_holder.h
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/intrusive_list.h"
7
8namespace Kernel {
9class KSynchronizationObject;
10} // namespace Kernel
11
12namespace Service {
13
14class MultiWait;
15
16class MultiWaitHolder {
17public:
18 explicit MultiWaitHolder(Kernel::KSynchronizationObject* native_handle)
19 : m_native_handle(native_handle) {}
20
21 void LinkToMultiWait(MultiWait* multi_wait);
22 void UnlinkFromMultiWait();
23
24 void SetUserData(uintptr_t user_data) {
25 m_user_data = user_data;
26 }
27
28 uintptr_t GetUserData() const {
29 return m_user_data;
30 }
31
32 Kernel::KSynchronizationObject* GetNativeHandle() const {
33 return m_native_handle;
34 }
35
36private:
37 friend class MultiWait;
38 Common::IntrusiveListNode m_list_node{};
39 MultiWait* m_multi_wait{};
40 Kernel::KSynchronizationObject* m_native_handle{};
41 uintptr_t m_user_data{};
42};
43
44} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait_utils.h b/src/core/hle/service/os/multi_wait_utils.h
new file mode 100644
index 000000000..96d3a10f3
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait_utils.h
@@ -0,0 +1,109 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/os/multi_wait.h"
7
8namespace Service {
9
10namespace impl {
11
12class AutoMultiWaitHolder {
13private:
14 MultiWaitHolder m_holder;
15
16public:
17 template <typename T>
18 explicit AutoMultiWaitHolder(MultiWait* multi_wait, T&& arg) : m_holder(arg) {
19 m_holder.LinkToMultiWait(multi_wait);
20 }
21
22 ~AutoMultiWaitHolder() {
23 m_holder.UnlinkFromMultiWait();
24 }
25
26 std::pair<MultiWaitHolder*, int> ConvertResult(const std::pair<MultiWaitHolder*, int> result,
27 int index) {
28 if (result.first == std::addressof(m_holder)) {
29 return std::make_pair(static_cast<MultiWaitHolder*>(nullptr), index);
30 } else {
31 return result;
32 }
33 }
34};
35
36using WaitAnyFunction = decltype(&MultiWait::WaitAny);
37
38inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
39 MultiWait* multi_wait, WaitAnyFunction func,
40 int) {
41 return std::pair<MultiWaitHolder*, int>((multi_wait->*func)(kernel), -1);
42}
43
44template <typename T, typename... Args>
45inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
46 MultiWait* multi_wait, WaitAnyFunction func,
47 int index, T&& x, Args&&... args) {
48 AutoMultiWaitHolder holder(multi_wait, std::forward<T>(x));
49 return holder.ConvertResult(
50 WaitAnyImpl(kernel, multi_wait, func, index + 1, std::forward<Args>(args)...), index);
51}
52
53template <typename... Args>
54inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
55 MultiWait* multi_wait, WaitAnyFunction func,
56 Args&&... args) {
57 return WaitAnyImpl(kernel, multi_wait, func, 0, std::forward<Args>(args)...);
58}
59
60template <typename... Args>
61inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
62 WaitAnyFunction func, Args&&... args) {
63 MultiWait temp_multi_wait;
64 return WaitAnyImpl(kernel, std::addressof(temp_multi_wait), func, 0,
65 std::forward<Args>(args)...);
66}
67
68class NotBoolButInt {
69public:
70 constexpr NotBoolButInt(int v) : m_value(v) {}
71 constexpr operator int() const {
72 return m_value;
73 }
74 explicit operator bool() const = delete;
75
76private:
77 int m_value;
78};
79
80} // namespace impl
81
82template <typename... Args>
83 requires(sizeof...(Args) > 0)
84inline std::pair<MultiWaitHolder*, int> WaitAny(Kernel::KernelCore& kernel, MultiWait* multi_wait,
85 Args&&... args) {
86 return impl::WaitAnyImpl(kernel, &MultiWait::WaitAny, multi_wait, std::forward<Args>(args)...);
87}
88
89template <typename... Args>
90 requires(sizeof...(Args) > 0)
91inline int WaitAny(Kernel::KernelCore& kernel, Args&&... args) {
92 return impl::WaitAnyImpl(kernel, &MultiWait::WaitAny, std::forward<Args>(args)...).second;
93}
94
95template <typename... Args>
96 requires(sizeof...(Args) > 0)
97inline std::pair<MultiWaitHolder*, int> TryWaitAny(Kernel::KernelCore& kernel,
98 MultiWait* multi_wait, Args&&... args) {
99 return impl::WaitAnyImpl(kernel, &MultiWait::TryWaitAny, multi_wait,
100 std::forward<Args>(args)...);
101}
102
103template <typename... Args>
104 requires(sizeof...(Args) > 0)
105inline impl::NotBoolButInt TryWaitAny(Kernel::KernelCore& kernel, Args&&... args) {
106 return impl::WaitAnyImpl(kernel, &MultiWait::TryWaitAny, std::forward<Args>(args)...).second;
107}
108
109} // namespace Service
diff --git a/src/core/hle/service/mutex.cpp b/src/core/hle/service/os/mutex.cpp
index b0ff71d1b..6009f4866 100644
--- a/src/core/hle/service/mutex.cpp
+++ b/src/core/hle/service/os/mutex.cpp
@@ -4,7 +4,7 @@
4#include "core/core.h" 4#include "core/core.h"
5#include "core/hle/kernel/k_event.h" 5#include "core/hle/kernel/k_event.h"
6#include "core/hle/kernel/k_synchronization_object.h" 6#include "core/hle/kernel/k_synchronization_object.h"
7#include "core/hle/service/mutex.h" 7#include "core/hle/service/os/mutex.h"
8 8
9namespace Service { 9namespace Service {
10 10
diff --git a/src/core/hle/service/mutex.h b/src/core/hle/service/os/mutex.h
index 95ac9b117..95ac9b117 100644
--- a/src/core/hle/service/mutex.h
+++ b/src/core/hle/service/os/mutex.h
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index 8ef49387d..8c7f94c8c 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -20,50 +20,91 @@
20 20
21namespace Service { 21namespace Service {
22 22
23constexpr size_t MaximumWaitObjects = 0x40; 23enum class UserDataTag {
24
25enum HandleType {
26 Port, 24 Port,
27 Session, 25 Session,
28 DeferEvent, 26 DeferEvent,
29 Event,
30}; 27};
31 28
32ServerManager::ServerManager(Core::System& system) : m_system{system}, m_serve_mutex{system} { 29class Port : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Port> {
30public:
31 explicit Port(Kernel::KServerPort* server_port, SessionRequestHandlerFactory&& handler_factory)
32 : MultiWaitHolder(server_port), m_handler_factory(std::move(handler_factory)) {
33 this->SetUserData(static_cast<uintptr_t>(UserDataTag::Port));
34 }
35
36 ~Port() {
37 this->GetNativeHandle()->Close();
38 }
39
40 SessionRequestHandlerPtr CreateHandler() {
41 return m_handler_factory();
42 }
43
44private:
45 const SessionRequestHandlerFactory m_handler_factory;
46};
47
48class Session : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Session> {
49public:
50 explicit Session(Kernel::KServerSession* server_session,
51 std::shared_ptr<SessionRequestManager>&& manager)
52 : MultiWaitHolder(server_session), m_manager(std::move(manager)) {
53 this->SetUserData(static_cast<uintptr_t>(UserDataTag::Session));
54 }
55
56 ~Session() {
57 this->GetNativeHandle()->Close();
58 }
59
60 std::shared_ptr<SessionRequestManager>& GetManager() {
61 return m_manager;
62 }
63
64 std::shared_ptr<HLERequestContext>& GetContext() {
65 return m_context;
66 }
67
68private:
69 std::shared_ptr<SessionRequestManager> m_manager;
70 std::shared_ptr<HLERequestContext> m_context;
71};
72
73ServerManager::ServerManager(Core::System& system) : m_system{system}, m_selection_mutex{system} {
33 // Initialize event. 74 // Initialize event.
34 m_event = Kernel::KEvent::Create(system.Kernel()); 75 m_wakeup_event = Kernel::KEvent::Create(system.Kernel());
35 m_event->Initialize(nullptr); 76 m_wakeup_event->Initialize(nullptr);
36 77
37 // Register event. 78 // Register event.
38 Kernel::KEvent::Register(system.Kernel(), m_event); 79 Kernel::KEvent::Register(system.Kernel(), m_wakeup_event);
80
81 // Link to holder.
82 m_wakeup_holder.emplace(std::addressof(m_wakeup_event->GetReadableEvent()));
83 m_wakeup_holder->LinkToMultiWait(std::addressof(m_deferred_list));
39} 84}
40 85
41ServerManager::~ServerManager() { 86ServerManager::~ServerManager() {
42 // Signal stop. 87 // Signal stop.
43 m_stop_source.request_stop(); 88 m_stop_source.request_stop();
44 m_event->Signal(); 89 m_wakeup_event->Signal();
45 90
46 // Wait for processing to stop. 91 // Wait for processing to stop.
47 m_stopped.Wait(); 92 m_stopped.Wait();
48 m_threads.clear(); 93 m_threads.clear();
49 94
50 // Clean up server ports. 95 // Clean up ports.
51 for (const auto& [port, handler] : m_ports) { 96 for (auto it = m_servers.begin(); it != m_servers.end(); it = m_servers.erase(it)) {
52 port->Close(); 97 delete std::addressof(*it);
53 } 98 }
54 99
55 // Clean up sessions. 100 // Clean up sessions.
56 for (const auto& [session, manager] : m_sessions) { 101 for (auto it = m_sessions.begin(); it != m_sessions.end(); it = m_sessions.erase(it)) {
57 session->Close(); 102 delete std::addressof(*it);
58 }
59
60 for (const auto& request : m_deferrals) {
61 request.session->Close();
62 } 103 }
63 104
64 // Close event. 105 // Close wakeup event.
65 m_event->GetReadableEvent().Close(); 106 m_wakeup_event->GetReadableEvent().Close();
66 m_event->Close(); 107 m_wakeup_event->Close();
67 108
68 if (m_deferral_event) { 109 if (m_deferral_event) {
69 m_deferral_event->GetReadableEvent().Close(); 110 m_deferral_event->GetReadableEvent().Close();
@@ -75,19 +116,19 @@ void ServerManager::RunServer(std::unique_ptr<ServerManager>&& server_manager) {
75 server_manager->m_system.RunServer(std::move(server_manager)); 116 server_manager->m_system.RunServer(std::move(server_manager));
76} 117}
77 118
78Result ServerManager::RegisterSession(Kernel::KServerSession* session, 119Result ServerManager::RegisterSession(Kernel::KServerSession* server_session,
79 std::shared_ptr<SessionRequestManager> manager) { 120 std::shared_ptr<SessionRequestManager> manager) {
80 ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
81
82 // We are taking ownership of the server session, so don't open it. 121 // We are taking ownership of the server session, so don't open it.
122 auto* session = new Session(server_session, std::move(manager));
123
83 // Begin tracking the server session. 124 // Begin tracking the server session.
84 { 125 {
85 std::scoped_lock ll{m_list_mutex}; 126 std::scoped_lock ll{m_deferred_list_mutex};
86 m_sessions.emplace(session, std::move(manager)); 127 m_sessions.push_back(*session);
87 } 128 }
88 129
89 // Signal the wakeup event. 130 // Register to wait on the session.
90 m_event->Signal(); 131 this->LinkToDeferredList(session);
91 132
92 R_SUCCEED(); 133 R_SUCCEED();
93} 134}
@@ -95,21 +136,22 @@ Result ServerManager::RegisterSession(Kernel::KServerSession* session,
95Result ServerManager::RegisterNamedService(const std::string& service_name, 136Result ServerManager::RegisterNamedService(const std::string& service_name,
96 SessionRequestHandlerFactory&& handler_factory, 137 SessionRequestHandlerFactory&& handler_factory,
97 u32 max_sessions) { 138 u32 max_sessions) {
98 ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
99
100 // Add the new server to sm: and get the moved server port. 139 // Add the new server to sm: and get the moved server port.
101 Kernel::KServerPort* server_port{}; 140 Kernel::KServerPort* server_port{};
102 R_ASSERT(m_system.ServiceManager().RegisterService(std::addressof(server_port), service_name, 141 R_ASSERT(m_system.ServiceManager().RegisterService(std::addressof(server_port), service_name,
103 max_sessions, handler_factory)); 142 max_sessions, handler_factory));
104 143
144 // We are taking ownership of the server port, so don't open it.
145 auto* server = new Port(server_port, std::move(handler_factory));
146
105 // Begin tracking the server port. 147 // Begin tracking the server port.
106 { 148 {
107 std::scoped_lock ll{m_list_mutex}; 149 std::scoped_lock ll{m_deferred_list_mutex};
108 m_ports.emplace(server_port, std::move(handler_factory)); 150 m_servers.push_back(*server);
109 } 151 }
110 152
111 // Signal the wakeup event. 153 // Register to wait on the server port.
112 m_event->Signal(); 154 this->LinkToDeferredList(server);
113 155
114 R_SUCCEED(); 156 R_SUCCEED();
115} 157}
@@ -127,8 +169,6 @@ Result ServerManager::RegisterNamedService(const std::string& service_name,
127Result ServerManager::ManageNamedPort(const std::string& service_name, 169Result ServerManager::ManageNamedPort(const std::string& service_name,
128 SessionRequestHandlerFactory&& handler_factory, 170 SessionRequestHandlerFactory&& handler_factory,
129 u32 max_sessions) { 171 u32 max_sessions) {
130 ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
131
132 // Create a new port. 172 // Create a new port.
133 auto* port = Kernel::KPort::Create(m_system.Kernel()); 173 auto* port = Kernel::KPort::Create(m_system.Kernel());
134 port->Initialize(max_sessions, false, 0); 174 port->Initialize(max_sessions, false, 0);
@@ -149,12 +189,18 @@ Result ServerManager::ManageNamedPort(const std::string& service_name,
149 // Open a new reference to the server port. 189 // Open a new reference to the server port.
150 port->GetServerPort().Open(); 190 port->GetServerPort().Open();
151 191
152 // Begin tracking the server port. 192 // Transfer ownership into a new port object.
193 auto* server = new Port(std::addressof(port->GetServerPort()), std::move(handler_factory));
194
195 // Begin tracking the port.
153 { 196 {
154 std::scoped_lock ll{m_list_mutex}; 197 std::scoped_lock ll{m_deferred_list_mutex};
155 m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler_factory)); 198 m_servers.push_back(*server);
156 } 199 }
157 200
201 // Register to wait on the port.
202 this->LinkToDeferredList(server);
203
158 // We succeeded. 204 // We succeeded.
159 R_SUCCEED(); 205 R_SUCCEED();
160} 206}
@@ -173,6 +219,11 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
173 // Set the output. 219 // Set the output.
174 *out_event = m_deferral_event; 220 *out_event = m_deferral_event;
175 221
222 // Register to wait on the event.
223 m_deferral_holder.emplace(std::addressof(m_deferral_event->GetReadableEvent()));
224 m_deferral_holder->SetUserData(static_cast<uintptr_t>(UserDataTag::DeferEvent));
225 this->LinkToDeferredList(std::addressof(*m_deferral_holder));
226
176 // We succeeded. 227 // We succeeded.
177 R_SUCCEED(); 228 R_SUCCEED();
178} 229}
@@ -191,270 +242,185 @@ Result ServerManager::LoopProcess() {
191 R_RETURN(this->LoopProcessImpl()); 242 R_RETURN(this->LoopProcessImpl());
192} 243}
193 244
194Result ServerManager::LoopProcessImpl() { 245void ServerManager::LinkToDeferredList(MultiWaitHolder* holder) {
195 while (!m_stop_source.stop_requested()) { 246 // Link.
196 R_TRY(this->WaitAndProcessImpl()); 247 {
248 std::scoped_lock lk{m_deferred_list_mutex};
249 holder->LinkToMultiWait(std::addressof(m_deferred_list));
197 } 250 }
198 251
199 R_SUCCEED(); 252 // Signal the wakeup event.
253 m_wakeup_event->Signal();
200} 254}
201 255
202Result ServerManager::WaitAndProcessImpl() { 256void ServerManager::LinkDeferred() {
203 Kernel::KScopedAutoObject<Kernel::KSynchronizationObject> wait_obj; 257 std::scoped_lock lk{m_deferred_list_mutex};
204 HandleType wait_type{}; 258 m_multi_wait.MoveAll(std::addressof(m_deferred_list));
259}
205 260
261MultiWaitHolder* ServerManager::WaitSignaled() {
206 // Ensure we are the only thread waiting for this server. 262 // Ensure we are the only thread waiting for this server.
207 std::unique_lock sl{m_serve_mutex}; 263 std::scoped_lock lk{m_selection_mutex};
208 264
209 // If we're done, return before we start waiting. 265 while (true) {
210 R_SUCCEED_IF(m_stop_source.stop_requested()); 266 this->LinkDeferred();
211 267
212 // Wait for a tracked object to become signaled. 268 // If we're done, return before we start waiting.
213 { 269 if (m_stop_source.stop_requested()) {
214 s32 num_objs{}; 270 return nullptr;
215 std::array<HandleType, MaximumWaitObjects> wait_types{};
216 std::array<Kernel::KSynchronizationObject*, MaximumWaitObjects> wait_objs{};
217
218 const auto AddWaiter{
219 [&](Kernel::KSynchronizationObject* synchronization_object, HandleType type) {
220 // Open a new reference to the object.
221 synchronization_object->Open();
222
223 // Insert into the list.
224 wait_types[num_objs] = type;
225 wait_objs[num_objs++] = synchronization_object;
226 }};
227
228 {
229 std::scoped_lock ll{m_list_mutex};
230
231 // Add all of our ports.
232 for (const auto& [port, handler] : m_ports) {
233 AddWaiter(port, HandleType::Port);
234 }
235
236 // Add all of our sessions.
237 for (const auto& [session, manager] : m_sessions) {
238 AddWaiter(session, HandleType::Session);
239 }
240 } 271 }
241 272
242 // Add the deferral wakeup event. 273 auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
243 if (m_deferral_event != nullptr) { 274 if (selected == std::addressof(*m_wakeup_holder)) {
244 AddWaiter(std::addressof(m_deferral_event->GetReadableEvent()), HandleType::DeferEvent); 275 // Clear and restart if we were woken up.
276 m_wakeup_event->Clear();
277 } else {
278 // Unlink and handle the event.
279 selected->UnlinkFromMultiWait();
280 return selected;
245 } 281 }
282 }
283}
246 284
247 // Add the wakeup event. 285Result ServerManager::Process(MultiWaitHolder* holder) {
248 AddWaiter(std::addressof(m_event->GetReadableEvent()), HandleType::Event); 286 switch (static_cast<UserDataTag>(holder->GetUserData())) {
249 287 case UserDataTag::Session:
250 // Clean up extra references on exit. 288 R_RETURN(this->OnSessionEvent(static_cast<Session*>(holder)));
251 SCOPE_EXIT({ 289 case UserDataTag::Port:
252 for (s32 i = 0; i < num_objs; i++) { 290 R_RETURN(this->OnPortEvent(static_cast<Port*>(holder)));
253 wait_objs[i]->Close(); 291 case UserDataTag::DeferEvent:
254 } 292 R_RETURN(this->OnDeferralEvent());
255 }); 293 default:
256 294 UNREACHABLE();
257 // Wait for a signal. 295 }
258 s32 out_index{-1}; 296}
259 R_TRY_CATCH(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index,
260 wait_objs.data(), num_objs, -1)) {
261 R_CATCH(Kernel::ResultSessionClosed) {
262 // On session closed, index is updated and we don't want to return an error.
263 }
264 }
265 R_END_TRY_CATCH;
266 ASSERT(out_index >= 0 && out_index < num_objs);
267 297
268 // Set the output index. 298bool ServerManager::WaitAndProcessImpl() {
269 wait_obj = wait_objs[out_index]; 299 if (auto* signaled_holder = this->WaitSignaled(); signaled_holder != nullptr) {
270 wait_type = wait_types[out_index]; 300 R_ASSERT(this->Process(signaled_holder));
301 return true;
302 } else {
303 return false;
271 } 304 }
305}
272 306
273 // Process what we just received, temporarily removing the object so it is 307Result ServerManager::LoopProcessImpl() {
274 // not processed concurrently by another thread. 308 while (!m_stop_source.stop_requested()) {
275 { 309 this->WaitAndProcessImpl();
276 switch (wait_type) {
277 case HandleType::Port: {
278 // Port signaled.
279 auto* port = wait_obj->DynamicCast<Kernel::KServerPort*>();
280 SessionRequestHandlerFactory handler_factory;
281
282 // Remove from tracking.
283 {
284 std::scoped_lock ll{m_list_mutex};
285 ASSERT(m_ports.contains(port));
286 m_ports.at(port).swap(handler_factory);
287 m_ports.erase(port);
288 }
289
290 // Allow other threads to serve.
291 sl.unlock();
292
293 // Finish.
294 R_RETURN(this->OnPortEvent(port, std::move(handler_factory)));
295 }
296 case HandleType::Session: {
297 // Session signaled.
298 auto* session = wait_obj->DynamicCast<Kernel::KServerSession*>();
299 std::shared_ptr<SessionRequestManager> manager;
300
301 // Remove from tracking.
302 {
303 std::scoped_lock ll{m_list_mutex};
304 ASSERT(m_sessions.contains(session));
305 m_sessions.at(session).swap(manager);
306 m_sessions.erase(session);
307 }
308
309 // Allow other threads to serve.
310 sl.unlock();
311
312 // Finish.
313 R_RETURN(this->OnSessionEvent(session, std::move(manager)));
314 }
315 case HandleType::DeferEvent: {
316 // Clear event.
317 ASSERT(R_SUCCEEDED(m_deferral_event->Clear()));
318
319 // Drain the list of deferrals while we process.
320 std::list<RequestState> deferrals;
321 {
322 std::scoped_lock ll{m_list_mutex};
323 m_deferrals.swap(deferrals);
324 }
325
326 // Allow other threads to serve.
327 sl.unlock();
328
329 // Finish.
330 R_RETURN(this->OnDeferralEvent(std::move(deferrals)));
331 }
332 case HandleType::Event: {
333 // Clear event and finish.
334 R_RETURN(m_event->Clear());
335 }
336 default: {
337 UNREACHABLE();
338 }
339 }
340 } 310 }
311
312 R_SUCCEED();
341} 313}
342 314
343Result ServerManager::OnPortEvent(Kernel::KServerPort* port, 315Result ServerManager::OnPortEvent(Port* server) {
344 SessionRequestHandlerFactory&& handler_factory) {
345 // Accept a new server session. 316 // Accept a new server session.
346 Kernel::KServerSession* session = port->AcceptSession(); 317 auto* server_port = static_cast<Kernel::KServerPort*>(server->GetNativeHandle());
347 ASSERT(session != nullptr); 318 Kernel::KServerSession* server_session = server_port->AcceptSession();
319 ASSERT(server_session != nullptr);
348 320
349 // Create the session manager and install the handler. 321 // Create the session manager and install the handler.
350 auto manager = std::make_shared<SessionRequestManager>(m_system.Kernel(), *this); 322 auto manager = std::make_shared<SessionRequestManager>(m_system.Kernel(), *this);
351 manager->SetSessionHandler(handler_factory()); 323 manager->SetSessionHandler(server->CreateHandler());
352 324
353 // Track the server session. 325 // Create and register the new session.
354 { 326 this->RegisterSession(server_session, std::move(manager));
355 std::scoped_lock ll{m_list_mutex};
356 m_ports.emplace(port, std::move(handler_factory));
357 m_sessions.emplace(session, std::move(manager));
358 }
359 327
360 // Signal the wakeup event. 328 // Resume tracking the port.
361 m_event->Signal(); 329 this->LinkToDeferredList(server);
362 330
363 // We succeeded. 331 // We succeeded.
364 R_SUCCEED(); 332 R_SUCCEED();
365} 333}
366 334
367Result ServerManager::OnSessionEvent(Kernel::KServerSession* session, 335Result ServerManager::OnSessionEvent(Session* session) {
368 std::shared_ptr<SessionRequestManager>&& manager) { 336 Result res = ResultSuccess;
369 Result rc{ResultSuccess};
370 337
371 // Try to receive a message. 338 // Try to receive a message.
372 std::shared_ptr<HLERequestContext> context; 339 auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
373 rc = session->ReceiveRequestHLE(&context, manager); 340 res = server_session->ReceiveRequestHLE(&session->GetContext(), session->GetManager());
374 341
375 // If the session has been closed, we're done. 342 // If the session has been closed, we're done.
376 if (rc == Kernel::ResultSessionClosed) { 343 if (res == Kernel::ResultSessionClosed) {
377 // Close the session. 344 this->DestroySession(session);
378 session->Close();
379
380 // Finish.
381 R_SUCCEED(); 345 R_SUCCEED();
382 } 346 }
383 ASSERT(R_SUCCEEDED(rc));
384 347
385 RequestState request{ 348 R_ASSERT(res);
386 .session = session,
387 .context = std::move(context),
388 .manager = std::move(manager),
389 };
390 349
391 // Complete the sync request with deferral handling. 350 // Complete the sync request with deferral handling.
392 R_RETURN(this->CompleteSyncRequest(std::move(request))); 351 R_RETURN(this->CompleteSyncRequest(session));
393} 352}
394 353
395Result ServerManager::CompleteSyncRequest(RequestState&& request) { 354Result ServerManager::CompleteSyncRequest(Session* session) {
396 Result rc{ResultSuccess}; 355 Result res = ResultSuccess;
397 Result service_rc{ResultSuccess}; 356 Result service_res = ResultSuccess;
398 357
399 // Mark the request as not deferred. 358 // Mark the request as not deferred.
400 request.context->SetIsDeferred(false); 359 session->GetContext()->SetIsDeferred(false);
401 360
402 // Complete the request. We have exclusive access to this session. 361 // Complete the request. We have exclusive access to this session.
403 service_rc = request.manager->CompleteSyncRequest(request.session, *request.context); 362 auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
363 service_res =
364 session->GetManager()->CompleteSyncRequest(server_session, *session->GetContext());
404 365
405 // If we've been deferred, we're done. 366 // If we've been deferred, we're done.
406 if (request.context->GetIsDeferred()) { 367 if (session->GetContext()->GetIsDeferred()) {
407 // Insert into deferral list. 368 // Insert into deferred session list.
408 std::scoped_lock ll{m_list_mutex}; 369 std::scoped_lock ll{m_deferred_list_mutex};
409 m_deferrals.emplace_back(std::move(request)); 370 m_deferred_sessions.push_back(session);
410 371
411 // Finish. 372 // Finish.
412 R_SUCCEED(); 373 R_SUCCEED();
413 } 374 }
414 375
415 // Send the reply. 376 // Send the reply.
416 rc = request.session->SendReplyHLE(); 377 res = server_session->SendReplyHLE();
417 378
418 // If the session has been closed, we're done. 379 // If the session has been closed, we're done.
419 if (rc == Kernel::ResultSessionClosed || service_rc == IPC::ResultSessionClosed) { 380 if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
420 // Close the session. 381 this->DestroySession(session);
421 request.session->Close();
422
423 // Finish.
424 R_SUCCEED(); 382 R_SUCCEED();
425 } 383 }
426 384
427 ASSERT(R_SUCCEEDED(rc)); 385 R_ASSERT(res);
428 ASSERT(R_SUCCEEDED(service_rc)); 386 R_ASSERT(service_res);
429
430 // Reinsert the session.
431 {
432 std::scoped_lock ll{m_list_mutex};
433 m_sessions.emplace(request.session, std::move(request.manager));
434 }
435 387
436 // Signal the wakeup event. 388 // We succeeded, so we can process future messages on this session.
437 m_event->Signal(); 389 this->LinkToDeferredList(session);
438 390
439 // We succeeded.
440 R_SUCCEED(); 391 R_SUCCEED();
441} 392}
442 393
443Result ServerManager::OnDeferralEvent(std::list<RequestState>&& deferrals) { 394Result ServerManager::OnDeferralEvent() {
444 ON_RESULT_FAILURE { 395 // Clear event before grabbing the list.
445 std::scoped_lock ll{m_list_mutex}; 396 m_deferral_event->Clear();
446 m_deferrals.splice(m_deferrals.end(), deferrals);
447 };
448 397
449 while (!deferrals.empty()) { 398 // Get and clear list.
450 RequestState request = deferrals.front(); 399 const auto deferrals = [&] {
451 deferrals.pop_front(); 400 std::scoped_lock lk{m_deferred_list_mutex};
401 return std::move(m_deferred_sessions);
402 }();
452 403
453 // Try again to complete the request. 404 // Relink deferral event.
454 R_TRY(this->CompleteSyncRequest(std::move(request))); 405 this->LinkToDeferredList(std::addressof(*m_deferral_holder));
406
407 // For each session, try again to complete the request.
408 for (auto* session : deferrals) {
409 R_ASSERT(this->CompleteSyncRequest(session));
455 } 410 }
456 411
457 R_SUCCEED(); 412 R_SUCCEED();
458} 413}
459 414
415void ServerManager::DestroySession(Session* session) {
416 // Unlink.
417 {
418 std::scoped_lock lk{m_deferred_list_mutex};
419 m_sessions.erase(m_sessions.iterator_to(*session));
420 }
421
422 // Free the session.
423 delete session;
424}
425
460} // namespace Service 426} // namespace Service
diff --git a/src/core/hle/service/server_manager.h b/src/core/hle/service/server_manager.h
index c4bc07262..5173ce46e 100644
--- a/src/core/hle/service/server_manager.h
+++ b/src/core/hle/service/server_manager.h
@@ -3,18 +3,17 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <functional>
7#include <list> 6#include <list>
8#include <map>
9#include <mutex> 7#include <mutex>
10#include <string_view> 8#include <optional>
11#include <vector> 9#include <vector>
12 10
13#include "common/polyfill_thread.h" 11#include "common/polyfill_thread.h"
14#include "common/thread.h" 12#include "common/thread.h"
15#include "core/hle/result.h" 13#include "core/hle/result.h"
16#include "core/hle/service/hle_ipc.h" 14#include "core/hle/service/hle_ipc.h"
17#include "core/hle/service/mutex.h" 15#include "core/hle/service/os/multi_wait.h"
16#include "core/hle/service/os/mutex.h"
18 17
19namespace Core { 18namespace Core {
20class System; 19class System;
@@ -24,11 +23,13 @@ namespace Kernel {
24class KEvent; 23class KEvent;
25class KServerPort; 24class KServerPort;
26class KServerSession; 25class KServerSession;
27class KSynchronizationObject;
28} // namespace Kernel 26} // namespace Kernel
29 27
30namespace Service { 28namespace Service {
31 29
30class Port;
31class Session;
32
32class ServerManager { 33class ServerManager {
33public: 34public:
34 explicit ServerManager(Core::System& system); 35 explicit ServerManager(Core::System& system);
@@ -52,34 +53,40 @@ public:
52 static void RunServer(std::unique_ptr<ServerManager>&& server); 53 static void RunServer(std::unique_ptr<ServerManager>&& server);
53 54
54private: 55private:
55 struct RequestState; 56 void LinkToDeferredList(MultiWaitHolder* holder);
56 57 void LinkDeferred();
58 MultiWaitHolder* WaitSignaled();
59 Result Process(MultiWaitHolder* holder);
60 bool WaitAndProcessImpl();
57 Result LoopProcessImpl(); 61 Result LoopProcessImpl();
58 Result WaitAndProcessImpl(); 62
59 Result OnPortEvent(Kernel::KServerPort* port, SessionRequestHandlerFactory&& handler_factory); 63 Result OnPortEvent(Port* port);
60 Result OnSessionEvent(Kernel::KServerSession* session, 64 Result OnSessionEvent(Session* session);
61 std::shared_ptr<SessionRequestManager>&& manager); 65 Result OnDeferralEvent();
62 Result OnDeferralEvent(std::list<RequestState>&& deferrals); 66 Result CompleteSyncRequest(Session* session);
63 Result CompleteSyncRequest(RequestState&& state); 67
68private:
69 void DestroySession(Session* session);
64 70
65private: 71private:
66 Core::System& m_system; 72 Core::System& m_system;
67 Mutex m_serve_mutex; 73 Mutex m_selection_mutex;
68 std::mutex m_list_mutex;
69 74
70 // Guest state tracking 75 // Events
71 std::map<Kernel::KServerPort*, SessionRequestHandlerFactory> m_ports{}; 76 Kernel::KEvent* m_wakeup_event{};
72 std::map<Kernel::KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions{};
73 Kernel::KEvent* m_event{};
74 Kernel::KEvent* m_deferral_event{}; 77 Kernel::KEvent* m_deferral_event{};
75 78
76 // Deferral tracking 79 // Deferred wait list
77 struct RequestState { 80 std::mutex m_deferred_list_mutex{};
78 Kernel::KServerSession* session; 81 MultiWait m_deferred_list{};
79 std::shared_ptr<HLERequestContext> context; 82
80 std::shared_ptr<SessionRequestManager> manager; 83 // Guest state tracking
81 }; 84 MultiWait m_multi_wait{};
82 std::list<RequestState> m_deferrals{}; 85 Common::IntrusiveListBaseTraits<Port>::ListType m_servers{};
86 Common::IntrusiveListBaseTraits<Session>::ListType m_sessions{};
87 std::list<Session*> m_deferred_sessions{};
88 std::optional<MultiWaitHolder> m_wakeup_holder{};
89 std::optional<MultiWaitHolder> m_deferral_holder{};
83 90
84 // Host state tracking 91 // Host state tracking
85 Common::Event m_stopped{}; 92 Common::Event m_stopped{};
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 06cbad268..f68c3c686 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -15,7 +15,7 @@
15#include "core/hle/service/aoc/aoc_u.h" 15#include "core/hle/service/aoc/aoc_u.h"
16#include "core/hle/service/apm/apm.h" 16#include "core/hle/service/apm/apm.h"
17#include "core/hle/service/audio/audio.h" 17#include "core/hle/service/audio/audio.h"
18#include "core/hle/service/bcat/bcat_module.h" 18#include "core/hle/service/bcat/bcat.h"
19#include "core/hle/service/bpc/bpc.h" 19#include "core/hle/service/bpc/bpc.h"
20#include "core/hle/service/btdrv/btdrv.h" 20#include "core/hle/service/btdrv/btdrv.h"
21#include "core/hle/service/btm/btm.h" 21#include "core/hle/service/btm/btm.h"
diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp
index 493bd6e9e..eca35d82a 100644
--- a/src/core/hle/service/vi/layer/vi_layer.cpp
+++ b/src/core/hle/service/vi/layer/vi_layer.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/hle/service/nvnflinger/hwc_layer.h"
4#include "core/hle/service/vi/layer/vi_layer.h" 5#include "core/hle/service/vi/layer/vi_layer.h"
5 6
6namespace Service::VI { 7namespace Service::VI {
@@ -8,8 +9,9 @@ namespace Service::VI {
8Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_, 9Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
9 android::BufferQueueProducer& binder_, 10 android::BufferQueueProducer& binder_,
10 std::shared_ptr<android::BufferItemConsumer>&& consumer_) 11 std::shared_ptr<android::BufferItemConsumer>&& consumer_)
11 : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, 12 : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move(
12 consumer{std::move(consumer_)}, open{false}, visible{true} {} 13 consumer_)},
14 blending{Nvnflinger::LayerBlending::None}, open{false}, visible{true} {}
13 15
14Layer::~Layer() = default; 16Layer::~Layer() = default;
15 17
diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h
index b4b031ee7..14e229903 100644
--- a/src/core/hle/service/vi/layer/vi_layer.h
+++ b/src/core/hle/service/vi/layer/vi_layer.h
@@ -14,6 +14,10 @@ class BufferQueueCore;
14class BufferQueueProducer; 14class BufferQueueProducer;
15} // namespace Service::android 15} // namespace Service::android
16 16
17namespace Service::Nvnflinger {
18enum class LayerBlending : u32;
19}
20
17namespace Service::VI { 21namespace Service::VI {
18 22
19/// Represents a single display layer. 23/// Represents a single display layer.
@@ -92,12 +96,21 @@ public:
92 return !std::exchange(open, true); 96 return !std::exchange(open, true);
93 } 97 }
94 98
99 Nvnflinger::LayerBlending GetBlending() {
100 return blending;
101 }
102
103 void SetBlending(Nvnflinger::LayerBlending b) {
104 blending = b;
105 }
106
95private: 107private:
96 const u64 layer_id; 108 const u64 layer_id;
97 const u32 binder_id; 109 const u32 binder_id;
98 android::BufferQueueCore& core; 110 android::BufferQueueCore& core;
99 android::BufferQueueProducer& binder; 111 android::BufferQueueProducer& binder;
100 std::shared_ptr<android::BufferItemConsumer> consumer; 112 std::shared_ptr<android::BufferItemConsumer> consumer;
113 Service::Nvnflinger::LayerBlending blending;
101 bool open; 114 bool open;
102 bool visible; 115 bool visible;
103}; 116};
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index d34624d28..2bebfeef9 100644
--- a/src/frontend_common/config.cpp
+++ b/src/frontend_common/config.cpp
@@ -401,6 +401,14 @@ void Config::ReadNetworkValues() {
401 EndGroup(); 401 EndGroup();
402} 402}
403 403
404void Config::ReadLibraryAppletValues() {
405 BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
406
407 ReadCategory(Settings::Category::LibraryApplet);
408
409 EndGroup();
410}
411
404void Config::ReadValues() { 412void Config::ReadValues() {
405 if (global) { 413 if (global) {
406 ReadDataStorageValues(); 414 ReadDataStorageValues();
@@ -410,6 +418,7 @@ void Config::ReadValues() {
410 ReadServiceValues(); 418 ReadServiceValues();
411 ReadWebServiceValues(); 419 ReadWebServiceValues();
412 ReadMiscellaneousValues(); 420 ReadMiscellaneousValues();
421 ReadLibraryAppletValues();
413 } 422 }
414 ReadControlValues(); 423 ReadControlValues();
415 ReadCoreValues(); 424 ReadCoreValues();
@@ -511,6 +520,7 @@ void Config::SaveValues() {
511 SaveNetworkValues(); 520 SaveNetworkValues();
512 SaveWebServiceValues(); 521 SaveWebServiceValues();
513 SaveMiscellaneousValues(); 522 SaveMiscellaneousValues();
523 SaveLibraryAppletValues();
514 } else { 524 } else {
515 LOG_DEBUG(Config, "Saving only generic configuration values"); 525 LOG_DEBUG(Config, "Saving only generic configuration values");
516 } 526 }
@@ -691,6 +701,14 @@ void Config::SaveWebServiceValues() {
691 EndGroup(); 701 EndGroup();
692} 702}
693 703
704void Config::SaveLibraryAppletValues() {
705 BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
706
707 WriteCategory(Settings::Category::LibraryApplet);
708
709 EndGroup();
710}
711
694bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) { 712bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) {
695 std::string full_key = GetFullKey(key, false); 713 std::string full_key = GetFullKey(key, false);
696 if (!default_value.has_value()) { 714 if (!default_value.has_value()) {
@@ -867,15 +885,9 @@ void Config::Reload() {
867} 885}
868 886
869void Config::ClearControlPlayerValues() const { 887void Config::ClearControlPlayerValues() const {
870 // If key is an empty string, all keys in the current group() are removed. 888 // Removes the entire [Controls] section
871 const char* section = Settings::TranslateCategory(Settings::Category::Controls); 889 const char* section = Settings::TranslateCategory(Settings::Category::Controls);
872 CSimpleIniA::TNamesDepend keys; 890 config->Delete(section, nullptr, true);
873 config->GetAllKeys(section, keys);
874 for (const auto& key : keys) {
875 if (std::string(config->GetValue(section, key.pItem)).empty()) {
876 config->Delete(section, key.pItem);
877 }
878 }
879} 891}
880 892
881const std::string& Config::GetConfigFilePath() const { 893const std::string& Config::GetConfigFilePath() const {
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
index 4ecb97044..8b0599cc3 100644
--- a/src/frontend_common/config.h
+++ b/src/frontend_common/config.h
@@ -88,6 +88,7 @@ protected:
88 void ReadSystemValues(); 88 void ReadSystemValues();
89 void ReadWebServiceValues(); 89 void ReadWebServiceValues();
90 void ReadNetworkValues(); 90 void ReadNetworkValues();
91 void ReadLibraryAppletValues();
91 92
92 // Read platform specific sections 93 // Read platform specific sections
93 virtual void ReadHidbusValues() = 0; 94 virtual void ReadHidbusValues() = 0;
@@ -121,6 +122,7 @@ protected:
121 void SaveScreenshotValues(); 122 void SaveScreenshotValues();
122 void SaveSystemValues(); 123 void SaveSystemValues();
123 void SaveWebServiceValues(); 124 void SaveWebServiceValues();
125 void SaveLibraryAppletValues();
124 126
125 // Save platform specific sections 127 // Save platform specific sections
126 virtual void SaveHidbusValues() = 0; 128 virtual void SaveHidbusValues() = 0;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 55180f4b5..2de2beb6e 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -18,6 +18,7 @@ add_library(video_core STATIC
18 buffer_cache/usage_tracker.h 18 buffer_cache/usage_tracker.h
19 buffer_cache/word_manager.h 19 buffer_cache/word_manager.h
20 cache_types.h 20 cache_types.h
21 capture.h
21 cdma_pusher.cpp 22 cdma_pusher.cpp
22 cdma_pusher.h 23 cdma_pusher.h
23 compatible_formats.cpp 24 compatible_formats.cpp
@@ -101,6 +102,7 @@ add_library(video_core STATIC
101 memory_manager.cpp 102 memory_manager.cpp
102 memory_manager.h 103 memory_manager.h
103 precompiled_headers.h 104 precompiled_headers.h
105 present.h
104 pte_kind.h 106 pte_kind.h
105 query_cache/bank_base.h 107 query_cache/bank_base.h
106 query_cache/query_base.h 108 query_cache/query_base.h
diff --git a/src/video_core/capture.h b/src/video_core/capture.h
new file mode 100644
index 000000000..8db14a8ec
--- /dev/null
+++ b/src/video_core/capture.h
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/alignment.h"
7#include "common/bit_util.h"
8#include "common/common_types.h"
9#include "core/frontend/framebuffer_layout.h"
10#include "video_core/surface.h"
11
12namespace VideoCore::Capture {
13
14constexpr u32 BlockHeight = 4;
15constexpr u32 BlockDepth = 0;
16constexpr u32 BppLog2 = 2;
17
18constexpr auto PixelFormat = Surface::PixelFormat::B8G8R8A8_UNORM;
19
20constexpr auto LinearWidth = Layout::ScreenUndocked::Width;
21constexpr auto LinearHeight = Layout::ScreenUndocked::Height;
22constexpr auto LinearDepth = 1U;
23constexpr auto BytesPerPixel = 4U;
24
25constexpr auto TiledWidth = LinearWidth;
26constexpr auto TiledHeight = Common::AlignUpLog2(LinearHeight, BlockHeight + BlockDepth + BppLog2);
27constexpr auto TiledSize = TiledWidth * TiledHeight * (1 << BppLog2);
28
29constexpr Layout::FramebufferLayout Layout{
30 .width = LinearWidth,
31 .height = LinearHeight,
32 .screen = {0, 0, LinearWidth, LinearHeight},
33 .is_srgb = false,
34};
35
36} // namespace VideoCore::Capture
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
index 6a18b76fb..8b2a49de5 100644
--- a/src/video_core/framebuffer_config.h
+++ b/src/video_core/framebuffer_config.h
@@ -11,6 +11,12 @@
11 11
12namespace Tegra { 12namespace Tegra {
13 13
14enum class BlendMode {
15 Opaque,
16 Premultiplied,
17 Coverage,
18};
19
14/** 20/**
15 * Struct describing framebuffer configuration 21 * Struct describing framebuffer configuration
16 */ 22 */
@@ -23,6 +29,7 @@ struct FramebufferConfig {
23 Service::android::PixelFormat pixel_format{}; 29 Service::android::PixelFormat pixel_format{};
24 Service::android::BufferTransformFlags transform_flags{}; 30 Service::android::BufferTransformFlags transform_flags{};
25 Common::Rectangle<int> crop_rect{}; 31 Common::Rectangle<int> crop_rect{};
32 BlendMode blending{};
26}; 33};
27 34
28Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width, 35Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width,
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index f4a5d831c..8e663f2a8 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -347,6 +347,17 @@ struct GPU::Impl {
347 WaitForSyncOperation(wait_fence); 347 WaitForSyncOperation(wait_fence);
348 } 348 }
349 349
350 std::vector<u8> GetAppletCaptureBuffer() {
351 std::vector<u8> out;
352
353 const auto wait_fence =
354 RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); });
355 gpu_thread.TickGPU();
356 WaitForSyncOperation(wait_fence);
357
358 return out;
359 }
360
350 GPU& gpu; 361 GPU& gpu;
351 Core::System& system; 362 Core::System& system;
352 Host1x::Host1x& host1x; 363 Host1x::Host1x& host1x;
@@ -505,6 +516,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
505 impl->RequestComposite(std::move(layers), std::move(fences)); 516 impl->RequestComposite(std::move(layers), std::move(fences));
506} 517}
507 518
519std::vector<u8> GPU::GetAppletCaptureBuffer() {
520 return impl->GetAppletCaptureBuffer();
521}
522
508u64 GPU::GetTicks() const { 523u64 GPU::GetTicks() const {
509 return impl->GetTicks(); 524 return impl->GetTicks();
510} 525}
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index c4602ca37..ad535512c 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -215,6 +215,8 @@ public:
215 void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, 215 void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
216 std::vector<Service::Nvidia::NvFence>&& fences); 216 std::vector<Service::Nvidia::NvFence>&& fences);
217 217
218 std::vector<u8> GetAppletCaptureBuffer();
219
218 /// Performs any additional setup necessary in order to begin GPU emulation. 220 /// Performs any additional setup necessary in order to begin GPU emulation.
219 /// This can be used to launch any necessary threads and register any necessary 221 /// This can be used to launch any necessary threads and register any necessary
220 /// core timing events. 222 /// core timing events.
diff --git a/src/video_core/host_shaders/fidelityfx_fsr.frag b/src/video_core/host_shaders/fidelityfx_fsr.frag
index a266e1c4e..54eedb450 100644
--- a/src/video_core/host_shaders/fidelityfx_fsr.frag
+++ b/src/video_core/host_shaders/fidelityfx_fsr.frag
@@ -37,6 +37,7 @@ layout(set=0,binding=0) uniform sampler2D InputTexture;
37 37
38#define A_GPU 1 38#define A_GPU 1
39#define A_GLSL 1 39#define A_GLSL 1
40#define FSR_RCAS_PASSTHROUGH_ALPHA 1
40 41
41#ifndef YUZU_USE_FP16 42#ifndef YUZU_USE_FP16
42 #include "ffx_a.h" 43 #include "ffx_a.h"
@@ -71,9 +72,7 @@ layout(set=0,binding=0) uniform sampler2D InputTexture;
71 72
72#include "ffx_fsr1.h" 73#include "ffx_fsr1.h"
73 74
74#if USE_RCAS 75layout (location = 0) in vec2 frag_texcoord;
75 layout(location = 0) in vec2 frag_texcoord;
76#endif
77layout (location = 0) out vec4 frag_color; 76layout (location = 0) out vec4 frag_color;
78 77
79void CurrFilter(AU2 pos) { 78void CurrFilter(AU2 pos) {
@@ -81,22 +80,22 @@ void CurrFilter(AU2 pos) {
81 #ifndef YUZU_USE_FP16 80 #ifndef YUZU_USE_FP16
82 AF3 c; 81 AF3 c;
83 FsrEasuF(c, pos, Const0, Const1, Const2, Const3); 82 FsrEasuF(c, pos, Const0, Const1, Const2, Const3);
84 frag_color = AF4(c, 1.0); 83 frag_color = AF4(c, texture(InputTexture, frag_texcoord).a);
85 #else 84 #else
86 AH3 c; 85 AH3 c;
87 FsrEasuH(c, pos, Const0, Const1, Const2, Const3); 86 FsrEasuH(c, pos, Const0, Const1, Const2, Const3);
88 frag_color = AH4(c, 1.0); 87 frag_color = AH4(c, texture(InputTexture, frag_texcoord).a);
89 #endif 88 #endif
90#endif 89#endif
91#if USE_RCAS 90#if USE_RCAS
92 #ifndef YUZU_USE_FP16 91 #ifndef YUZU_USE_FP16
93 AF3 c; 92 AF4 c;
94 FsrRcasF(c.r, c.g, c.b, pos, Const0); 93 FsrRcasF(c.r, c.g, c.b, c.a, pos, Const0);
95 frag_color = AF4(c, 1.0); 94 frag_color = c;
96 #else 95 #else
97 AH3 c; 96 AH4 c;
98 FsrRcasH(c.r, c.g, c.b, pos, Const0); 97 FsrRcasH(c.r, c.g, c.b, c.a, pos, Const0);
99 frag_color = AH4(c, 1.0); 98 frag_color = c;
100 #endif 99 #endif
101#endif 100#endif
102} 101}
diff --git a/src/video_core/host_shaders/fxaa.frag b/src/video_core/host_shaders/fxaa.frag
index 9bffc20d5..192a602c1 100644
--- a/src/video_core/host_shaders/fxaa.frag
+++ b/src/video_core/host_shaders/fxaa.frag
@@ -71,5 +71,5 @@ vec3 FxaaPixelShader(vec4 posPos, sampler2D tex) {
71} 71}
72 72
73void main() { 73void main() {
74 frag_color = vec4(FxaaPixelShader(posPos, input_texture), 1.0); 74 frag_color = vec4(FxaaPixelShader(posPos, input_texture), texture(input_texture, posPos.xy).a);
75} 75}
diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
index 16d22f58e..fc47d3810 100644
--- a/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
+++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
@@ -31,6 +31,7 @@ layout (location = 0) uniform uvec4 constants[4];
31 31
32#define A_GPU 1 32#define A_GPU 1
33#define A_GLSL 1 33#define A_GLSL 1
34#define FSR_RCAS_PASSTHROUGH_ALPHA 1
34 35
35#ifdef YUZU_USE_FP16 36#ifdef YUZU_USE_FP16
36 #define A_HALF 37 #define A_HALF
@@ -67,9 +68,7 @@ layout (location = 0) uniform uvec4 constants[4];
67 68
68#include "ffx_fsr1.h" 69#include "ffx_fsr1.h"
69 70
70#if USE_RCAS 71layout (location = 0) in vec2 frag_texcoord;
71 layout(location = 0) in vec2 frag_texcoord;
72#endif
73layout (location = 0) out vec4 frag_color; 72layout (location = 0) out vec4 frag_color;
74 73
75void CurrFilter(AU2 pos) 74void CurrFilter(AU2 pos)
@@ -78,22 +77,22 @@ void CurrFilter(AU2 pos)
78 #ifndef YUZU_USE_FP16 77 #ifndef YUZU_USE_FP16
79 AF3 c; 78 AF3 c;
80 FsrEasuF(c, pos, constants[0], constants[1], constants[2], constants[3]); 79 FsrEasuF(c, pos, constants[0], constants[1], constants[2], constants[3]);
81 frag_color = AF4(c, 1.0); 80 frag_color = AF4(c, texture(InputTexture, frag_texcoord).a);
82 #else 81 #else
83 AH3 c; 82 AH3 c;
84 FsrEasuH(c, pos, constants[0], constants[1], constants[2], constants[3]); 83 FsrEasuH(c, pos, constants[0], constants[1], constants[2], constants[3]);
85 frag_color = AH4(c, 1.0); 84 frag_color = AH4(c, texture(InputTexture, frag_texcoord).a);
86 #endif 85 #endif
87#endif 86#endif
88#if USE_RCAS 87#if USE_RCAS
89 #ifndef YUZU_USE_FP16 88 #ifndef YUZU_USE_FP16
90 AF3 c; 89 AF4 c;
91 FsrRcasF(c.r, c.g, c.b, pos, constants[0]); 90 FsrRcasF(c.r, c.g, c.b, c.a, pos, constants[0]);
92 frag_color = AF4(c, 1.0); 91 frag_color = c;
93 #else 92 #else
94 AH3 c; 93 AH3 c;
95 FsrRcasH(c.r, c.g, c.b, pos, constants[0]); 94 FsrRcasH(c.r, c.g, c.b, c.a, pos, constants[0]);
96 frag_color = AH4(c, 1.0); 95 frag_color = c;
97 #endif 96 #endif
98#endif 97#endif
99} 98}
diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag
index 5fd7ad297..096b4e4db 100644
--- a/src/video_core/host_shaders/opengl_present.frag
+++ b/src/video_core/host_shaders/opengl_present.frag
@@ -9,5 +9,5 @@ layout (location = 0) out vec4 color;
9layout (binding = 0) uniform sampler2D color_texture; 9layout (binding = 0) uniform sampler2D color_texture;
10 10
11void main() { 11void main() {
12 color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f); 12 color = vec4(texture(color_texture, frag_tex_coord));
13} 13}
diff --git a/src/video_core/host_shaders/present_bicubic.frag b/src/video_core/host_shaders/present_bicubic.frag
index c814629cf..a9d9d40a3 100644
--- a/src/video_core/host_shaders/present_bicubic.frag
+++ b/src/video_core/host_shaders/present_bicubic.frag
@@ -52,5 +52,5 @@ vec4 textureBicubic( sampler2D textureSampler, vec2 texCoords ) {
52} 52}
53 53
54void main() { 54void main() {
55 color = vec4(textureBicubic(color_texture, frag_tex_coord).rgb, 1.0f); 55 color = textureBicubic(color_texture, frag_tex_coord);
56} 56}
diff --git a/src/video_core/host_shaders/present_gaussian.frag b/src/video_core/host_shaders/present_gaussian.frag
index ad9bb76a4..78edeb9b4 100644
--- a/src/video_core/host_shaders/present_gaussian.frag
+++ b/src/video_core/host_shaders/present_gaussian.frag
@@ -46,14 +46,14 @@ vec4 blurDiagonal(sampler2D textureSampler, vec2 coord, vec2 norm) {
46} 46}
47 47
48void main() { 48void main() {
49 vec3 base = texture(color_texture, vec2(frag_tex_coord)).rgb * weight[0]; 49 vec4 base = texture(color_texture, vec2(frag_tex_coord)) * weight[0];
50 vec2 tex_offset = 1.0f / textureSize(color_texture, 0); 50 vec2 tex_offset = 1.0f / textureSize(color_texture, 0);
51 51
52 // TODO(Blinkhawk): This code can be optimized through shader group instructions. 52 // TODO(Blinkhawk): This code can be optimized through shader group instructions.
53 vec3 horizontal = blurHorizontal(color_texture, frag_tex_coord, tex_offset).rgb; 53 vec4 horizontal = blurHorizontal(color_texture, frag_tex_coord, tex_offset);
54 vec3 vertical = blurVertical(color_texture, frag_tex_coord, tex_offset).rgb; 54 vec4 vertical = blurVertical(color_texture, frag_tex_coord, tex_offset);
55 vec3 diagonalA = blurDiagonal(color_texture, frag_tex_coord, tex_offset).rgb; 55 vec4 diagonalA = blurDiagonal(color_texture, frag_tex_coord, tex_offset);
56 vec3 diagonalB = blurDiagonal(color_texture, frag_tex_coord, tex_offset * vec2(1.0, -1.0)).rgb; 56 vec4 diagonalB = blurDiagonal(color_texture, frag_tex_coord, tex_offset * vec2(1.0, -1.0));
57 vec3 combination = mix(mix(horizontal, vertical, 0.5f), mix(diagonalA, diagonalB, 0.5f), 0.5f); 57 vec4 combination = mix(mix(horizontal, vertical, 0.5f), mix(diagonalA, diagonalB, 0.5f), 0.5f);
58 color = vec4(combination + base, 1.0f); 58 color = combination + base;
59} 59}
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
index d369bef06..05d033310 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
@@ -6,5 +6,6 @@
6 6
7#define YUZU_USE_FP16 7#define YUZU_USE_FP16
8#define USE_EASU 1 8#define USE_EASU 1
9#define VERSION 1
9 10
10#include "fidelityfx_fsr.frag" 11#include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
index 6f25ef00f..7ae11dd66 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
@@ -5,5 +5,6 @@
5#extension GL_GOOGLE_include_directive : enable 5#extension GL_GOOGLE_include_directive : enable
6 6
7#define USE_EASU 1 7#define USE_EASU 1
8#define VERSION 1
8 9
9#include "fidelityfx_fsr.frag" 10#include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
index 0c953a900..c017214a5 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
@@ -6,5 +6,6 @@
6 6
7#define YUZU_USE_FP16 7#define YUZU_USE_FP16
8#define USE_RCAS 1 8#define USE_RCAS 1
9#define VERSION 1
9 10
10#include "fidelityfx_fsr.frag" 11#include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
index 02e9a27c6..976825f4b 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
@@ -5,5 +5,6 @@
5#extension GL_GOOGLE_include_directive : enable 5#extension GL_GOOGLE_include_directive : enable
6 6
7#define USE_RCAS 1 7#define USE_RCAS 1
8#define VERSION 1
8 9
9#include "fidelityfx_fsr.frag" 10#include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_present.vert b/src/video_core/host_shaders/vulkan_present.vert
index 249c9675a..c0e6e8537 100644
--- a/src/video_core/host_shaders/vulkan_present.vert
+++ b/src/video_core/host_shaders/vulkan_present.vert
@@ -19,15 +19,13 @@ layout (push_constant) uniform PushConstants {
19// Any member of a push constant block that is declared as an 19// Any member of a push constant block that is declared as an
20// array must only be accessed with dynamically uniform indices. 20// array must only be accessed with dynamically uniform indices.
21ScreenRectVertex GetVertex(int index) { 21ScreenRectVertex GetVertex(int index) {
22 switch (index) { 22 if (index < 1) {
23 case 0:
24 default:
25 return vertices[0]; 23 return vertices[0];
26 case 1: 24 } else if (index < 2) {
27 return vertices[1]; 25 return vertices[1];
28 case 2: 26 } else if (index < 3) {
29 return vertices[2]; 27 return vertices[2];
30 case 3: 28 } else {
31 return vertices[3]; 29 return vertices[3];
32 } 30 }
33} 31}
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
index 79ea817c2..cea5dac9d 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
@@ -5,7 +5,7 @@
5 5
6#extension GL_GOOGLE_include_directive : enable 6#extension GL_GOOGLE_include_directive : enable
7 7
8#define VERSION 1 8#define VERSION 2
9#define YUZU_USE_FP16 9#define YUZU_USE_FP16
10 10
11#include "opengl_present_scaleforce.frag" 11#include "opengl_present_scaleforce.frag"
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
index 9605bb58b..10ddf0401 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
@@ -5,6 +5,6 @@
5 5
6#extension GL_GOOGLE_include_directive : enable 6#extension GL_GOOGLE_include_directive : enable
7 7
8#define VERSION 1 8#define VERSION 2
9 9
10#include "opengl_present_scaleforce.frag" 10#include "opengl_present_scaleforce.frag"
diff --git a/src/video_core/present.h b/src/video_core/present.h
new file mode 100644
index 000000000..4fdfcca68
--- /dev/null
+++ b/src/video_core/present.h
@@ -0,0 +1,37 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/settings.h"
7
8static inline Settings::ScalingFilter GetScalingFilter() {
9 return Settings::values.scaling_filter.GetValue();
10}
11
12static inline Settings::AntiAliasing GetAntiAliasing() {
13 return Settings::values.anti_aliasing.GetValue();
14}
15
16static inline Settings::ScalingFilter GetScalingFilterForAppletCapture() {
17 return Settings::ScalingFilter::Bilinear;
18}
19
20static inline Settings::AntiAliasing GetAntiAliasingForAppletCapture() {
21 return Settings::AntiAliasing::None;
22}
23
24struct PresentFilters {
25 Settings::ScalingFilter (*get_scaling_filter)();
26 Settings::AntiAliasing (*get_anti_aliasing)();
27};
28
29constexpr PresentFilters PresentFiltersForDisplay{
30 .get_scaling_filter = &GetScalingFilter,
31 .get_anti_aliasing = &GetAntiAliasing,
32};
33
34constexpr PresentFilters PresentFiltersForAppletCapture{
35 .get_scaling_filter = &GetScalingFilterForAppletCapture,
36 .get_anti_aliasing = &GetAntiAliasingForAppletCapture,
37};
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 3ad180f67..67427f937 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -40,6 +40,9 @@ public:
40 /// Finalize rendering the guest frame and draw into the presentation texture 40 /// Finalize rendering the guest frame and draw into the presentation texture
41 virtual void Composite(std::span<const Tegra::FramebufferConfig> layers) = 0; 41 virtual void Composite(std::span<const Tegra::FramebufferConfig> layers) = 0;
42 42
43 /// Get the tiled applet layer capture buffer
44 virtual std::vector<u8> GetAppletCaptureBuffer() = 0;
45
43 [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0; 46 [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
44 47
45 [[nodiscard]] virtual std::string GetDeviceVendor() const = 0; 48 [[nodiscard]] virtual std::string GetDeviceVendor() const = 0;
diff --git a/src/video_core/renderer_null/renderer_null.cpp b/src/video_core/renderer_null/renderer_null.cpp
index c89daff53..e6147d66c 100644
--- a/src/video_core/renderer_null/renderer_null.cpp
+++ b/src/video_core/renderer_null/renderer_null.cpp
@@ -3,6 +3,7 @@
3 3
4#include "core/frontend/emu_window.h" 4#include "core/frontend/emu_window.h"
5#include "core/frontend/graphics_context.h" 5#include "core/frontend/graphics_context.h"
6#include "video_core/capture.h"
6#include "video_core/renderer_null/renderer_null.h" 7#include "video_core/renderer_null/renderer_null.h"
7 8
8namespace Null { 9namespace Null {
@@ -22,4 +23,8 @@ void RendererNull::Composite(std::span<const Tegra::FramebufferConfig> framebuff
22 render_window.OnFrameDisplayed(); 23 render_window.OnFrameDisplayed();
23} 24}
24 25
26std::vector<u8> RendererNull::GetAppletCaptureBuffer() {
27 return std::vector<u8>(VideoCore::Capture::TiledSize);
28}
29
25} // namespace Null 30} // namespace Null
diff --git a/src/video_core/renderer_null/renderer_null.h b/src/video_core/renderer_null/renderer_null.h
index 063b476bb..34dbe1e4f 100644
--- a/src/video_core/renderer_null/renderer_null.h
+++ b/src/video_core/renderer_null/renderer_null.h
@@ -19,6 +19,8 @@ public:
19 19
20 void Composite(std::span<const Tegra::FramebufferConfig> framebuffer) override; 20 void Composite(std::span<const Tegra::FramebufferConfig> framebuffer) override;
21 21
22 std::vector<u8> GetAppletCaptureBuffer() override;
23
22 VideoCore::RasterizerInterface* ReadRasterizer() override { 24 VideoCore::RasterizerInterface* ReadRasterizer() override {
23 return &m_rasterizer; 25 return &m_rasterizer;
24 } 26 }
diff --git a/src/video_core/renderer_opengl/gl_blit_screen.cpp b/src/video_core/renderer_opengl/gl_blit_screen.cpp
index 6ba8b214b..9260a4dc4 100644
--- a/src/video_core/renderer_opengl/gl_blit_screen.cpp
+++ b/src/video_core/renderer_opengl/gl_blit_screen.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/settings.h" 4#include "common/settings.h"
5#include "video_core/present.h"
5#include "video_core/renderer_opengl/gl_blit_screen.h" 6#include "video_core/renderer_opengl/gl_blit_screen.h"
6#include "video_core/renderer_opengl/gl_state_tracker.h" 7#include "video_core/renderer_opengl/gl_state_tracker.h"
7#include "video_core/renderer_opengl/present/filters.h" 8#include "video_core/renderer_opengl/present/filters.h"
@@ -13,14 +14,14 @@ namespace OpenGL {
13BlitScreen::BlitScreen(RasterizerOpenGL& rasterizer_, 14BlitScreen::BlitScreen(RasterizerOpenGL& rasterizer_,
14 Tegra::MaxwellDeviceMemoryManager& device_memory_, 15 Tegra::MaxwellDeviceMemoryManager& device_memory_,
15 StateTracker& state_tracker_, ProgramManager& program_manager_, 16 StateTracker& state_tracker_, ProgramManager& program_manager_,
16 Device& device_) 17 Device& device_, const PresentFilters& filters_)
17 : rasterizer(rasterizer_), device_memory(device_memory_), state_tracker(state_tracker_), 18 : rasterizer(rasterizer_), device_memory(device_memory_), state_tracker(state_tracker_),
18 program_manager(program_manager_), device(device_) {} 19 program_manager(program_manager_), device(device_), filters(filters_) {}
19 20
20BlitScreen::~BlitScreen() = default; 21BlitScreen::~BlitScreen() = default;
21 22
22void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers, 23void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers,
23 const Layout::FramebufferLayout& layout) { 24 const Layout::FramebufferLayout& layout, bool invert_y) {
24 // TODO: Signal state tracker about these changes 25 // TODO: Signal state tracker about these changes
25 state_tracker.NotifyScreenDrawVertexArray(); 26 state_tracker.NotifyScreenDrawVertexArray();
26 state_tracker.NotifyPolygonModes(); 27 state_tracker.NotifyPolygonModes();
@@ -56,22 +57,22 @@ void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffe
56 glDepthRangeIndexed(0, 0.0, 0.0); 57 glDepthRangeIndexed(0, 0.0, 0.0);
57 58
58 while (layers.size() < framebuffers.size()) { 59 while (layers.size() < framebuffers.size()) {
59 layers.emplace_back(rasterizer, device_memory); 60 layers.emplace_back(rasterizer, device_memory, filters);
60 } 61 }
61 62
62 CreateWindowAdapt(); 63 CreateWindowAdapt();
63 window_adapt->DrawToFramebuffer(program_manager, layers, framebuffers, layout); 64 window_adapt->DrawToFramebuffer(program_manager, layers, framebuffers, layout, invert_y);
64 65
65 // TODO 66 // TODO
66 // program_manager.RestoreGuestPipeline(); 67 // program_manager.RestoreGuestPipeline();
67} 68}
68 69
69void BlitScreen::CreateWindowAdapt() { 70void BlitScreen::CreateWindowAdapt() {
70 if (window_adapt && Settings::values.scaling_filter.GetValue() == current_window_adapt) { 71 if (window_adapt && filters.get_scaling_filter() == current_window_adapt) {
71 return; 72 return;
72 } 73 }
73 74
74 current_window_adapt = Settings::values.scaling_filter.GetValue(); 75 current_window_adapt = filters.get_scaling_filter();
75 switch (current_window_adapt) { 76 switch (current_window_adapt) {
76 case Settings::ScalingFilter::NearestNeighbor: 77 case Settings::ScalingFilter::NearestNeighbor:
77 window_adapt = MakeNearestNeighbor(device); 78 window_adapt = MakeNearestNeighbor(device);
diff --git a/src/video_core/renderer_opengl/gl_blit_screen.h b/src/video_core/renderer_opengl/gl_blit_screen.h
index 0c3d838f1..df2da9424 100644
--- a/src/video_core/renderer_opengl/gl_blit_screen.h
+++ b/src/video_core/renderer_opengl/gl_blit_screen.h
@@ -15,6 +15,8 @@ namespace Layout {
15struct FramebufferLayout; 15struct FramebufferLayout;
16} 16}
17 17
18struct PresentFilters;
19
18namespace Tegra { 20namespace Tegra {
19struct FramebufferConfig; 21struct FramebufferConfig;
20} 22}
@@ -46,12 +48,12 @@ public:
46 explicit BlitScreen(RasterizerOpenGL& rasterizer, 48 explicit BlitScreen(RasterizerOpenGL& rasterizer,
47 Tegra::MaxwellDeviceMemoryManager& device_memory, 49 Tegra::MaxwellDeviceMemoryManager& device_memory,
48 StateTracker& state_tracker, ProgramManager& program_manager, 50 StateTracker& state_tracker, ProgramManager& program_manager,
49 Device& device); 51 Device& device, const PresentFilters& filters);
50 ~BlitScreen(); 52 ~BlitScreen();
51 53
52 /// Draws the emulated screens to the emulator window. 54 /// Draws the emulated screens to the emulator window.
53 void DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers, 55 void DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers,
54 const Layout::FramebufferLayout& layout); 56 const Layout::FramebufferLayout& layout, bool invert_y);
55 57
56private: 58private:
57 void CreateWindowAdapt(); 59 void CreateWindowAdapt();
@@ -61,6 +63,7 @@ private:
61 StateTracker& state_tracker; 63 StateTracker& state_tracker;
62 ProgramManager& program_manager; 64 ProgramManager& program_manager;
63 Device& device; 65 Device& device;
66 const PresentFilters& filters;
64 67
65 Settings::ScalingFilter current_window_adapt{}; 68 Settings::ScalingFilter current_window_adapt{};
66 std::unique_ptr<WindowAdaptPass> window_adapt; 69 std::unique_ptr<WindowAdaptPass> window_adapt;
diff --git a/src/video_core/renderer_opengl/present/layer.cpp b/src/video_core/renderer_opengl/present/layer.cpp
index 8643e07c6..6c7092d22 100644
--- a/src/video_core/renderer_opengl/present/layer.cpp
+++ b/src/video_core/renderer_opengl/present/layer.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "video_core/framebuffer_config.h" 4#include "video_core/framebuffer_config.h"
5#include "video_core/present.h"
5#include "video_core/renderer_opengl/gl_blit_screen.h" 6#include "video_core/renderer_opengl/gl_blit_screen.h"
6#include "video_core/renderer_opengl/gl_rasterizer.h" 7#include "video_core/renderer_opengl/gl_rasterizer.h"
7#include "video_core/renderer_opengl/present/fsr.h" 8#include "video_core/renderer_opengl/present/fsr.h"
@@ -14,8 +15,9 @@
14 15
15namespace OpenGL { 16namespace OpenGL {
16 17
17Layer::Layer(RasterizerOpenGL& rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_) 18Layer::Layer(RasterizerOpenGL& rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_,
18 : rasterizer(rasterizer_), device_memory(device_memory_) { 19 const PresentFilters& filters_)
20 : rasterizer(rasterizer_), device_memory(device_memory_), filters(filters_) {
19 // Allocate textures for the screen 21 // Allocate textures for the screen
20 framebuffer_texture.resource.Create(GL_TEXTURE_2D); 22 framebuffer_texture.resource.Create(GL_TEXTURE_2D);
21 23
@@ -34,12 +36,12 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
34 std::array<ScreenRectVertex, 4>& out_vertices, 36 std::array<ScreenRectVertex, 4>& out_vertices,
35 ProgramManager& program_manager, 37 ProgramManager& program_manager,
36 const Tegra::FramebufferConfig& framebuffer, 38 const Tegra::FramebufferConfig& framebuffer,
37 const Layout::FramebufferLayout& layout) { 39 const Layout::FramebufferLayout& layout, bool invert_y) {
38 FramebufferTextureInfo info = PrepareRenderTarget(framebuffer); 40 FramebufferTextureInfo info = PrepareRenderTarget(framebuffer);
39 auto crop = Tegra::NormalizeCrop(framebuffer, info.width, info.height); 41 auto crop = Tegra::NormalizeCrop(framebuffer, info.width, info.height);
40 GLuint texture = info.display_texture; 42 GLuint texture = info.display_texture;
41 43
42 auto anti_aliasing = Settings::values.anti_aliasing.GetValue(); 44 auto anti_aliasing = filters.get_anti_aliasing();
43 if (anti_aliasing != Settings::AntiAliasing::None) { 45 if (anti_aliasing != Settings::AntiAliasing::None) {
44 glEnablei(GL_SCISSOR_TEST, 0); 46 glEnablei(GL_SCISSOR_TEST, 0);
45 auto viewport_width = Settings::values.resolution_info.ScaleUp(framebuffer_texture.width); 47 auto viewport_width = Settings::values.resolution_info.ScaleUp(framebuffer_texture.width);
@@ -64,7 +66,7 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
64 66
65 glDisablei(GL_SCISSOR_TEST, 0); 67 glDisablei(GL_SCISSOR_TEST, 0);
66 68
67 if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) { 69 if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
68 if (!fsr || fsr->NeedsRecreation(layout.screen)) { 70 if (!fsr || fsr->NeedsRecreation(layout.screen)) {
69 fsr = std::make_unique<FSR>(layout.screen.GetWidth(), layout.screen.GetHeight()); 71 fsr = std::make_unique<FSR>(layout.screen.GetWidth(), layout.screen.GetHeight());
70 } 72 }
@@ -83,10 +85,15 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
83 const auto w = screen.GetWidth(); 85 const auto w = screen.GetWidth();
84 const auto h = screen.GetHeight(); 86 const auto h = screen.GetHeight();
85 87
86 out_vertices[0] = ScreenRectVertex(x, y, crop.left, crop.top); 88 const auto left = crop.left;
87 out_vertices[1] = ScreenRectVertex(x + w, y, crop.right, crop.top); 89 const auto right = crop.right;
88 out_vertices[2] = ScreenRectVertex(x, y + h, crop.left, crop.bottom); 90 const auto top = invert_y ? crop.bottom : crop.top;
89 out_vertices[3] = ScreenRectVertex(x + w, y + h, crop.right, crop.bottom); 91 const auto bottom = invert_y ? crop.top : crop.bottom;
92
93 out_vertices[0] = ScreenRectVertex(x, y, left, top);
94 out_vertices[1] = ScreenRectVertex(x + w, y, right, top);
95 out_vertices[2] = ScreenRectVertex(x, y + h, left, bottom);
96 out_vertices[3] = ScreenRectVertex(x + w, y + h, right, bottom);
90 97
91 return texture; 98 return texture;
92} 99}
@@ -131,10 +138,12 @@ FramebufferTextureInfo Layer::LoadFBToScreenInfo(const Tegra::FramebufferConfig&
131 const u64 size_in_bytes{Tegra::Texture::CalculateSize( 138 const u64 size_in_bytes{Tegra::Texture::CalculateSize(
132 true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)}; 139 true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
133 const u8* const host_ptr{device_memory.GetPointer<u8>(framebuffer_addr)}; 140 const u8* const host_ptr{device_memory.GetPointer<u8>(framebuffer_addr)};
134 const std::span<const u8> input_data(host_ptr, size_in_bytes); 141 if (host_ptr) {
135 Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel, 142 const std::span<const u8> input_data(host_ptr, size_in_bytes);
136 framebuffer.width, framebuffer.height, 1, block_height_log2, 143 Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel,
137 0); 144 framebuffer.width, framebuffer.height, 1,
145 block_height_log2, 0);
146 }
138 147
139 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); 148 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
140 glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); 149 glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
diff --git a/src/video_core/renderer_opengl/present/layer.h b/src/video_core/renderer_opengl/present/layer.h
index ef1055abf..5b15b730f 100644
--- a/src/video_core/renderer_opengl/present/layer.h
+++ b/src/video_core/renderer_opengl/present/layer.h
@@ -13,6 +13,8 @@ namespace Layout {
13struct FramebufferLayout; 13struct FramebufferLayout;
14} 14}
15 15
16struct PresentFilters;
17
16namespace Service::android { 18namespace Service::android {
17enum class PixelFormat : u32; 19enum class PixelFormat : u32;
18}; 20};
@@ -44,14 +46,15 @@ struct ScreenRectVertex;
44 46
45class Layer { 47class Layer {
46public: 48public:
47 explicit Layer(RasterizerOpenGL& rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory); 49 explicit Layer(RasterizerOpenGL& rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory,
50 const PresentFilters& filters);
48 ~Layer(); 51 ~Layer();
49 52
50 GLuint ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix, 53 GLuint ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
51 std::array<ScreenRectVertex, 4>& out_vertices, 54 std::array<ScreenRectVertex, 4>& out_vertices,
52 ProgramManager& program_manager, 55 ProgramManager& program_manager,
53 const Tegra::FramebufferConfig& framebuffer, 56 const Tegra::FramebufferConfig& framebuffer,
54 const Layout::FramebufferLayout& layout); 57 const Layout::FramebufferLayout& layout, bool invert_y);
55 58
56private: 59private:
57 /// Loads framebuffer from emulated memory into the active OpenGL texture. 60 /// Loads framebuffer from emulated memory into the active OpenGL texture.
@@ -65,6 +68,7 @@ private:
65private: 68private:
66 RasterizerOpenGL& rasterizer; 69 RasterizerOpenGL& rasterizer;
67 Tegra::MaxwellDeviceMemoryManager& device_memory; 70 Tegra::MaxwellDeviceMemoryManager& device_memory;
71 const PresentFilters& filters;
68 72
69 /// OpenGL framebuffer data 73 /// OpenGL framebuffer data
70 std::vector<u8> gl_framebuffer_data; 74 std::vector<u8> gl_framebuffer_data;
diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp
index 4d681606b..d8b6a11cb 100644
--- a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp
+++ b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp
@@ -37,7 +37,7 @@ WindowAdaptPass::~WindowAdaptPass() = default;
37 37
38void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers, 38void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers,
39 std::span<const Tegra::FramebufferConfig> framebuffers, 39 std::span<const Tegra::FramebufferConfig> framebuffers,
40 const Layout::FramebufferLayout& layout) { 40 const Layout::FramebufferLayout& layout, bool invert_y) {
41 GLint old_read_fb; 41 GLint old_read_fb;
42 GLint old_draw_fb; 42 GLint old_draw_fb;
43 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb); 43 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -51,7 +51,7 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
51 auto layer_it = layers.begin(); 51 auto layer_it = layers.begin();
52 for (size_t i = 0; i < layer_count; i++) { 52 for (size_t i = 0; i < layer_count; i++) {
53 textures[i] = layer_it->ConfigureDraw(matrices[i], vertices[i], program_manager, 53 textures[i] = layer_it->ConfigureDraw(matrices[i], vertices[i], program_manager,
54 framebuffers[i], layout); 54 framebuffers[i], layout, invert_y);
55 layer_it++; 55 layer_it++;
56 } 56 }
57 57
@@ -92,6 +92,21 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
92 glClear(GL_COLOR_BUFFER_BIT); 92 glClear(GL_COLOR_BUFFER_BIT);
93 93
94 for (size_t i = 0; i < layer_count; i++) { 94 for (size_t i = 0; i < layer_count; i++) {
95 switch (framebuffers[i].blending) {
96 case Tegra::BlendMode::Opaque:
97 default:
98 glDisablei(GL_BLEND, 0);
99 break;
100 case Tegra::BlendMode::Premultiplied:
101 glEnablei(GL_BLEND, 0);
102 glBlendFuncSeparatei(0, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
103 break;
104 case Tegra::BlendMode::Coverage:
105 glEnablei(GL_BLEND, 0);
106 glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
107 break;
108 }
109
95 glBindTextureUnit(0, textures[i]); 110 glBindTextureUnit(0, textures[i]);
96 glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE, 111 glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
97 matrices[i].data()); 112 matrices[i].data());
diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.h b/src/video_core/renderer_opengl/present/window_adapt_pass.h
index 00975a9c6..0a8bcef2f 100644
--- a/src/video_core/renderer_opengl/present/window_adapt_pass.h
+++ b/src/video_core/renderer_opengl/present/window_adapt_pass.h
@@ -31,7 +31,7 @@ public:
31 31
32 void DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers, 32 void DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers,
33 std::span<const Tegra::FramebufferConfig> framebuffers, 33 std::span<const Tegra::FramebufferConfig> framebuffers,
34 const Layout::FramebufferLayout& layout); 34 const Layout::FramebufferLayout& layout, bool invert_y);
35 35
36private: 36private:
37 const Device& device; 37 const Device& device;
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index e33a32592..5fb54635d 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -16,6 +16,8 @@
16#include "core/core_timing.h" 16#include "core/core_timing.h"
17#include "core/frontend/emu_window.h" 17#include "core/frontend/emu_window.h"
18#include "core/telemetry_session.h" 18#include "core/telemetry_session.h"
19#include "video_core/capture.h"
20#include "video_core/present.h"
19#include "video_core/renderer_opengl/gl_blit_screen.h" 21#include "video_core/renderer_opengl/gl_blit_screen.h"
20#include "video_core/renderer_opengl/gl_rasterizer.h" 22#include "video_core/renderer_opengl/gl_rasterizer.h"
21#include "video_core/renderer_opengl/gl_shader_manager.h" 23#include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -120,7 +122,15 @@ RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
120 glEnableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV); 122 glEnableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV);
121 } 123 }
122 blit_screen = std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker, 124 blit_screen = std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker,
123 program_manager, device); 125 program_manager, device, PresentFiltersForDisplay);
126 blit_applet =
127 std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker, program_manager,
128 device, PresentFiltersForAppletCapture);
129 capture_framebuffer.Create();
130 capture_renderbuffer.Create();
131 glBindRenderbuffer(GL_RENDERBUFFER, capture_renderbuffer.handle);
132 glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, VideoCore::Capture::LinearWidth,
133 VideoCore::Capture::LinearHeight);
124} 134}
125 135
126RendererOpenGL::~RendererOpenGL() = default; 136RendererOpenGL::~RendererOpenGL() = default;
@@ -130,10 +140,11 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
130 return; 140 return;
131 } 141 }
132 142
143 RenderAppletCaptureLayer(framebuffers);
133 RenderScreenshot(framebuffers); 144 RenderScreenshot(framebuffers);
134 145
135 state_tracker.BindFramebuffer(0); 146 state_tracker.BindFramebuffer(0);
136 blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout()); 147 blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout(), false);
137 148
138 ++m_current_frame; 149 ++m_current_frame;
139 150
@@ -159,11 +170,8 @@ void RendererOpenGL::AddTelemetryFields() {
159 telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version)); 170 telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
160} 171}
161 172
162void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) { 173void RendererOpenGL::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
163 if (!renderer_settings.screenshot_requested) { 174 const Layout::FramebufferLayout& layout, void* dst) {
164 return;
165 }
166
167 GLint old_read_fb; 175 GLint old_read_fb;
168 GLint old_draw_fb; 176 GLint old_draw_fb;
169 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb); 177 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -173,29 +181,86 @@ void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
173 screenshot_framebuffer.Create(); 181 screenshot_framebuffer.Create();
174 glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle); 182 glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
175 183
176 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
177
178 GLuint renderbuffer; 184 GLuint renderbuffer;
179 glGenRenderbuffers(1, &renderbuffer); 185 glGenRenderbuffers(1, &renderbuffer);
180 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); 186 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
181 glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, layout.width, layout.height); 187 glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, layout.width, layout.height);
182 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); 188 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
183 189
184 blit_screen->DrawScreen(framebuffers, layout); 190 blit_screen->DrawScreen(framebuffers, layout, false);
185 191
186 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); 192 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
187 glPixelStorei(GL_PACK_ROW_LENGTH, 0); 193 glPixelStorei(GL_PACK_ROW_LENGTH, 0);
188 glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 194 glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, dst);
189 renderer_settings.screenshot_bits);
190 195
191 screenshot_framebuffer.Release(); 196 screenshot_framebuffer.Release();
192 glDeleteRenderbuffers(1, &renderbuffer); 197 glDeleteRenderbuffers(1, &renderbuffer);
193 198
194 glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb); 199 glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
195 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); 200 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
201}
202
203void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
204 if (!renderer_settings.screenshot_requested) {
205 return;
206 }
207
208 RenderToBuffer(framebuffers, renderer_settings.screenshot_framebuffer_layout,
209 renderer_settings.screenshot_bits);
196 210
197 renderer_settings.screenshot_complete_callback(true); 211 renderer_settings.screenshot_complete_callback(true);
198 renderer_settings.screenshot_requested = false; 212 renderer_settings.screenshot_requested = false;
199} 213}
200 214
215void RendererOpenGL::RenderAppletCaptureLayer(
216 std::span<const Tegra::FramebufferConfig> framebuffers) {
217 GLint old_read_fb;
218 GLint old_draw_fb;
219 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
220 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
221
222 glBindFramebuffer(GL_FRAMEBUFFER, capture_framebuffer.handle);
223 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
224 capture_renderbuffer.handle);
225
226 blit_applet->DrawScreen(framebuffers, VideoCore::Capture::Layout, true);
227
228 glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
229 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
230}
231
232std::vector<u8> RendererOpenGL::GetAppletCaptureBuffer() {
233 using namespace VideoCore::Capture;
234
235 std::vector<u8> linear(TiledSize);
236 std::vector<u8> out(TiledSize);
237
238 GLint old_read_fb;
239 GLint old_draw_fb;
240 GLint old_pixel_pack_buffer;
241 GLint old_pack_row_length;
242 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
243 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
244 glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &old_pixel_pack_buffer);
245 glGetIntegerv(GL_PACK_ROW_LENGTH, &old_pack_row_length);
246
247 glBindFramebuffer(GL_FRAMEBUFFER, capture_framebuffer.handle);
248 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
249 capture_renderbuffer.handle);
250 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
251 glPixelStorei(GL_PACK_ROW_LENGTH, 0);
252 glReadPixels(0, 0, LinearWidth, LinearHeight, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
253 linear.data());
254
255 glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
256 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
257 glBindBuffer(GL_PIXEL_PACK_BUFFER, old_pixel_pack_buffer);
258 glPixelStorei(GL_PACK_ROW_LENGTH, old_pack_row_length);
259
260 Tegra::Texture::SwizzleTexture(out, linear, BytesPerPixel, LinearWidth, LinearHeight,
261 LinearDepth, BlockHeight, BlockDepth);
262
263 return out;
264}
265
201} // namespace OpenGL 266} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index c4625c96e..60d6a1477 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -42,6 +42,8 @@ public:
42 42
43 void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override; 43 void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override;
44 44
45 std::vector<u8> GetAppletCaptureBuffer() override;
46
45 VideoCore::RasterizerInterface* ReadRasterizer() override { 47 VideoCore::RasterizerInterface* ReadRasterizer() override {
46 return &rasterizer; 48 return &rasterizer;
47 } 49 }
@@ -52,7 +54,11 @@ public:
52 54
53private: 55private:
54 void AddTelemetryFields(); 56 void AddTelemetryFields();
57
58 void RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
59 const Layout::FramebufferLayout& layout, void* dst);
55 void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers); 60 void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
61 void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
56 62
57 Core::TelemetrySession& telemetry_session; 63 Core::TelemetrySession& telemetry_session;
58 Core::Frontend::EmuWindow& emu_window; 64 Core::Frontend::EmuWindow& emu_window;
@@ -64,8 +70,11 @@ private:
64 ProgramManager program_manager; 70 ProgramManager program_manager;
65 RasterizerOpenGL rasterizer; 71 RasterizerOpenGL rasterizer;
66 OGLFramebuffer screenshot_framebuffer; 72 OGLFramebuffer screenshot_framebuffer;
73 OGLFramebuffer capture_framebuffer;
74 OGLRenderbuffer capture_renderbuffer;
67 75
68 std::unique_ptr<BlitScreen> blit_screen; 76 std::unique_ptr<BlitScreen> blit_screen;
77 std::unique_ptr<BlitScreen> blit_applet;
69}; 78};
70 79
71} // namespace OpenGL 80} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp
index cfc04be44..3847a9a13 100644
--- a/src/video_core/renderer_vulkan/present/layer.cpp
+++ b/src/video_core/renderer_vulkan/present/layer.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "video_core/present.h"
4#include "video_core/renderer_vulkan/vk_rasterizer.h" 5#include "video_core/renderer_vulkan/vk_rasterizer.h"
5 6
6#include "common/settings.h" 7#include "common/settings.h"
@@ -48,12 +49,12 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
48 49
49Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, 50Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
50 Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_, 51 Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_,
51 VkExtent2D output_size, VkDescriptorSetLayout layout) 52 VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_)
52 : device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_), 53 : device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_),
53 device_memory(device_memory_), image_count(image_count_) { 54 device_memory(device_memory_), filters(filters_), image_count(image_count_) {
54 CreateDescriptorPool(); 55 CreateDescriptorPool();
55 CreateDescriptorSets(layout); 56 CreateDescriptorSets(layout);
56 if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) { 57 if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
57 CreateFSR(output_size); 58 CreateFSR(output_size);
58 } 59 }
59} 60}
@@ -171,11 +172,11 @@ void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
171} 172}
172 173
173void Layer::SetAntiAliasPass() { 174void Layer::SetAntiAliasPass() {
174 if (anti_alias && anti_alias_setting == Settings::values.anti_aliasing.GetValue()) { 175 if (anti_alias && anti_alias_setting == filters.get_anti_aliasing()) {
175 return; 176 return;
176 } 177 }
177 178
178 anti_alias_setting = Settings::values.anti_aliasing.GetValue(); 179 anti_alias_setting = filters.get_anti_aliasing();
179 180
180 const VkExtent2D render_area{ 181 const VkExtent2D render_area{
181 .width = Settings::values.resolution_info.ScaleUp(raw_width), 182 .width = Settings::values.resolution_info.ScaleUp(raw_width),
@@ -270,9 +271,11 @@ void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t i
270 const u64 linear_size{GetSizeInBytes(framebuffer)}; 271 const u64 linear_size{GetSizeInBytes(framebuffer)};
271 const u64 tiled_size{Tegra::Texture::CalculateSize( 272 const u64 tiled_size{Tegra::Texture::CalculateSize(
272 true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)}; 273 true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
273 Tegra::Texture::UnswizzleTexture( 274 if (host_ptr) {
274 mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size), 275 Tegra::Texture::UnswizzleTexture(
275 bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0); 276 mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
277 bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
278 }
276 279
277 const VkBufferImageCopy copy{ 280 const VkBufferImageCopy copy{
278 .bufferOffset = image_offset, 281 .bufferOffset = image_offset,
diff --git a/src/video_core/renderer_vulkan/present/layer.h b/src/video_core/renderer_vulkan/present/layer.h
index 88d43fc5f..f5effdcd7 100644
--- a/src/video_core/renderer_vulkan/present/layer.h
+++ b/src/video_core/renderer_vulkan/present/layer.h
@@ -11,6 +11,8 @@ namespace Layout {
11struct FramebufferLayout; 11struct FramebufferLayout;
12} 12}
13 13
14struct PresentFilters;
15
14namespace Tegra { 16namespace Tegra {
15struct FramebufferConfig; 17struct FramebufferConfig;
16} 18}
@@ -37,7 +39,8 @@ class Layer final {
37public: 39public:
38 explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler, 40 explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler,
39 Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count, 41 Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count,
40 VkExtent2D output_size, VkDescriptorSetLayout layout); 42 VkExtent2D output_size, VkDescriptorSetLayout layout,
43 const PresentFilters& filters);
41 ~Layer(); 44 ~Layer();
42 45
43 void ConfigureDraw(PresentPushConstants* out_push_constants, 46 void ConfigureDraw(PresentPushConstants* out_push_constants,
@@ -71,6 +74,7 @@ private:
71 MemoryAllocator& memory_allocator; 74 MemoryAllocator& memory_allocator;
72 Scheduler& scheduler; 75 Scheduler& scheduler;
73 Tegra::MaxwellDeviceMemoryManager& device_memory; 76 Tegra::MaxwellDeviceMemoryManager& device_memory;
77 const PresentFilters& filters;
74 const size_t image_count{}; 78 const size_t image_count{};
75 vk::DescriptorPool descriptor_pool{}; 79 vk::DescriptorPool descriptor_pool{};
76 vk::DescriptorSets descriptor_sets{}; 80 vk::DescriptorSets descriptor_sets{};
diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp
index 6ee16595d..7f27c7c1b 100644
--- a/src/video_core/renderer_vulkan/present/util.cpp
+++ b/src/video_core/renderer_vulkan/present/util.cpp
@@ -362,10 +362,10 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
362 }); 362 });
363} 363}
364 364
365vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, 365static vk::Pipeline CreateWrappedPipelineImpl(
366 vk::PipelineLayout& layout, 366 const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
367 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders, 367 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
368 bool enable_blending) { 368 VkPipelineColorBlendAttachmentState blending) {
369 const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{ 369 const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{
370 { 370 {
371 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 371 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
@@ -443,30 +443,6 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
443 .alphaToOneEnable = VK_FALSE, 443 .alphaToOneEnable = VK_FALSE,
444 }; 444 };
445 445
446 constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{
447 .blendEnable = VK_FALSE,
448 .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
449 .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
450 .colorBlendOp = VK_BLEND_OP_ADD,
451 .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
452 .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
453 .alphaBlendOp = VK_BLEND_OP_ADD,
454 .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
455 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
456 };
457
458 constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_enabled{
459 .blendEnable = VK_TRUE,
460 .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
461 .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
462 .colorBlendOp = VK_BLEND_OP_ADD,
463 .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
464 .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
465 .alphaBlendOp = VK_BLEND_OP_ADD,
466 .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
467 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
468 };
469
470 const VkPipelineColorBlendStateCreateInfo color_blend_ci{ 446 const VkPipelineColorBlendStateCreateInfo color_blend_ci{
471 .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, 447 .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
472 .pNext = nullptr, 448 .pNext = nullptr,
@@ -474,8 +450,7 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
474 .logicOpEnable = VK_FALSE, 450 .logicOpEnable = VK_FALSE,
475 .logicOp = VK_LOGIC_OP_COPY, 451 .logicOp = VK_LOGIC_OP_COPY,
476 .attachmentCount = 1, 452 .attachmentCount = 1,
477 .pAttachments = 453 .pAttachments = &blending,
478 enable_blending ? &color_blend_attachment_enabled : &color_blend_attachment_disabled,
479 .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, 454 .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
480 }; 455 };
481 456
@@ -515,6 +490,63 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
515 }); 490 });
516} 491}
517 492
493vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
494 vk::PipelineLayout& layout,
495 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
496 constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{
497 .blendEnable = VK_FALSE,
498 .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
499 .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
500 .colorBlendOp = VK_BLEND_OP_ADD,
501 .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
502 .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
503 .alphaBlendOp = VK_BLEND_OP_ADD,
504 .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
505 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
506 };
507
508 return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
509 color_blend_attachment_disabled);
510}
511
512vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
513 const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
514 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
515 constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_premultiplied{
516 .blendEnable = VK_TRUE,
517 .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
518 .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
519 .colorBlendOp = VK_BLEND_OP_ADD,
520 .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
521 .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
522 .alphaBlendOp = VK_BLEND_OP_ADD,
523 .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
524 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
525 };
526
527 return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
528 color_blend_attachment_premultiplied);
529}
530
531vk::Pipeline CreateWrappedCoverageBlendingPipeline(
532 const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
533 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
534 constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_coverage{
535 .blendEnable = VK_TRUE,
536 .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
537 .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
538 .colorBlendOp = VK_BLEND_OP_ADD,
539 .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
540 .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
541 .alphaBlendOp = VK_BLEND_OP_ADD,
542 .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
543 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
544 };
545
546 return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
547 color_blend_attachment_coverage);
548}
549
518VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images, 550VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images,
519 VkSampler sampler, VkImageView view, 551 VkSampler sampler, VkImageView view,
520 VkDescriptorSet set, u32 binding) { 552 VkDescriptorSet set, u32 binding) {
diff --git a/src/video_core/renderer_vulkan/present/util.h b/src/video_core/renderer_vulkan/present/util.h
index 1104aaa15..5b22f0fa8 100644
--- a/src/video_core/renderer_vulkan/present/util.h
+++ b/src/video_core/renderer_vulkan/present/util.h
@@ -42,8 +42,13 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
42 vk::DescriptorSetLayout& layout); 42 vk::DescriptorSetLayout& layout);
43vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, 43vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
44 vk::PipelineLayout& layout, 44 vk::PipelineLayout& layout,
45 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders, 45 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
46 bool enable_blending = false); 46vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
47 const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
48 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
49vk::Pipeline CreateWrappedCoverageBlendingPipeline(
50 const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
51 std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
47VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images, 52VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images,
48 VkSampler sampler, VkImageView view, 53 VkSampler sampler, VkImageView view,
49 VkDescriptorSet set, u32 binding); 54 VkDescriptorSet set, u32 binding);
diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
index c5db0230d..22ffacf11 100644
--- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
+++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
@@ -22,7 +22,7 @@ WindowAdaptPass::WindowAdaptPass(const Device& device_, VkFormat frame_format,
22 CreatePipelineLayout(); 22 CreatePipelineLayout();
23 CreateVertexShader(); 23 CreateVertexShader();
24 CreateRenderPass(frame_format); 24 CreateRenderPass(frame_format);
25 CreatePipeline(); 25 CreatePipelines();
26} 26}
27 27
28WindowAdaptPass::~WindowAdaptPass() = default; 28WindowAdaptPass::~WindowAdaptPass() = default;
@@ -34,7 +34,6 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
34 34
35 const VkFramebuffer host_framebuffer{*dst->framebuffer}; 35 const VkFramebuffer host_framebuffer{*dst->framebuffer};
36 const VkRenderPass renderpass{*render_pass}; 36 const VkRenderPass renderpass{*render_pass};
37 const VkPipeline graphics_pipeline{*pipeline};
38 const VkPipelineLayout graphics_pipeline_layout{*pipeline_layout}; 37 const VkPipelineLayout graphics_pipeline_layout{*pipeline_layout};
39 const VkExtent2D render_area{ 38 const VkExtent2D render_area{
40 .width = dst->width, 39 .width = dst->width,
@@ -44,9 +43,23 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
44 const size_t layer_count = configs.size(); 43 const size_t layer_count = configs.size();
45 std::vector<PresentPushConstants> push_constants(layer_count); 44 std::vector<PresentPushConstants> push_constants(layer_count);
46 std::vector<VkDescriptorSet> descriptor_sets(layer_count); 45 std::vector<VkDescriptorSet> descriptor_sets(layer_count);
46 std::vector<VkPipeline> graphics_pipelines(layer_count);
47 47
48 auto layer_it = layers.begin(); 48 auto layer_it = layers.begin();
49 for (size_t i = 0; i < layer_count; i++) { 49 for (size_t i = 0; i < layer_count; i++) {
50 switch (configs[i].blending) {
51 case Tegra::BlendMode::Opaque:
52 default:
53 graphics_pipelines[i] = *opaque_pipeline;
54 break;
55 case Tegra::BlendMode::Premultiplied:
56 graphics_pipelines[i] = *premultiplied_pipeline;
57 break;
58 case Tegra::BlendMode::Coverage:
59 graphics_pipelines[i] = *coverage_pipeline;
60 break;
61 }
62
50 layer_it->ConfigureDraw(&push_constants[i], &descriptor_sets[i], rasterizer, *sampler, 63 layer_it->ConfigureDraw(&push_constants[i], &descriptor_sets[i], rasterizer, *sampler,
51 image_index, configs[i], layout); 64 image_index, configs[i], layout);
52 layer_it++; 65 layer_it++;
@@ -77,8 +90,8 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
77 BeginRenderPass(cmdbuf, renderpass, host_framebuffer, render_area); 90 BeginRenderPass(cmdbuf, renderpass, host_framebuffer, render_area);
78 cmdbuf.ClearAttachments({clear_attachment}, {clear_rect}); 91 cmdbuf.ClearAttachments({clear_attachment}, {clear_rect});
79 92
80 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
81 for (size_t i = 0; i < layer_count; i++) { 93 for (size_t i = 0; i < layer_count; i++) {
94 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipelines[i]);
82 cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 95 cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
83 push_constants[i]); 96 push_constants[i]);
84 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0, 97 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0,
@@ -129,9 +142,13 @@ void WindowAdaptPass::CreateRenderPass(VkFormat frame_format) {
129 render_pass = CreateWrappedRenderPass(device, frame_format, VK_IMAGE_LAYOUT_UNDEFINED); 142 render_pass = CreateWrappedRenderPass(device, frame_format, VK_IMAGE_LAYOUT_UNDEFINED);
130} 143}
131 144
132void WindowAdaptPass::CreatePipeline() { 145void WindowAdaptPass::CreatePipelines() {
133 pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout, 146 opaque_pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout,
134 std::tie(vertex_shader, fragment_shader), false); 147 std::tie(vertex_shader, fragment_shader));
148 premultiplied_pipeline = CreateWrappedPremultipliedBlendingPipeline(
149 device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader));
150 coverage_pipeline = CreateWrappedCoverageBlendingPipeline(
151 device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader));
135} 152}
136 153
137} // namespace Vulkan 154} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.h b/src/video_core/renderer_vulkan/present/window_adapt_pass.h
index 0e2edfc31..cf667a4fc 100644
--- a/src/video_core/renderer_vulkan/present/window_adapt_pass.h
+++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.h
@@ -42,7 +42,7 @@ private:
42 void CreatePipelineLayout(); 42 void CreatePipelineLayout();
43 void CreateVertexShader(); 43 void CreateVertexShader();
44 void CreateRenderPass(VkFormat frame_format); 44 void CreateRenderPass(VkFormat frame_format);
45 void CreatePipeline(); 45 void CreatePipelines();
46 46
47private: 47private:
48 const Device& device; 48 const Device& device;
@@ -52,7 +52,9 @@ private:
52 vk::ShaderModule vertex_shader; 52 vk::ShaderModule vertex_shader;
53 vk::ShaderModule fragment_shader; 53 vk::ShaderModule fragment_shader;
54 vk::RenderPass render_pass; 54 vk::RenderPass render_pass;
55 vk::Pipeline pipeline; 55 vk::Pipeline opaque_pipeline;
56 vk::Pipeline premultiplied_pipeline;
57 vk::Pipeline coverage_pipeline;
56}; 58};
57 59
58} // namespace Vulkan 60} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 48a105327..d50417116 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -19,7 +19,9 @@
19#include "core/core_timing.h" 19#include "core/core_timing.h"
20#include "core/frontend/graphics_context.h" 20#include "core/frontend/graphics_context.h"
21#include "core/telemetry_session.h" 21#include "core/telemetry_session.h"
22#include "video_core/capture.h"
22#include "video_core/gpu.h" 23#include "video_core/gpu.h"
24#include "video_core/present.h"
23#include "video_core/renderer_vulkan/present/util.h" 25#include "video_core/renderer_vulkan/present/util.h"
24#include "video_core/renderer_vulkan/renderer_vulkan.h" 26#include "video_core/renderer_vulkan/renderer_vulkan.h"
25#include "video_core/renderer_vulkan/vk_blit_screen.h" 27#include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -38,6 +40,20 @@
38 40
39namespace Vulkan { 41namespace Vulkan {
40namespace { 42namespace {
43
44constexpr VkExtent2D CaptureImageSize{
45 .width = VideoCore::Capture::LinearWidth,
46 .height = VideoCore::Capture::LinearHeight,
47};
48
49constexpr VkExtent3D CaptureImageExtent{
50 .width = VideoCore::Capture::LinearWidth,
51 .height = VideoCore::Capture::LinearHeight,
52 .depth = VideoCore::Capture::LinearDepth,
53};
54
55constexpr VkFormat CaptureFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
56
41std::string GetReadableVersion(u32 version) { 57std::string GetReadableVersion(u32 version) {
42 return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version), 58 return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
43 VK_VERSION_PATCH(version)); 59 VK_VERSION_PATCH(version));
@@ -99,10 +115,15 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
99 render_window.GetFramebufferLayout().height), 115 render_window.GetFramebufferLayout().height),
100 present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain, 116 present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain,
101 surface), 117 surface),
102 blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler), 118 blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler,
103 blit_screenshot(device_memory, device, memory_allocator, present_manager, scheduler), 119 PresentFiltersForDisplay),
120 blit_capture(device_memory, device, memory_allocator, present_manager, scheduler,
121 PresentFiltersForDisplay),
122 blit_applet(device_memory, device, memory_allocator, present_manager, scheduler,
123 PresentFiltersForAppletCapture),
104 rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, 124 rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
105 scheduler) { 125 scheduler),
126 applet_frame() {
106 if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) { 127 if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
107 turbo_mode.emplace(instance, dld); 128 turbo_mode.emplace(instance, dld);
108 scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); }); 129 scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
@@ -125,6 +146,8 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
125 146
126 SCOPE_EXIT({ render_window.OnFrameDisplayed(); }); 147 SCOPE_EXIT({ render_window.OnFrameDisplayed(); });
127 148
149 RenderAppletCaptureLayer(framebuffers);
150
128 if (!render_window.IsShown()) { 151 if (!render_window.IsShown()) {
129 return; 152 return;
130 } 153 }
@@ -167,30 +190,20 @@ void RendererVulkan::Report() const {
167 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); 190 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
168} 191}
169 192
170void Vulkan::RendererVulkan::RenderScreenshot( 193vk::Buffer RendererVulkan::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
171 std::span<const Tegra::FramebufferConfig> framebuffers) { 194 const Layout::FramebufferLayout& layout, VkFormat format,
172 if (!renderer_settings.screenshot_requested) { 195 VkDeviceSize buffer_size) {
173 return;
174 }
175
176 constexpr VkFormat ScreenshotFormat{VK_FORMAT_B8G8R8A8_UNORM};
177 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
178
179 auto frame = [&]() { 196 auto frame = [&]() {
180 Frame f{}; 197 Frame f{};
181 f.image = CreateWrappedImage(memory_allocator, VkExtent2D{layout.width, layout.height}, 198 f.image =
182 ScreenshotFormat); 199 CreateWrappedImage(memory_allocator, VkExtent2D{layout.width, layout.height}, format);
183 f.image_view = CreateWrappedImageView(device, f.image, ScreenshotFormat); 200 f.image_view = CreateWrappedImageView(device, f.image, format);
184 f.framebuffer = blit_screenshot.CreateFramebuffer(layout, *f.image_view, ScreenshotFormat); 201 f.framebuffer = blit_capture.CreateFramebuffer(layout, *f.image_view, format);
185 return f; 202 return f;
186 }(); 203 }();
187 204
188 blit_screenshot.DrawToFrame(rasterizer, &frame, framebuffers, layout, 1, 205 auto dst_buffer = CreateWrappedBuffer(memory_allocator, buffer_size, MemoryUsage::Download);
189 VK_FORMAT_B8G8R8A8_UNORM); 206 blit_capture.DrawToFrame(rasterizer, &frame, framebuffers, layout, 1, format);
190
191 const auto dst_buffer = CreateWrappedBuffer(
192 memory_allocator, static_cast<VkDeviceSize>(layout.width * layout.height * 4),
193 MemoryUsage::Download);
194 207
195 scheduler.RequestOutsideRenderPassOperationContext(); 208 scheduler.RequestOutsideRenderPassOperationContext();
196 scheduler.Record([&](vk::CommandBuffer cmdbuf) { 209 scheduler.Record([&](vk::CommandBuffer cmdbuf) {
@@ -198,15 +211,68 @@ void Vulkan::RendererVulkan::RenderScreenshot(
198 VkExtent3D{layout.width, layout.height, 1}); 211 VkExtent3D{layout.width, layout.height, 1});
199 }); 212 });
200 213
201 // Ensure the copy is fully completed before saving the screenshot 214 // Ensure the copy is fully completed before saving the capture
202 scheduler.Finish(); 215 scheduler.Finish();
203 216
204 // Copy backing image data to the QImage screenshot buffer 217 // Copy backing image data to the capture buffer
205 dst_buffer.Invalidate(); 218 dst_buffer.Invalidate();
219 return dst_buffer;
220}
221
222void RendererVulkan::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
223 if (!renderer_settings.screenshot_requested) {
224 return;
225 }
226
227 const auto& layout{renderer_settings.screenshot_framebuffer_layout};
228 const auto dst_buffer = RenderToBuffer(framebuffers, layout, VK_FORMAT_B8G8R8A8_UNORM,
229 layout.width * layout.height * 4);
230
206 std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(), 231 std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(),
207 dst_buffer.Mapped().size()); 232 dst_buffer.Mapped().size());
208 renderer_settings.screenshot_complete_callback(false); 233 renderer_settings.screenshot_complete_callback(false);
209 renderer_settings.screenshot_requested = false; 234 renderer_settings.screenshot_requested = false;
210} 235}
211 236
237std::vector<u8> RendererVulkan::GetAppletCaptureBuffer() {
238 using namespace VideoCore::Capture;
239
240 std::vector<u8> out(VideoCore::Capture::TiledSize);
241
242 if (!applet_frame.image) {
243 return out;
244 }
245
246 const auto dst_buffer =
247 CreateWrappedBuffer(memory_allocator, VideoCore::Capture::TiledSize, MemoryUsage::Download);
248
249 scheduler.RequestOutsideRenderPassOperationContext();
250 scheduler.Record([&](vk::CommandBuffer cmdbuf) {
251 DownloadColorImage(cmdbuf, *applet_frame.image, *dst_buffer, CaptureImageExtent);
252 });
253
254 // Ensure the copy is fully completed before writing the capture
255 scheduler.Finish();
256
257 // Swizzle image data to the capture buffer
258 dst_buffer.Invalidate();
259 Tegra::Texture::SwizzleTexture(out, dst_buffer.Mapped(), BytesPerPixel, LinearWidth,
260 LinearHeight, LinearDepth, BlockHeight, BlockDepth);
261
262 return out;
263}
264
265void RendererVulkan::RenderAppletCaptureLayer(
266 std::span<const Tegra::FramebufferConfig> framebuffers) {
267 if (!applet_frame.image) {
268 applet_frame.image = CreateWrappedImage(memory_allocator, CaptureImageSize, CaptureFormat);
269 applet_frame.image_view = CreateWrappedImageView(device, applet_frame.image, CaptureFormat);
270 applet_frame.framebuffer = blit_applet.CreateFramebuffer(
271 VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat);
272 }
273
274 blit_applet.DrawToFrame(rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1,
275 CaptureFormat);
276}
277
212} // namespace Vulkan 278} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index c6d8a0f21..fb9d83412 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -48,6 +48,8 @@ public:
48 48
49 void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override; 49 void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override;
50 50
51 std::vector<u8> GetAppletCaptureBuffer() override;
52
51 VideoCore::RasterizerInterface* ReadRasterizer() override { 53 VideoCore::RasterizerInterface* ReadRasterizer() override {
52 return &rasterizer; 54 return &rasterizer;
53 } 55 }
@@ -59,7 +61,11 @@ public:
59private: 61private:
60 void Report() const; 62 void Report() const;
61 63
64 vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
65 const Layout::FramebufferLayout& layout, VkFormat format,
66 VkDeviceSize buffer_size);
62 void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers); 67 void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
68 void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
63 69
64 Core::TelemetrySession& telemetry_session; 70 Core::TelemetrySession& telemetry_session;
65 Tegra::MaxwellDeviceMemoryManager& device_memory; 71 Tegra::MaxwellDeviceMemoryManager& device_memory;
@@ -79,9 +85,12 @@ private:
79 Swapchain swapchain; 85 Swapchain swapchain;
80 PresentManager present_manager; 86 PresentManager present_manager;
81 BlitScreen blit_swapchain; 87 BlitScreen blit_swapchain;
82 BlitScreen blit_screenshot; 88 BlitScreen blit_capture;
89 BlitScreen blit_applet;
83 RasterizerVulkan rasterizer; 90 RasterizerVulkan rasterizer;
84 std::optional<TurboMode> turbo_mode; 91 std::optional<TurboMode> turbo_mode;
92
93 Frame applet_frame;
85}; 94};
86 95
87} // namespace Vulkan 96} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 2275fcc46..b7797f833 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "video_core/framebuffer_config.h" 4#include "video_core/framebuffer_config.h"
5#include "video_core/present.h"
5#include "video_core/renderer_vulkan/present/filters.h" 6#include "video_core/renderer_vulkan/present/filters.h"
6#include "video_core/renderer_vulkan/present/layer.h" 7#include "video_core/renderer_vulkan/present/layer.h"
7#include "video_core/renderer_vulkan/vk_blit_screen.h" 8#include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -12,9 +13,9 @@ namespace Vulkan {
12 13
13BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_, 14BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_,
14 MemoryAllocator& memory_allocator_, PresentManager& present_manager_, 15 MemoryAllocator& memory_allocator_, PresentManager& present_manager_,
15 Scheduler& scheduler_) 16 Scheduler& scheduler_, const PresentFilters& filters_)
16 : device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_}, 17 : device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_},
17 present_manager{present_manager_}, scheduler{scheduler_}, image_count{1}, 18 present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1},
18 swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {} 19 swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {}
19 20
20BlitScreen::~BlitScreen() = default; 21BlitScreen::~BlitScreen() = default;
@@ -27,7 +28,7 @@ void BlitScreen::WaitIdle() {
27 28
28void BlitScreen::SetWindowAdaptPass() { 29void BlitScreen::SetWindowAdaptPass() {
29 layers.clear(); 30 layers.clear();
30 scaling_filter = Settings::values.scaling_filter.GetValue(); 31 scaling_filter = filters.get_scaling_filter();
31 32
32 switch (scaling_filter) { 33 switch (scaling_filter) {
33 case Settings::ScalingFilter::NearestNeighbor: 34 case Settings::ScalingFilter::NearestNeighbor:
@@ -59,7 +60,7 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
59 bool presentation_recreate_required = false; 60 bool presentation_recreate_required = false;
60 61
61 // Recreate dynamic resources if the adapting filter changed 62 // Recreate dynamic resources if the adapting filter changed
62 if (!window_adapt || scaling_filter != Settings::values.scaling_filter.GetValue()) { 63 if (!window_adapt || scaling_filter != filters.get_scaling_filter()) {
63 resource_update_required = true; 64 resource_update_required = true;
64 } 65 }
65 66
@@ -102,7 +103,7 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
102 103
103 while (layers.size() < framebuffers.size()) { 104 while (layers.size() < framebuffers.size()) {
104 layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count, 105 layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
105 window_size, window_adapt->GetDescriptorSetLayout()); 106 window_size, window_adapt->GetDescriptorSetLayout(), filters);
106 } 107 }
107 108
108 // Perform the draw 109 // Perform the draw
@@ -119,8 +120,7 @@ vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& l
119 VkFormat current_view_format) { 120 VkFormat current_view_format) {
120 const bool format_updated = 121 const bool format_updated =
121 std::exchange(swapchain_view_format, current_view_format) != current_view_format; 122 std::exchange(swapchain_view_format, current_view_format) != current_view_format;
122 if (!window_adapt || scaling_filter != Settings::values.scaling_filter.GetValue() || 123 if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) {
123 format_updated) {
124 WaitIdle(); 124 WaitIdle();
125 SetWindowAdaptPass(); 125 SetWindowAdaptPass();
126 } 126 }
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index cbdf2d5d0..531c57fc5 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -16,6 +16,8 @@ namespace Core {
16class System; 16class System;
17} 17}
18 18
19struct PresentFilters;
20
19namespace Tegra { 21namespace Tegra {
20struct FramebufferConfig; 22struct FramebufferConfig;
21} 23}
@@ -47,7 +49,7 @@ class BlitScreen {
47public: 49public:
48 explicit BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory, const Device& device, 50 explicit BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory, const Device& device,
49 MemoryAllocator& memory_allocator, PresentManager& present_manager, 51 MemoryAllocator& memory_allocator, PresentManager& present_manager,
50 Scheduler& scheduler); 52 Scheduler& scheduler, const PresentFilters& filters);
51 ~BlitScreen(); 53 ~BlitScreen();
52 54
53 void DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, 55 void DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
@@ -70,6 +72,7 @@ private:
70 MemoryAllocator& memory_allocator; 72 MemoryAllocator& memory_allocator;
71 PresentManager& present_manager; 73 PresentManager& present_manager;
72 Scheduler& scheduler; 74 Scheduler& scheduler;
75 const PresentFilters& filters;
73 std::size_t image_count{}; 76 std::size_t image_count{};
74 std::size_t image_index{}; 77 std::size_t image_index{};
75 VkFormat swapchain_view_format{}; 78 VkFormat swapchain_view_format{};
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 25c4a0957..01c3561c9 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -746,7 +746,13 @@ std::pair<typename P::ImageView*, bool> TextureCache<P>::TryFindFramebufferImage
746 }(); 746 }();
747 747
748 const auto GetImageViewForFramebuffer = [&](ImageId image_id) { 748 const auto GetImageViewForFramebuffer = [&](ImageId image_id) {
749 const ImageViewInfo info{ImageViewType::e2D, view_format}; 749 ImageViewInfo info{ImageViewType::e2D, view_format};
750 if (config.blending == Tegra::BlendMode::Opaque) {
751 info.x_source = static_cast<u8>(SwizzleSource::R);
752 info.y_source = static_cast<u8>(SwizzleSource::G);
753 info.z_source = static_cast<u8>(SwizzleSource::B);
754 info.w_source = static_cast<u8>(SwizzleSource::OneFloat);
755 }
750 return std::make_pair(&slot_image_views[FindOrEmplaceImageView(image_id, info)], 756 return std::make_pair(&slot_image_views[FindOrEmplaceImageView(image_id, info)],
751 slot_images[image_id].IsRescaled()); 757 slot_images[image_id].IsRescaled());
752 }; 758 };
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 76f06da12..0259a8c29 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -41,6 +41,9 @@ add_executable(yuzu
41 configuration/configuration_shared.cpp 41 configuration/configuration_shared.cpp
42 configuration/configuration_shared.h 42 configuration/configuration_shared.h
43 configuration/configure.ui 43 configuration/configure.ui
44 configuration/configure_applets.cpp
45 configuration/configure_applets.h
46 configuration/configure_applets.ui
44 configuration/configure_audio.cpp 47 configuration/configure_audio.cpp
45 configuration/configure_audio.h 48 configuration/configure_audio.h
46 configuration/configure_audio.ui 49 configuration/configure_audio.ui
diff --git a/src/yuzu/configuration/configure_applets.cpp b/src/yuzu/configuration/configure_applets.cpp
new file mode 100644
index 000000000..513ecb548
--- /dev/null
+++ b/src/yuzu/configuration/configure_applets.cpp
@@ -0,0 +1,86 @@
1// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/settings.h"
5#include "core/core.h"
6#include "ui_configure_applets.h"
7#include "yuzu/configuration/configuration_shared.h"
8#include "yuzu/configuration/configure_applets.h"
9#include "yuzu/configuration/shared_widget.h"
10
11ConfigureApplets::ConfigureApplets(Core::System& system_,
12 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
13 const ConfigurationShared::Builder& builder, QWidget* parent)
14 : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureApplets>()}, system{system_} {
15 ui->setupUi(this);
16
17 Setup(builder);
18
19 SetConfiguration();
20}
21
22ConfigureApplets::~ConfigureApplets() = default;
23
24void ConfigureApplets::changeEvent(QEvent* event) {
25 if (event->type() == QEvent::LanguageChange) {
26 RetranslateUI();
27 }
28
29 QWidget::changeEvent(event);
30}
31
32void ConfigureApplets::RetranslateUI() {
33 ui->retranslateUi(this);
34}
35
36void ConfigureApplets::Setup(const ConfigurationShared::Builder& builder) {
37 auto& library_applets_layout = *ui->group_library_applet_modes->layout();
38 std::map<u32, QWidget*> applets_hold{};
39
40 std::vector<Settings::BasicSetting*> settings;
41 auto push = [&settings](auto& list) {
42 for (auto setting : list) {
43 settings.push_back(setting);
44 }
45 };
46
47 push(Settings::values.linkage.by_category[Settings::Category::LibraryApplet]);
48
49 for (auto setting : settings) {
50 ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
51
52 if (widget == nullptr) {
53 continue;
54 }
55 if (!widget->Valid()) {
56 widget->deleteLater();
57 continue;
58 }
59
60 // Untested applets
61 if (setting->Id() == Settings::values.data_erase_applet_mode.Id() ||
62 setting->Id() == Settings::values.error_applet_mode.Id() ||
63 setting->Id() == Settings::values.net_connect_applet_mode.Id() ||
64 setting->Id() == Settings::values.web_applet_mode.Id() ||
65 setting->Id() == Settings::values.shop_applet_mode.Id() ||
66 setting->Id() == Settings::values.login_share_applet_mode.Id() ||
67 setting->Id() == Settings::values.wifi_web_auth_applet_mode.Id() ||
68 setting->Id() == Settings::values.my_page_applet_mode.Id()) {
69 widget->setHidden(true);
70 }
71
72 applets_hold.emplace(setting->Id(), widget);
73 }
74 for (const auto& [label, widget] : applets_hold) {
75 library_applets_layout.addWidget(widget);
76 }
77}
78
79void ConfigureApplets::SetConfiguration() {}
80
81void ConfigureApplets::ApplyConfiguration() {
82 const bool powered_on = system.IsPoweredOn();
83 for (const auto& func : apply_funcs) {
84 func(powered_on);
85 }
86}
diff --git a/src/yuzu/configuration/configure_applets.h b/src/yuzu/configuration/configure_applets.h
new file mode 100644
index 000000000..54f494d2f
--- /dev/null
+++ b/src/yuzu/configuration/configure_applets.h
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QWidget>
7#include "yuzu/configuration/configuration_shared.h"
8
9class QCheckBox;
10class QLineEdit;
11class QComboBox;
12class QDateTimeEdit;
13namespace Core {
14class System;
15}
16
17namespace Ui {
18class ConfigureApplets;
19}
20
21namespace ConfigurationShared {
22class Builder;
23}
24
25class ConfigureApplets : public ConfigurationShared::Tab {
26public:
27 explicit ConfigureApplets(Core::System& system_,
28 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
29 const ConfigurationShared::Builder& builder,
30 QWidget* parent = nullptr);
31 ~ConfigureApplets() override;
32
33 void ApplyConfiguration() override;
34 void SetConfiguration() override;
35
36private:
37 void changeEvent(QEvent* event) override;
38 void RetranslateUI();
39
40 void Setup(const ConfigurationShared::Builder& builder);
41
42 std::vector<std::function<void(bool)>> apply_funcs{};
43
44 std::unique_ptr<Ui::ConfigureApplets> ui;
45 bool enabled = false;
46
47 Core::System& system;
48};
diff --git a/src/yuzu/configuration/configure_applets.ui b/src/yuzu/configuration/configure_applets.ui
new file mode 100644
index 000000000..6f2ca66bd
--- /dev/null
+++ b/src/yuzu/configuration/configure_applets.ui
@@ -0,0 +1,65 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureApplets</class>
4 <widget class="QWidget" name="ConfigureApplets">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>605</width>
10 <height>300</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <property name="accessibleName">
17 <string>Applets</string>
18 </property>
19 <layout class="QVBoxLayout" name="verticalLayout_1">
20 <item>
21 <layout class="QVBoxLayout" name="verticalLayout">
22 <item>
23 <widget class="QGroupBox" name="group_library_applet_modes">
24 <property name="title">
25 <string>Applet mode preference</string>
26 </property>
27 <layout class="QVBoxLayout">
28 <item>
29 <widget class="QWidget" name="applets_widget" native="true">
30 <layout class="QVBoxLayout" name="verticalLayout_3">
31 <property name="leftMargin">
32 <number>0</number>
33 </property>
34 <property name="topMargin">
35 <number>0</number>
36 </property>
37 <property name="rightMargin">
38 <number>0</number>
39 </property>
40 </layout>
41 </widget>
42 </item>
43 </layout>
44 </widget>
45 </item>
46 </layout>
47 </item>
48 <item>
49 <spacer name="verticalSpacer">
50 <property name="orientation">
51 <enum>Qt::Vertical</enum>
52 </property>
53 <property name="sizeHint" stdset="0">
54 <size>
55 <width>20</width>
56 <height>40</height>
57 </size>
58 </property>
59 </spacer>
60 </item>
61 </layout>
62 </widget>
63 <resources/>
64 <connections/>
65</ui>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index aab54a1cc..37f23388e 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -8,6 +8,7 @@
8#include "core/core.h" 8#include "core/core.h"
9#include "ui_configure.h" 9#include "ui_configure.h"
10#include "vk_device_info.h" 10#include "vk_device_info.h"
11#include "yuzu/configuration/configure_applets.h"
11#include "yuzu/configuration/configure_audio.h" 12#include "yuzu/configuration/configure_audio.h"
12#include "yuzu/configuration/configure_cpu.h" 13#include "yuzu/configuration/configure_cpu.h"
13#include "yuzu/configuration/configure_debug_tab.h" 14#include "yuzu/configuration/configure_debug_tab.h"
@@ -34,6 +35,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
34 : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, 35 : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
35 registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>( 36 registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
36 this, !system_.IsPoweredOn())}, 37 this, !system_.IsPoweredOn())},
38 applets_tab{std::make_unique<ConfigureApplets>(system_, nullptr, *builder, this)},
37 audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)}, 39 audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
38 cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)}, 40 cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},
39 debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)}, 41 debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
@@ -58,6 +60,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
58 60
59 ui->setupUi(this); 61 ui->setupUi(this);
60 62
63 ui->tabWidget->addTab(applets_tab.get(), tr("Applets"));
61 ui->tabWidget->addTab(audio_tab.get(), tr("Audio")); 64 ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
62 ui->tabWidget->addTab(cpu_tab.get(), tr("CPU")); 65 ui->tabWidget->addTab(cpu_tab.get(), tr("CPU"));
63 ui->tabWidget->addTab(debug_tab_tab.get(), tr("Debug")); 66 ui->tabWidget->addTab(debug_tab_tab.get(), tr("Debug"));
@@ -124,6 +127,7 @@ void ConfigureDialog::ApplyConfiguration() {
124 debug_tab_tab->ApplyConfiguration(); 127 debug_tab_tab->ApplyConfiguration();
125 web_tab->ApplyConfiguration(); 128 web_tab->ApplyConfiguration();
126 network_tab->ApplyConfiguration(); 129 network_tab->ApplyConfiguration();
130 applets_tab->ApplyConfiguration();
127 system.ApplySettings(); 131 system.ApplySettings();
128 Settings::LogSettings(); 132 Settings::LogSettings();
129} 133}
@@ -161,7 +165,8 @@ void ConfigureDialog::PopulateSelectionList() {
161 {{tr("General"), 165 {{tr("General"),
162 {general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}}, 166 {general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}},
163 {tr("System"), 167 {tr("System"),
164 {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get()}}, 168 {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(),
169 applets_tab.get()}},
165 {tr("CPU"), {cpu_tab.get()}}, 170 {tr("CPU"), {cpu_tab.get()}},
166 {tr("Graphics"), {graphics_tab.get(), graphics_advanced_tab.get()}}, 171 {tr("Graphics"), {graphics_tab.get(), graphics_advanced_tab.get()}},
167 {tr("Audio"), {audio_tab.get()}}, 172 {tr("Audio"), {audio_tab.get()}},
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index b28ce288c..d0a24a07b 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -15,6 +15,7 @@ namespace Core {
15class System; 15class System;
16} 16}
17 17
18class ConfigureApplets;
18class ConfigureAudio; 19class ConfigureAudio;
19class ConfigureCpu; 20class ConfigureCpu;
20class ConfigureDebugTab; 21class ConfigureDebugTab;
@@ -75,6 +76,7 @@ private:
75 std::unique_ptr<ConfigurationShared::Builder> builder; 76 std::unique_ptr<ConfigurationShared::Builder> builder;
76 std::vector<ConfigurationShared::Tab*> tab_group; 77 std::vector<ConfigurationShared::Tab*> tab_group;
77 78
79 std::unique_ptr<ConfigureApplets> applets_tab;
78 std::unique_ptr<ConfigureAudio> audio_tab; 80 std::unique_ptr<ConfigureAudio> audio_tab;
79 std::unique_ptr<ConfigureCpu> cpu_tab; 81 std::unique_ptr<ConfigureCpu> cpu_tab;
80 std::unique_ptr<ConfigureDebugTab> debug_tab_tab; 82 std::unique_ptr<ConfigureDebugTab> debug_tab_tab;
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index ed9c7d859..d138b53c8 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -26,6 +26,23 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
26 26
27 // A setting can be ignored by giving it a blank name 27 // A setting can be ignored by giving it a blank name
28 28
29 // Applets
30 INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QStringLiteral());
31 INSERT(Settings, controller_applet_mode, tr("Controller configuration"), QStringLiteral());
32 INSERT(Settings, data_erase_applet_mode, tr("Data erase"), QStringLiteral());
33 INSERT(Settings, error_applet_mode, tr("Error"), QStringLiteral());
34 INSERT(Settings, net_connect_applet_mode, tr("Net connect"), QStringLiteral());
35 INSERT(Settings, player_select_applet_mode, tr("Player select"), QStringLiteral());
36 INSERT(Settings, swkbd_applet_mode, tr("Software keyboard"), QStringLiteral());
37 INSERT(Settings, mii_edit_applet_mode, tr("Mii Edit"), QStringLiteral());
38 INSERT(Settings, web_applet_mode, tr("Online web"), QStringLiteral());
39 INSERT(Settings, shop_applet_mode, tr("Shop"), QStringLiteral());
40 INSERT(Settings, photo_viewer_applet_mode, tr("Photo viewer"), QStringLiteral());
41 INSERT(Settings, offline_web_applet_mode, tr("Offline web"), QStringLiteral());
42 INSERT(Settings, login_share_applet_mode, tr("Login share"), QStringLiteral());
43 INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QStringLiteral());
44 INSERT(Settings, my_page_applet_mode, tr("My page"), QStringLiteral());
45
29 // Audio 46 // Audio
30 INSERT(Settings, sink_id, tr("Output Engine:"), QStringLiteral()); 47 INSERT(Settings, sink_id, tr("Output Engine:"), QStringLiteral());
31 INSERT(Settings, audio_output_device_id, tr("Output Device:"), QStringLiteral()); 48 INSERT(Settings, audio_output_device_id, tr("Output Device:"), QStringLiteral());
@@ -37,13 +54,28 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
37 QStringLiteral()); 54 QStringLiteral());
38 55
39 // Core 56 // Core
40 INSERT(Settings, use_multi_core, tr("Multicore CPU Emulation"), QStringLiteral()); 57 INSERT(
41 INSERT(Settings, memory_layout_mode, tr("Memory Layout"), QStringLiteral()); 58 Settings, use_multi_core, tr("Multicore CPU Emulation"),
59 tr("This option increases CPU emulation thread use from 1 to the Switch’s maximum of 4.\n"
60 "This is mainly a debug option and shouldn’t be disabled."));
61 INSERT(
62 Settings, memory_layout_mode, tr("Memory Layout"),
63 tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the "
64 "developer kit's 8/6GB.\nIt’s doesn’t improve stability or performance and is intended "
65 "to let big texture mods fit in emulated RAM.\nEnabling it will increase memory "
66 "use. It is not recommended to enable unless a specific game with a texture mod needs "
67 "it."));
42 INSERT(Settings, use_speed_limit, QStringLiteral(), QStringLiteral()); 68 INSERT(Settings, use_speed_limit, QStringLiteral(), QStringLiteral());
43 INSERT(Settings, speed_limit, tr("Limit Speed Percent"), QStringLiteral()); 69 INSERT(Settings, speed_limit, tr("Limit Speed Percent"),
70 tr("Controls the game's maximum rendering speed, but it’s up to each game if it runs "
71 "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a "
72 "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the "
73 "maximum your PC can reach."));
44 74
45 // Cpu 75 // Cpu
46 INSERT(Settings, cpu_accuracy, tr("Accuracy:"), QStringLiteral()); 76 INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
77 tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless "
78 "you know what you are doing."));
47 INSERT(Settings, cpu_backend, tr("Backend:"), QStringLiteral()); 79 INSERT(Settings, cpu_backend, tr("Backend:"), QStringLiteral());
48 80
49 // Cpu Debug 81 // Cpu Debug
@@ -63,34 +95,75 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
63 tr("This option improves the speed of 32 bits ASIMD floating-point functions by running " 95 tr("This option improves the speed of 32 bits ASIMD floating-point functions by running "
64 "with incorrect rounding modes.")); 96 "with incorrect rounding modes."));
65 INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"), 97 INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"),
66 tr("This option improves speed by removing NaN checking. Please note this also reduces " 98 tr("This option improves speed by removing NaN checking.\nPlease note this also reduces "
67 "accuracy of certain floating-point instructions.")); 99 "accuracy of certain floating-point instructions."));
68 INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"), 100 INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"),
69 tr("This option improves speed by eliminating a safety check before every memory " 101 tr("This option improves speed by eliminating a safety check before every memory "
70 "read/write " 102 "read/write in guest.\nDisabling it may allow a game to read/write the emulator's "
71 "in guest. Disabling it may allow a game to read/write the emulator's memory.")); 103 "memory."));
72 INSERT( 104 INSERT(
73 Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"), 105 Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"),
74 tr("This option improves speed by relying only on the semantics of cmpxchg to ensure " 106 tr("This option improves speed by relying only on the semantics of cmpxchg to ensure "
75 "safety of exclusive access instructions. Please note this may result in deadlocks and " 107 "safety of exclusive access instructions.\nPlease note this may result in deadlocks and "
76 "other race conditions.")); 108 "other race conditions."));
77 109
78 // Renderer 110 // Renderer
79 INSERT(Settings, renderer_backend, tr("API:"), QStringLiteral()); 111 INSERT(
80 INSERT(Settings, vulkan_device, tr("Device:"), QStringLiteral()); 112 Settings, renderer_backend, tr("API:"),
81 INSERT(Settings, shader_backend, tr("Shader Backend:"), QStringLiteral()); 113 tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases."));
82 INSERT(Settings, resolution_setup, tr("Resolution:"), QStringLiteral()); 114 INSERT(Settings, vulkan_device, tr("Device:"),
115 tr("This setting selects the GPU to use with the Vulkan backend."));
116 INSERT(Settings, shader_backend, tr("Shader Backend:"),
117 tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in "
118 "performance and the best in rendering accuracy.\n"
119 "GLASM is a deprecated NVIDIA-only backend that offers much better shader building "
120 "performance at the cost of FPS and rendering accuracy.\n"
121 "SPIR-V compiles the fastest, but yields poor results on most GPU drivers."));
122 INSERT(Settings, resolution_setup, tr("Resolution:"),
123 tr("Forces the game to render at a different resolution.\nHigher resolutions require "
124 "much more VRAM and bandwidth.\n"
125 "Options lower than 1X can cause rendering issues."));
83 INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QStringLiteral()); 126 INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QStringLiteral());
84 INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), QStringLiteral()); 127 INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"),
85 INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), QStringLiteral()); 128 tr("Determines how sharpened the image will look while using FSR’s dynamic contrast."));
86 INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), QStringLiteral()); 129 INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
87 INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), QStringLiteral()); 130 tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a "
88 INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"), QStringLiteral()); 131 "lower performance impact and can produce a better and more stable picture under "
89 INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), 132 "very low resolutions."));
90 QStringLiteral()); 133 INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
91 INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), QStringLiteral()); 134 tr("The method used to render the window in fullscreen.\nBorderless offers the best "
92 INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), QStringLiteral()); 135 "compatibility with the on-screen keyboard that some games request for "
93 INSERT(Settings, astc_recompression, tr("ASTC Recompression Method:"), QStringLiteral()); 136 "input.\nExclusive "
137 "fullscreen may offer better performance and better Freesync/Gsync support."));
138 INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"),
139 tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support "
140 "16:9, so custom game mods are required to get other ratios.\nAlso controls the "
141 "aspect ratio of captured screenshots."));
142 INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"),
143 tr("Allows saving shaders to storage for faster loading on following game "
144 "boots.\nDisabling "
145 "it is only intended for debugging."));
146 INSERT(
147 Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
148 tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
149 INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
150 tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
151 "decoding, or perform no decoding at all (black screen on videos).\n"
152 "In most cases, GPU decoding provides the best performance."));
153 INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"),
154 tr("This option controls how ASTC textures should be decoded.\n"
155 "CPU: Use the CPU for decoding, slowest but safest method.\n"
156 "GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most "
157 "games and users.\n"
158 "CPU Asynchronously: Use the CPU to decode ASTC textures as they arrive. Completely "
159 "eliminates ASTC decoding\nstuttering at the cost of rendering issues while the "
160 "texture is being decoded."));
161 INSERT(
162 Settings, astc_recompression, tr("ASTC Recompression Method:"),
163 tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing "
164 "the emulator to decompress to an intermediate format any card supports, RGBA8.\n"
165 "This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but "
166 "negatively affecting image quality."));
94 INSERT( 167 INSERT(
95 Settings, vsync_mode, tr("VSync Mode:"), 168 Settings, vsync_mode, tr("VSync Mode:"),
96 tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " 169 tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
@@ -104,22 +177,29 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
104 177
105 // Renderer (Advanced Graphics) 178 // Renderer (Advanced Graphics)
106 INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"), 179 INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
107 QStringLiteral()); 180 tr("Slightly improves performance by moving presentation to a separate CPU thread."));
108 INSERT( 181 INSERT(
109 Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"), 182 Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"),
110 tr("Runs work in the background while waiting for graphics commands to keep the GPU from " 183 tr("Runs work in the background while waiting for graphics commands to keep the GPU from "
111 "lowering its clock speed.")); 184 "lowering its clock speed."));
112 INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), QStringLiteral()); 185 INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"),
113 INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"), QStringLiteral()); 186 tr("Controls the quality of texture rendering at oblique angles.\nIt’s a light setting "
114 INSERT( 187 "and safe to set at 16x on most GPUs."));
115 Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"), 188 INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"),
116 tr("Enables asynchronous shader compilation, which may reduce shader stutter. This feature " 189 tr("GPU emulation accuracy.\nMost games render fine with Normal, but High is still "
117 "is experimental.")); 190 "required for some.\nParticles tend to only render correctly with High "
191 "accuracy.\nExtreme should only be used for debugging.\nThis option can "
192 "be changed while playing.\nSome games may require booting on high to render "
193 "properly."));
194 INSERT(Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"),
195 tr("Enables asynchronous shader compilation, which may reduce shader stutter.\nThis "
196 "feature "
197 "is experimental."));
118 INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"), 198 INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"),
119 tr("Enables Fast GPU Time. This option will force most games to run at their highest " 199 tr("Enables Fast GPU Time. This option will force most games to run at their highest "
120 "native resolution.")); 200 "native resolution."));
121 INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"), 201 INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"),
122 tr("Enables GPU vendor-specific pipeline cache. This option can improve shader loading " 202 tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading "
123 "time significantly in cases where the Vulkan driver does not store pipeline cache " 203 "time significantly in cases where the Vulkan driver does not store pipeline cache "
124 "files internally.")); 204 "files internally."));
125 INSERT( 205 INSERT(
@@ -140,19 +220,27 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
140 // Renderer (Debug) 220 // Renderer (Debug)
141 221
142 // System 222 // System
143 INSERT(Settings, rng_seed, tr("RNG Seed"), QStringLiteral()); 223 INSERT(Settings, rng_seed, tr("RNG Seed"),
224 tr("Controls the seed of the random number generator.\nMainly used for speedrunning "
225 "purposes."));
144 INSERT(Settings, rng_seed_enabled, QStringLiteral(), QStringLiteral()); 226 INSERT(Settings, rng_seed_enabled, QStringLiteral(), QStringLiteral());
145 INSERT(Settings, device_name, tr("Device Name"), QStringLiteral()); 227 INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch."));
146 INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), QStringLiteral()); 228 INSERT(Settings, custom_rtc, tr("Custom RTC Date:"),
229 tr("This option allows to change the emulated clock of the Switch.\n"
230 "Can be used to manipulate time in games."));
147 INSERT(Settings, custom_rtc_enabled, QStringLiteral(), QStringLiteral()); 231 INSERT(Settings, custom_rtc_enabled, QStringLiteral(), QStringLiteral());
148 INSERT(Settings, custom_rtc_offset, QStringLiteral(" "), 232 INSERT(Settings, custom_rtc_offset, QStringLiteral(" "),
149 QStringLiteral("The number of seconds from the current unix time")); 233 QStringLiteral("The number of seconds from the current unix time"));
150 INSERT(Settings, language_index, tr("Language:"), 234 INSERT(Settings, language_index, tr("Language:"),
151 tr("Note: this can be overridden when region setting is auto-select")); 235 tr("Note: this can be overridden when region setting is auto-select"));
152 INSERT(Settings, region_index, tr("Region:"), QStringLiteral()); 236 INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch."));
153 INSERT(Settings, time_zone_index, tr("Time Zone:"), QStringLiteral()); 237 INSERT(Settings, time_zone_index, tr("Time Zone:"),
238 tr("The time zone of the emulated Switch."));
154 INSERT(Settings, sound_index, tr("Sound Output Mode:"), QStringLiteral()); 239 INSERT(Settings, sound_index, tr("Sound Output Mode:"), QStringLiteral());
155 INSERT(Settings, use_docked_mode, tr("Console Mode:"), QStringLiteral()); 240 INSERT(Settings, use_docked_mode, tr("Console Mode:"),
241 tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change "
242 "their resolution, details and supported controllers and depending on this setting.\n"
243 "Setting to Handheld can help improve performance for low end systems."));
156 INSERT(Settings, current_user, QStringLiteral(), QStringLiteral()); 244 INSERT(Settings, current_user, QStringLiteral(), QStringLiteral());
157 245
158 // Controls 246 // Controls
@@ -170,14 +258,19 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
170 // Ui 258 // Ui
171 259
172 // Ui General 260 // Ui General
173 INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"), QStringLiteral()); 261 INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"),
262 tr("Ask to select a user profile on each boot, useful if multiple people use yuzu on "
263 "the same PC."));
174 INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"), 264 INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"),
175 QStringLiteral()); 265 tr("This setting pauses yuzu when focusing other windows."));
176 INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"), 266 INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"),
177 QStringLiteral()); 267 tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling "
178 INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), QStringLiteral()); 268 "it bypasses such prompts and directly exits the emulation."));
269 INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"),
270 tr("This setting hides the mouse after 2.5s of inactivity."));
179 INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), 271 INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
180 QStringLiteral()); 272 tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest "
273 "attempts to open the controller applet, it is immediately closed."));
181 274
182 // Linux 275 // Linux
183 INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral()); 276 INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral());
@@ -203,6 +296,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
203#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)} 296#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
204 297
205 // Intentionally skipping VSyncMode to let the UI fill that one out 298 // Intentionally skipping VSyncMode to let the UI fill that one out
299 translations->insert({Settings::EnumMetadata<Settings::AppletMode>::Index(),
300 {
301 PAIR(AppletMode, HLE, tr("Custom frontend")),
302 PAIR(AppletMode, LLE, tr("Real applet")),
303 }});
206 304
207 translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(), 305 translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
208 { 306 {
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 170f14684..1931dcd1f 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -190,10 +190,8 @@ void ControllerShortcut::ControllerUpdateEvent(Core::HID::ControllerTriggerType
190 if (type != Core::HID::ControllerTriggerType::Button) { 190 if (type != Core::HID::ControllerTriggerType::Button) {
191 return; 191 return;
192 } 192 }
193 if (!Settings::values.controller_navigation) { 193 if (button_sequence.npad.raw == Core::HID::NpadButton::None &&
194 return; 194 button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) {
195 }
196 if (button_sequence.npad.raw == Core::HID::NpadButton::None) {
197 return; 195 return;
198 } 196 }
199 197