summaryrefslogtreecommitdiffstats
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() {
requireActivity(),
titleId = R.string.delete_save_data,
descriptionId = R.string.delete_save_data_warning_description,
- positiveAction = {
+ positiveButtonTitleId = android.R.string.cancel,
+ negativeButtonTitleId = android.R.string.ok,
+ negativeAction = {
File(args.game.saveDir).deleteRecursively()
Toast.makeText(
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 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
-import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -16,18 +15,52 @@ import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.model.MessageDialogViewModel
+import org.yuzu.yuzu_emu.utils.Log
class MessageDialogFragment : DialogFragment() {
private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE_ID)
- val titleString = requireArguments().getString(TITLE_STRING)!!
+ val title = if (titleId != 0) {
+ getString(titleId)
+ } else {
+ requireArguments().getString(TITLE_STRING)!!
+ }
+
val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
- val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
+ val description = if (descriptionId != 0) {
+ getString(descriptionId)
+ } else {
+ requireArguments().getString(DESCRIPTION_STRING)!!
+ }
+
+ val positiveButtonId = requireArguments().getInt(POSITIVE_BUTTON_TITLE_ID)
+ val positiveButtonString = requireArguments().getString(POSITIVE_BUTTON_TITLE_STRING)!!
+ val positiveButton = if (positiveButtonId != 0) {
+ getString(positiveButtonId)
+ } else if (positiveButtonString.isNotEmpty()) {
+ positiveButtonString
+ } else if (messageDialogViewModel.positiveAction != null) {
+ getString(R.string.close)
+ } else {
+ getString(android.R.string.ok)
+ }
+
+ val negativeButtonId = requireArguments().getInt(NEGATIVE_BUTTON_TITLE_ID)
+ val negativeButtonString = requireArguments().getString(NEGATIVE_BUTTON_TITLE_STRING)!!
+ val negativeButton = if (negativeButtonId != 0) {
+ getString(negativeButtonId)
+ } else if (negativeButtonString.isNotEmpty()) {
+ negativeButtonString
+ } else {
+ getString(android.R.string.cancel)
+ }
+
val helpLinkId = requireArguments().getInt(HELP_LINK)
val dismissible = requireArguments().getBoolean(DISMISSIBLE)
- val clearPositiveAction = requireArguments().getBoolean(CLEAR_POSITIVE_ACTION)
+ val clearPositiveAction = requireArguments().getBoolean(CLEAR_ACTIONS)
+ val showNegativeButton = requireArguments().getBoolean(SHOW_NEGATIVE_BUTTON)
val builder = MaterialAlertDialogBuilder(requireContext())
@@ -35,21 +68,19 @@ class MessageDialogFragment : DialogFragment() {
messageDialogViewModel.positiveAction = null
}
- if (messageDialogViewModel.positiveAction == null) {
- builder.setPositiveButton(R.string.close, null)
- } else {
- builder.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
- messageDialogViewModel.positiveAction?.invoke()
- }.setNegativeButton(android.R.string.cancel, null)
+ builder.setPositiveButton(positiveButton) { _, _ ->
+ messageDialogViewModel.positiveAction?.invoke()
+ }
+ if (messageDialogViewModel.negativeAction != null || showNegativeButton) {
+ builder.setNegativeButton(negativeButton) { _, _ ->
+ messageDialogViewModel.negativeAction?.invoke()
+ }
}
- if (titleId != 0) builder.setTitle(titleId)
- if (titleString.isNotEmpty()) builder.setTitle(titleString)
-
- if (descriptionId != 0) {
- builder.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
+ if (title.isNotEmpty()) builder.setTitle(title)
+ if (description.isNotEmpty()) {
+ builder.setMessage(Html.fromHtml(description, Html.FROM_HTML_MODE_LEGACY))
}
- if (descriptionString.isNotEmpty()) builder.setMessage(descriptionString)
if (helpLinkId != 0) {
builder.setNeutralButton(R.string.learn_more) { _, _ ->
@@ -76,8 +107,41 @@ class MessageDialogFragment : DialogFragment() {
private const val DESCRIPTION_STRING = "DescriptionString"
private const val HELP_LINK = "Link"
private const val DISMISSIBLE = "Dismissible"
- private const val CLEAR_POSITIVE_ACTION = "ClearPositiveAction"
-
+ private const val CLEAR_ACTIONS = "ClearActions"
+ private const val POSITIVE_BUTTON_TITLE_ID = "PositiveButtonTitleId"
+ private const val POSITIVE_BUTTON_TITLE_STRING = "PositiveButtonTitleString"
+ private const val SHOW_NEGATIVE_BUTTON = "ShowNegativeButton"
+ private const val NEGATIVE_BUTTON_TITLE_ID = "NegativeButtonTitleId"
+ private const val NEGATIVE_BUTTON_TITLE_STRING = "NegativeButtonTitleString"
+
+ /**
+ * Creates a new [MessageDialogFragment] instance.
+ * @param activity Activity that will hold a [MessageDialogViewModel] instance if using
+ * [positiveAction] or [negativeAction].
+ * @param titleId String resource ID that will be used for the title. [titleString] used if 0.
+ * @param titleString String that will be used for the title. No title is set if empty.
+ * @param descriptionId String resource ID that will be used for the description.
+ * [descriptionString] used if 0.
+ * @param descriptionString String that will be used for the description.
+ * No description is set if empty.
+ * @param helpLinkId String resource ID that contains a help link. Will be added as a neutral
+ * button with the title R.string.help.
+ * @param dismissible Whether the dialog is dismissible or not. Typically used to ensure that
+ * the user clicks on one of the dialog buttons before closing.
+ * @param positiveButtonTitleId String resource ID that will be used for the positive button.
+ * [positiveButtonTitleString] used if 0.
+ * @param positiveButtonTitleString String that will be used for the positive button.
+ * android.R.string.ok used if empty. android.R.string.close will be used if [positiveAction]
+ * is not null.
+ * @param positiveAction Lambda to run when the positive button is clicked.
+ * @param showNegativeButton Normally the negative button isn't shown if there is no
+ * [negativeAction] set. This can override that behavior to always show a button.
+ * @param negativeButtonTitleId String resource ID that will be used for the negative button.
+ * [negativeButtonTitleString] used if 0.
+ * @param negativeButtonTitleString String that will be used for the negative button.
+ * android.R.string.cancel used if empty.
+ * @param negativeAction Lambda to run when the negative button is clicked
+ */
fun newInstance(
activity: FragmentActivity? = null,
titleId: Int = 0,
@@ -86,16 +150,27 @@ class MessageDialogFragment : DialogFragment() {
descriptionString: String = "",
helpLinkId: Int = 0,
dismissible: Boolean = true,
- positiveAction: (() -> Unit)? = null
+ positiveButtonTitleId: Int = 0,
+ positiveButtonTitleString: String = "",
+ positiveAction: (() -> Unit)? = null,
+ showNegativeButton: Boolean = false,
+ negativeButtonTitleId: Int = 0,
+ negativeButtonTitleString: String = "",
+ negativeAction: (() -> Unit)? = null
): MessageDialogFragment {
- var clearPositiveAction = false
+ var clearActions = false
if (activity != null) {
ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
clear()
this.positiveAction = positiveAction
+ this.negativeAction = negativeAction
}
} else {
- clearPositiveAction = true
+ clearActions = true
+ }
+
+ if (activity == null && (positiveAction == null || negativeAction == null)) {
+ Log.warning("[$TAG] Tried to set action with no activity!")
}
val dialog = MessageDialogFragment()
@@ -106,7 +181,12 @@ class MessageDialogFragment : DialogFragment() {
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
putBoolean(DISMISSIBLE, dismissible)
- putBoolean(CLEAR_POSITIVE_ACTION, clearPositiveAction)
+ putBoolean(CLEAR_ACTIONS, clearActions)
+ putInt(POSITIVE_BUTTON_TITLE_ID, positiveButtonTitleId)
+ putString(POSITIVE_BUTTON_TITLE_STRING, positiveButtonTitleString)
+ putBoolean(SHOW_NEGATIVE_BUTTON, showNegativeButton)
+ putInt(NEGATIVE_BUTTON_TITLE_ID, negativeButtonTitleId)
+ putString(NEGATIVE_BUTTON_TITLE_STRING, negativeButtonTitleString)
}
dialog.arguments = bundle
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
class MessageDialogViewModel : ViewModel() {
var positiveAction: (() -> Unit)? = null
+ var negativeAction: (() -> Unit)? = null
fun clear() {
positiveAction = null
+ negativeAction = null
}
}
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 {
#define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED>
#define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED>
+SETTING(AppletMode, false);
SETTING(AudioEngine, false);
SETTING(bool, false);
SETTING(int, false);
@@ -215,6 +216,8 @@ const char* TranslateCategory(Category category) {
return "Debugging";
case Category::GpuDriver:
return "GpuDriver";
+ case Category::LibraryApplet:
+ return "LibraryApplet";
case Category::Miscellaneous:
return "Miscellaneous";
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 {
struct Values {
Linkage linkage{};
+ // Applet
+ Setting<AppletMode> cabinet_applet_mode{linkage, AppletMode::LLE, "cabinet_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> controller_applet_mode{linkage, AppletMode::HLE, "controller_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> data_erase_applet_mode{linkage, AppletMode::HLE, "data_erase_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> error_applet_mode{linkage, AppletMode::HLE, "error_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> player_select_applet_mode{
+ linkage, AppletMode::HLE, "player_select_applet_mode", Category::LibraryApplet};
+ Setting<AppletMode> swkbd_applet_mode{linkage, AppletMode::LLE, "swkbd_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> mii_edit_applet_mode{linkage, AppletMode::LLE, "mii_edit_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> web_applet_mode{linkage, AppletMode::HLE, "web_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> shop_applet_mode{linkage, AppletMode::HLE, "shop_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> photo_viewer_applet_mode{
+ linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
+ Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
+ Category::LibraryApplet};
+ Setting<AppletMode> wifi_web_auth_applet_mode{
+ linkage, AppletMode::HLE, "wifi_web_auth_applet_mode", Category::LibraryApplet};
+ Setting<AppletMode> my_page_applet_mode{linkage, AppletMode::LLE, "my_page_applet_mode",
+ Category::LibraryApplet};
+
// Audio
SwitchableSetting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine",
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 {
Services,
Paths,
Linux,
+ LibraryApplet,
MaxEnum,
};
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);
ENUM(ConsoleMode, Handheld, Docked);
+ENUM(AppletMode, HLE, LLE);
+
template <typename Type>
inline std::string CanonicalizeEnum(Type id) {
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
hle/service/audio/hwopus.h
hle/service/bcat/backend/backend.cpp
hle/service/bcat/backend/backend.h
+ hle/service/bcat/news/newly_arrived_event_holder.cpp
+ hle/service/bcat/news/newly_arrived_event_holder.h
+ hle/service/bcat/news/news_data_service.cpp
+ hle/service/bcat/news/news_data_service.h
+ hle/service/bcat/news/news_database_service.cpp
+ hle/service/bcat/news/news_database_service.h
+ hle/service/bcat/news/news_service.cpp
+ hle/service/bcat/news/news_service.h
+ hle/service/bcat/news/overwrite_event_holder.cpp
+ hle/service/bcat/news/overwrite_event_holder.h
+ hle/service/bcat/news/service_creator.cpp
+ hle/service/bcat/news/service_creator.h
hle/service/bcat/bcat.cpp
hle/service/bcat/bcat.h
- hle/service/bcat/bcat_module.cpp
- hle/service/bcat/bcat_module.h
+ hle/service/bcat/bcat_result.h
+ hle/service/bcat/bcat_service.cpp
+ hle/service/bcat/bcat_service.h
+ hle/service/bcat/bcat_types.h
+ hle/service/bcat/bcat_util.h
+ hle/service/bcat/delivery_cache_directory_service.cpp
+ hle/service/bcat/delivery_cache_directory_service.h
+ hle/service/bcat/delivery_cache_file_service.cpp
+ hle/service/bcat/delivery_cache_file_service.h
+ hle/service/bcat/delivery_cache_progress_service.cpp
+ hle/service/bcat/delivery_cache_progress_service.h
+ hle/service/bcat/delivery_cache_storage_service.cpp
+ hle/service/bcat/delivery_cache_storage_service.h
+ hle/service/bcat/service_creator.cpp
+ hle/service/bcat/service_creator.h
hle/service/bpc/bpc.cpp
hle/service/bpc/bpc.h
hle/service/btdrv/btdrv.cpp
@@ -548,8 +573,6 @@ add_library(core STATIC
hle/service/es/es.h
hle/service/eupld/eupld.cpp
hle/service/eupld/eupld.h
- hle/service/event.cpp
- hle/service/event.h
hle/service/fatal/fatal.cpp
hle/service/fatal/fatal.h
hle/service/fatal/fatal_p.cpp
@@ -676,8 +699,6 @@ add_library(core STATIC
hle/service/mm/mm_u.h
hle/service/mnpp/mnpp_app.cpp
hle/service/mnpp/mnpp_app.h
- hle/service/mutex.cpp
- hle/service/mutex.h
hle/service/ncm/ncm.cpp
hle/service/ncm/ncm.h
hle/service/nfc/common/amiibo_crypto.cpp
@@ -790,6 +811,15 @@ add_library(core STATIC
hle/service/nvnflinger/window.h
hle/service/olsc/olsc.cpp
hle/service/olsc/olsc.h
+ hle/service/os/event.cpp
+ hle/service/os/event.h
+ hle/service/os/multi_wait_holder.cpp
+ hle/service/os/multi_wait_holder.h
+ hle/service/os/multi_wait_utils.h
+ hle/service/os/multi_wait.cpp
+ hle/service/os/multi_wait.h
+ hle/service/os/mutex.cpp
+ hle/service/os/mutex.h
hle/service/pcie/pcie.cpp
hle/service/pcie/pcie.h
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 {
return reader->GetSdkAddonVersion();
}
+u8 NCA::GetKeyGeneration() const {
+ return reader->GetKeyGeneration();
+}
+
bool NCA::IsUpdate() const {
return is_update;
}
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:
u64 GetTitleId() const;
RightsId GetRightsId() const;
u32 GetSDKVersion() const;
+ u8 GetKeyGeneration() const;
bool IsUpdate() const;
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};
constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
+constexpr Result ResultPermissionDenied{ErrorModule::FS, 6400};
constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
} // 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 @@
#include <random>
#include "common/scope_exit.h"
#include "common/settings.h"
+#include "core/arm/dynarmic/arm_dynarmic.h"
+#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
#include "core/core.h"
-#include "core/gpu_dirty_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
@@ -1258,6 +1259,10 @@ void KProcess::InitializeInterfaces() {
#ifdef HAS_NCE
if (this->IsApplication() && Settings::IsNceEnabled()) {
+ // Register the scoped JIT handler before creating any NCE instances
+ // so that its signal handler will appear first in the signal chain.
+ Core::ScopedJitExecution::RegisterHandler();
+
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
m_arm_interfaces[i] = std::make_unique<Core::ArmNce>(m_kernel.System(), true, i);
}
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 {
enum class LibraryAppletMode : u32 {
AllForeground = 0,
- Background = 1,
- NoUI = 2,
- BackgroundIndirectDisplay = 3,
+ PartialForeground = 1,
+ NoUi = 2,
+ PartialForegroundIndirectDisplay = 3,
AllForegroundInitiallyHidden = 4,
};
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 @@
#include "common/math_util.h"
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/caps/caps_types.h"
-#include "core/hle/service/event.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
#include "core/hle/service/service.h"
#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 @@
#include <memory>
#include <mutex>
-#include "core/hle/service/event.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
union Result;
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() {
case LibraryAppletMode::AllForeground:
InitializeForeground();
break;
- case LibraryAppletMode::Background:
- case LibraryAppletMode::BackgroundIndirectDisplay:
- InitializeBackground(applet_mode);
+ case LibraryAppletMode::PartialForeground:
+ case LibraryAppletMode::PartialForegroundIndirectDisplay:
+ InitializePartialForeground(applet_mode);
break;
default:
ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode);
@@ -243,7 +243,7 @@ void SoftwareKeyboard::InitializeForeground() {
InitializeFrontendNormalKeyboard();
}
-void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) {
+void SoftwareKeyboard::InitializePartialForeground(LibraryAppletMode library_applet_mode) {
LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet.");
is_background = true;
@@ -258,9 +258,9 @@ void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mod
swkbd_inline_initialize_arg.size());
if (swkbd_initialize_arg.library_applet_mode_flag) {
- ASSERT(library_applet_mode == LibraryAppletMode::Background);
+ ASSERT(library_applet_mode == LibraryAppletMode::PartialForeground);
} else {
- ASSERT(library_applet_mode == LibraryAppletMode::BackgroundIndirectDisplay);
+ ASSERT(library_applet_mode == LibraryAppletMode::PartialForegroundIndirectDisplay);
}
}
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:
void InitializeForeground();
/// Initializes the inline software keyboard.
- void InitializeBackground(LibraryAppletMode library_applet_mode);
+ void InitializePartialForeground(LibraryAppletMode library_applet_mode);
/// Processes the text check sent by the application.
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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/settings.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/am/applet_data_broker.h"
#include "core/hle/service/am/applet_manager.h"
@@ -16,6 +17,34 @@ namespace Service::AM {
namespace {
+bool ShouldCreateGuestApplet(AppletId applet_id) {
+#define X(Name, name) \
+ if (applet_id == AppletId::Name && \
+ Settings::values.name##_applet_mode.GetValue() != Settings::AppletMode::LLE) { \
+ return false; \
+ }
+
+ X(Cabinet, cabinet)
+ X(Controller, controller)
+ X(DataErase, data_erase)
+ X(Error, error)
+ X(NetConnect, net_connect)
+ X(ProfileSelect, player_select)
+ X(SoftwareKeyboard, swkbd)
+ X(MiiEdit, mii_edit)
+ X(Web, web)
+ X(Shop, shop)
+ X(PhotoViewer, photo_viewer)
+ X(OfflineWeb, offline_web)
+ X(LoginShare, login_share)
+ X(WebAuth, wifi_web_auth)
+ X(MyPage, my_page)
+
+#undef X
+
+ return true;
+}
+
AppletProgramId AppletIdToProgramId(AppletId applet_id) {
switch (applet_id) {
case AppletId::OverlayDisplay:
@@ -63,17 +92,26 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
}
}
-[[maybe_unused]] std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(
- Core::System& system, std::shared_ptr<Applet> caller_applet, AppletId applet_id,
- LibraryAppletMode mode) {
+std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
+ std::shared_ptr<Applet> caller_applet,
+ AppletId applet_id,
+ LibraryAppletMode mode) {
const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id));
if (program_id == 0) {
// Unknown applet
return {};
}
+ // TODO: enable other versions of applets
+ enum : u8 {
+ Firmware1400 = 14,
+ Firmware1500 = 15,
+ Firmware1600 = 16,
+ Firmware1700 = 17,
+ };
+
auto process = std::make_unique<Process>(system);
- if (!process->Initialize(program_id)) {
+ if (!process->Initialize(program_id, Firmware1400, Firmware1700)) {
// Couldn't initialize the guest process
return {};
}
@@ -87,24 +125,18 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
// Set focus state
switch (mode) {
case LibraryAppletMode::AllForeground:
- case LibraryAppletMode::NoUI:
- applet->focus_state = FocusState::InFocus;
+ case LibraryAppletMode::NoUi:
+ case LibraryAppletMode::PartialForeground:
+ case LibraryAppletMode::PartialForegroundIndirectDisplay:
applet->hid_registration.EnableAppletToGetInput(true);
+ applet->focus_state = FocusState::InFocus;
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
- applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
break;
case LibraryAppletMode::AllForegroundInitiallyHidden:
- applet->system_buffer_manager.SetWindowVisibility(false);
- applet->focus_state = FocusState::NotInFocus;
applet->hid_registration.EnableAppletToGetInput(false);
- applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
- break;
- case LibraryAppletMode::Background:
- case LibraryAppletMode::BackgroundIndirectDisplay:
- default:
- applet->focus_state = FocusState::Background;
- applet->hid_registration.EnableAppletToGetInput(true);
- applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
+ applet->focus_state = FocusState::NotInFocus;
+ applet->system_buffer_manager.SetWindowVisibility(false);
+ applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
break;
}
@@ -117,9 +149,10 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
return std::make_shared<ILibraryAppletAccessor>(system, broker, applet);
}
-[[maybe_unused]] std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(
- Core::System& system, std::shared_ptr<Applet> caller_applet, AppletId applet_id,
- LibraryAppletMode mode) {
+std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& system,
+ std::shared_ptr<Applet> caller_applet,
+ AppletId applet_id,
+ LibraryAppletMode mode) {
const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id));
auto process = std::make_unique<Process>(system);
@@ -163,7 +196,13 @@ void ILibraryAppletCreator::CreateLibraryApplet(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id,
applet_mode);
- auto library_applet = CreateFrontendApplet(system, applet, applet_id, applet_mode);
+ std::shared_ptr<ILibraryAppletAccessor> library_applet;
+ if (ShouldCreateGuestApplet(applet_id)) {
+ library_applet = CreateGuestApplet(system, applet, applet_id, applet_mode);
+ }
+ if (!library_applet) {
+ library_applet = CreateFrontendApplet(system, applet, applet_id, applet_mode);
+ }
if (!library_applet) {
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
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 @@
#include "common/scope_exit.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/kernel/k_process.h"
@@ -20,7 +21,7 @@ Process::~Process() {
this->Finalize();
}
-bool Process::Initialize(u64 program_id) {
+bool Process::Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) {
// First, ensure we are not holding another process.
this->Finalize();
@@ -29,21 +30,33 @@ bool Process::Initialize(u64 program_id) {
// Attempt to load program NCA.
const FileSys::RegisteredCache* bis_system{};
- FileSys::VirtualFile nca{};
+ FileSys::VirtualFile nca_raw{};
// Get the program NCA from built-in storage.
bis_system = fsc.GetSystemNANDContents();
if (bis_system) {
- nca = bis_system->GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
+ nca_raw = bis_system->GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
}
// Ensure we retrieved a program NCA.
- if (!nca) {
+ if (!nca_raw) {
return false;
}
+ // Ensure we have a suitable version.
+ if (minimum_key_generation > 0) {
+ FileSys::NCA nca(nca_raw);
+ if (nca.GetStatus() == Loader::ResultStatus::Success &&
+ (nca.GetKeyGeneration() < minimum_key_generation ||
+ nca.GetKeyGeneration() > maximum_key_generation)) {
+ LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
+ nca.GetKeyGeneration());
+ return false;
+ }
+ }
+
// Get the appropriate loader to parse this NCA.
- auto app_loader = Loader::GetLoader(m_system, nca, program_id, 0);
+ auto app_loader = Loader::GetLoader(m_system, nca_raw, program_id, 0);
// Ensure we have a loader which can parse the NCA.
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:
explicit Process(Core::System& system);
~Process();
- bool Initialize(u64 program_id);
+ bool Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation);
void Finalize();
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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/logging/log.h"
+#include "core/hle/result.h"
#include "core/hle/service/am/am_results.h"
#include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/am/self_controller.h"
#include "core/hle/service/caps/caps_su.h"
+#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
@@ -47,7 +50,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
{51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
- {61, nullptr, "SetMediaPlaybackState"},
+ {61, &ISelfController::SetMediaPlaybackState, "SetMediaPlaybackState"},
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
{63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
{64, nullptr, "SetInputDetectionSourceSet"},
@@ -288,7 +291,8 @@ void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
}
Result ISelfController::EnsureBufferSharingEnabled(Kernel::KProcess* process) {
- if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id)) {
+ if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id,
+ applet->library_applet_mode)) {
return ResultSuccess;
}
@@ -323,6 +327,16 @@ void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void ISelfController::SetMediaPlaybackState(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u8 state = rp.Pop<u8>();
+
+ LOG_WARNING(Service_AM, "(STUBBED) called, state={}", state);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
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 @@
#pragma once
+#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
@@ -38,6 +39,7 @@ private:
void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
void SetHandlesRequestToDisplay(HLERequestContext& ctx);
void ApproveToDisplay(HLERequestContext& ctx);
+ void SetMediaPlaybackState(HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
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() {
// Clean up shared layers.
if (m_buffer_sharing_enabled) {
+ m_nvnflinger->GetSystemBufferManager().Finalize(m_process);
}
}
bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process,
- AppletId applet_id) {
+ AppletId applet_id, LibraryAppletMode mode) {
if (m_nvnflinger) {
return m_buffer_sharing_enabled;
}
@@ -36,9 +37,15 @@ bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel:
return false;
}
+ Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
+ if (mode == LibraryAppletMode::PartialForeground ||
+ mode == LibraryAppletMode::PartialForegroundIndirectDisplay) {
+ blending = Nvnflinger::LayerBlending::Coverage;
+ }
+
const auto display_id = m_nvnflinger->OpenDisplay("Default").value();
const auto res = m_nvnflinger->GetSystemBufferManager().Initialize(
- &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id);
+ m_process, &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id, blending);
if (res.IsSuccess()) {
m_buffer_sharing_enabled = true;
@@ -62,8 +69,12 @@ void SystemBufferManager::SetWindowVisibility(bool visible) {
Result SystemBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
s32* out_fbshare_layer_index) {
- // TODO
- R_SUCCEED();
+ if (!m_buffer_sharing_enabled) {
+ return VI::ResultPermissionDenied;
+ }
+
+ return m_nvnflinger->GetSystemBufferManager().WriteAppletCaptureBuffer(out_was_written,
+ out_fbshare_layer_index);
}
} // 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:
SystemBufferManager();
~SystemBufferManager();
- bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id);
+ bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id,
+ LibraryAppletMode mode);
void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
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) {
applet->hid_registration.EnableAppletToGetInput(visible);
if (visible) {
- applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
applet->focus_state = FocusState::InFocus;
+ applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
} else {
applet->focus_state = FocusState::NotInFocus;
+ applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
}
- applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
IPC::ResponseBuilder rb{ctx, 2};
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) {
}
void ProgressServiceBackend::StartConnecting() {
- impl.status = DeliveryCacheProgressImpl::Status::Connecting;
+ impl.status = DeliveryCacheProgressStatus::Connecting;
SignalUpdate();
}
void ProgressServiceBackend::StartProcessingDataList() {
- impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
+ impl.status = DeliveryCacheProgressStatus::ProcessingDataList;
SignalUpdate();
}
void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
std::string_view file_name, u64 file_size) {
- impl.status = DeliveryCacheProgressImpl::Status::Downloading;
+ impl.status = DeliveryCacheProgressStatus::Downloading;
impl.current_downloaded_bytes = 0;
impl.current_total_bytes = file_size;
std::memcpy(impl.current_directory.data(), dir_name.data(),
@@ -65,7 +65,7 @@ void ProgressServiceBackend::FinishDownloadingFile() {
}
void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
- impl.status = DeliveryCacheProgressImpl::Status::Committing;
+ impl.status = DeliveryCacheProgressStatus::Committing;
impl.current_file.fill(0);
impl.current_downloaded_bytes = 0;
impl.current_total_bytes = 0;
@@ -76,7 +76,7 @@ void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
void ProgressServiceBackend::FinishDownload(Result result) {
impl.total_downloaded_bytes = impl.total_bytes;
- impl.status = DeliveryCacheProgressImpl::Status::Done;
+ impl.status = DeliveryCacheProgressStatus::Done;
impl.result = result;
SignalUpdate();
}
@@ -85,15 +85,15 @@ void ProgressServiceBackend::SignalUpdate() {
update_event->Signal();
}
-Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
+BcatBackend::BcatBackend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
-Backend::~Backend() = default;
+BcatBackend::~BcatBackend() = default;
-NullBackend::NullBackend(DirectoryGetter getter) : Backend(std::move(getter)) {}
+NullBcatBackend::NullBcatBackend(DirectoryGetter getter) : BcatBackend(std::move(getter)) {}
-NullBackend::~NullBackend() = default;
+NullBcatBackend::~NullBcatBackend() = default;
-bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
+bool NullBcatBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
title.build_id);
@@ -101,8 +101,8 @@ bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& prog
return true;
}
-bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
- ProgressServiceBackend& progress) {
+bool NullBcatBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
title.build_id, name);
@@ -110,18 +110,18 @@ bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
return true;
}
-bool NullBackend::Clear(u64 title_id) {
+bool NullBcatBackend::Clear(u64 title_id) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
return true;
}
-void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
+void NullBcatBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
Common::HexToString(passphrase));
}
-std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
+std::optional<std::vector<u8>> NullBcatBackend::GetLaunchParameter(TitleIDVersion title) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
title.build_id);
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 @@
#include "common/common_types.h"
#include "core/file_sys/vfs/vfs_types.h"
#include "core/hle/result.h"
+#include "core/hle/service/bcat/bcat_types.h"
#include "core/hle/service/kernel_helpers.h"
namespace Core {
@@ -24,44 +25,6 @@ class KReadableEvent;
namespace Service::BCAT {
-struct DeliveryCacheProgressImpl;
-
-using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
-using Passphrase = std::array<u8, 0x20>;
-
-struct TitleIDVersion {
- u64 title_id;
- u64 build_id;
-};
-
-using DirectoryName = std::array<char, 0x20>;
-using FileName = std::array<char, 0x20>;
-
-struct DeliveryCacheProgressImpl {
- enum class Status : s32 {
- None = 0x0,
- Queued = 0x1,
- Connecting = 0x2,
- ProcessingDataList = 0x3,
- Downloading = 0x4,
- Committing = 0x5,
- Done = 0x9,
- };
-
- Status status;
- Result result = ResultSuccess;
- DirectoryName current_directory;
- FileName current_file;
- s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
- s64 current_total_bytes; ///< Bytes total on current file.
- s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
- s64 total_bytes; ///< Bytes total on overall download.
- INSERT_PADDING_BYTES(
- 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
-};
-static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
- "DeliveryCacheProgressImpl has incorrect size.");
-
// A class to manage the signalling to the game about BCAT download progress.
// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
class ProgressServiceBackend {
@@ -107,10 +70,10 @@ private:
};
// A class representing an abstract backend for BCAT functionality.
-class Backend {
+class BcatBackend {
public:
- explicit Backend(DirectoryGetter getter);
- virtual ~Backend();
+ explicit BcatBackend(DirectoryGetter getter);
+ virtual ~BcatBackend();
// Called when the backend is needed to synchronize the data for the game with title ID and
// version in title. A ProgressServiceBackend object is provided to alert the application of
@@ -135,10 +98,10 @@ protected:
};
// A backend of BCAT that provides no operation.
-class NullBackend : public Backend {
+class NullBcatBackend : public BcatBackend {
public:
- explicit NullBackend(DirectoryGetter getter);
- ~NullBackend() override;
+ explicit NullBcatBackend(DirectoryGetter getter);
+ ~NullBcatBackend() override;
bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
bool SynchronizeDirectory(TitleIDVersion title, std::string name,
@@ -151,6 +114,7 @@ public:
std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
};
-std::unique_ptr<Backend> CreateBackendFromSettings(Core::System& system, DirectoryGetter getter);
+std::unique_ptr<BcatBackend> CreateBackendFromSettings(Core::System& system,
+ DirectoryGetter getter);
} // 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 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/bcat/bcat.h"
+#include "core/hle/service/bcat/news/service_creator.h"
+#include "core/hle/service/bcat/service_creator.h"
+#include "core/hle/service/server_manager.h"
namespace Service::BCAT {
-BCAT::BCAT(Core::System& system_, std::shared_ptr<Module> module_,
- FileSystem::FileSystemController& fsc_, const char* name_)
- : Interface(system_, std::move(module_), fsc_, name_) {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &BCAT::CreateBcatService, "CreateBcatService"},
- {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
- {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
- {3, nullptr, "CreateDeliveryCacheProgressService"},
- {4, nullptr, "CreateDeliveryCacheProgressServiceWithApplicationId"},
- };
- // clang-format on
- RegisterHandlers(functions);
+void LoopProcess(Core::System& system) {
+ auto server_manager = std::make_unique<ServerManager>(system);
+
+ server_manager->RegisterNamedService("bcat:a",
+ std::make_shared<IServiceCreator>(system, "bcat:a"));
+ server_manager->RegisterNamedService("bcat:m",
+ std::make_shared<IServiceCreator>(system, "bcat:m"));
+ server_manager->RegisterNamedService("bcat:u",
+ std::make_shared<IServiceCreator>(system, "bcat:u"));
+ server_manager->RegisterNamedService("bcat:s",
+ std::make_shared<IServiceCreator>(system, "bcat:s"));
+
+ server_manager->RegisterNamedService(
+ "news:a", std::make_shared<News::IServiceCreator>(system, 0xffffffff, "news:a"));
+ server_manager->RegisterNamedService(
+ "news:p", std::make_shared<News::IServiceCreator>(system, 0x1, "news:p"));
+ server_manager->RegisterNamedService(
+ "news:c", std::make_shared<News::IServiceCreator>(system, 0x2, "news:c"));
+ server_manager->RegisterNamedService(
+ "news:v", std::make_shared<News::IServiceCreator>(system, 0x4, "news:v"));
+ server_manager->RegisterNamedService(
+ "news:m", std::make_shared<News::IServiceCreator>(system, 0xd, "news:m"));
+
+ ServerManager::RunServer(std::move(server_manager));
}
-BCAT::~BCAT() = default;
} // 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 @@
#pragma once
-#include "core/hle/service/bcat/bcat_module.h"
+#include "core/hle/service/service.h"
namespace Core {
class System;
@@ -11,11 +11,6 @@ class System;
namespace Service::BCAT {
-class BCAT final : public Module::Interface {
-public:
- explicit BCAT(Core::System& system_, std::shared_ptr<Module> module_,
- FileSystem::FileSystemController& fsc_, const char* name_);
- ~BCAT() override;
-};
+void LoopProcess(Core::System& system);
} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <cctype>
-#include <mbedtls/md5.h>
-#include "common/hex_util.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "common/string_util.h"
-#include "core/core.h"
-#include "core/file_sys/vfs/vfs.h"
-#include "core/hle/kernel/k_readable_event.h"
-#include "core/hle/service/bcat/backend/backend.h"
-#include "core/hle/service/bcat/bcat.h"
-#include "core/hle/service/bcat/bcat_module.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/server_manager.h"
-
-namespace Service::BCAT {
-
-constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
-constexpr Result ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
-constexpr Result ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
-constexpr Result ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
-
-// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
-// and if any of them have a non-zero result it just forwards that result. This is the FS error code
-// for permission denied, which is the closest approximation of this scenario.
-constexpr Result ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
-
-using BCATDigest = std::array<u8, 0x10>;
-
-namespace {
-
-u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
- u64 out{};
- std::memcpy(&out, id.data(), sizeof(u64));
- return out;
-}
-
-// The digest is only used to determine if a file is unique compared to others of the same name.
-// Since the algorithm isn't ever checked in game, MD5 is safe.
-BCATDigest DigestFile(const FileSys::VirtualFile& file) {
- BCATDigest out{};
- const auto bytes = file->ReadAllBytes();
- mbedtls_md5_ret(bytes.data(), bytes.size(), out.data());
- return out;
-}
-
-// For a name to be valid it must be non-empty, must have a null terminating character as the final
-// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
-// file.
-bool VerifyNameValidInternal(HLERequestContext& ctx, std::array<char, 0x20> name, char match_char) {
- const auto null_chars = std::count(name.begin(), name.end(), 0);
- const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
- return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
- });
- if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
- LOG_ERROR(Service_BCAT, "Name passed was invalid!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- return false;
- }
-
- return true;
-}
-
-bool VerifyNameValidDir(HLERequestContext& ctx, DirectoryName name) {
- return VerifyNameValidInternal(ctx, name, '-');
-}
-
-bool VerifyNameValidFile(HLERequestContext& ctx, FileName name) {
- return VerifyNameValidInternal(ctx, name, '.');
-}
-
-} // Anonymous namespace
-
-struct DeliveryCacheDirectoryEntry {
- FileName name;
- u64 size;
- BCATDigest digest;
-};
-
-class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
-public:
- explicit IDeliveryCacheProgressService(Core::System& system_, Kernel::KReadableEvent& event_,
- const DeliveryCacheProgressImpl& impl_)
- : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{event_}, impl{impl_} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
- {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void GetEvent(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(event);
- }
-
- void GetImpl(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- ctx.WriteBuffer(impl);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- Kernel::KReadableEvent& event;
- const DeliveryCacheProgressImpl& impl;
-};
-
-class IBcatService final : public ServiceFramework<IBcatService> {
-public:
- explicit IBcatService(Core::System& system_, Backend& backend_)
- : ServiceFramework{system_, "IBcatService"}, backend{backend_},
- progress{{
- ProgressServiceBackend{system_, "Normal"},
- ProgressServiceBackend{system_, "Directory"},
- }} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
- {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
- {10200, nullptr, "CancelSyncDeliveryCacheRequest"},
- {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
- {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
- {20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"},
- {20301, nullptr, "RequestSuspendDeliveryTask"},
- {20400, nullptr, "RegisterSystemApplicationDeliveryTask"},
- {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
- {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
- {30100, &IBcatService::SetPassphrase, "SetPassphrase"},
- {30101, nullptr, "Unknown30101"},
- {30102, nullptr, "Unknown30102"},
- {30200, nullptr, "RegisterBackgroundDeliveryTask"},
- {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
- {30202, nullptr, "BlockDeliveryTask"},
- {30203, nullptr, "UnblockDeliveryTask"},
- {30210, nullptr, "SetDeliveryTaskTimer"},
- {30300, nullptr, "RegisterSystemApplicationDeliveryTasks"},
- {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
- {90101, nullptr, "Unknown90101"},
- {90200, nullptr, "GetDeliveryList"},
- {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
- {90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"},
- {90300, nullptr, "GetPushNotificationLog"},
- {90301, nullptr, "Unknown90301"},
- };
- // clang-format on
- RegisterHandlers(functions);
- }
-
-private:
- enum class SyncType {
- Normal,
- Directory,
- Count,
- };
-
- std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
- auto& progress_backend{GetProgressBackend(type)};
- return std::make_shared<IDeliveryCacheProgressService>(system, progress_backend.GetEvent(),
- progress_backend.GetImpl());
- }
-
- void RequestSyncDeliveryCache(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- backend.Synchronize({system.GetApplicationProcessProgramID(),
- GetCurrentBuildID(system.GetApplicationProcessBuildID())},
- GetProgressBackend(SyncType::Normal));
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
- }
-
- void RequestSyncDeliveryCacheWithDirectoryName(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto name_raw = rp.PopRaw<DirectoryName>();
- const auto name =
- Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
-
- LOG_DEBUG(Service_BCAT, "called, name={}", name);
-
- backend.SynchronizeDirectory({system.GetApplicationProcessProgramID(),
- GetCurrentBuildID(system.GetApplicationProcessBuildID())},
- name, GetProgressBackend(SyncType::Directory));
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
- }
-
- void SetPassphrase(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto title_id = rp.PopRaw<u64>();
-
- const auto passphrase_raw = ctx.ReadBuffer();
-
- LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
- Common::HexToString(passphrase_raw));
-
- if (title_id == 0) {
- LOG_ERROR(Service_BCAT, "Invalid title ID!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- }
-
- if (passphrase_raw.size() > 0x40) {
- LOG_ERROR(Service_BCAT, "Passphrase too large!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- return;
- }
-
- Passphrase passphrase{};
- std::memcpy(passphrase.data(), passphrase_raw.data(),
- std::min(passphrase.size(), passphrase_raw.size()));
-
- backend.SetPassphrase(title_id, passphrase);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void ClearDeliveryCacheStorage(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto title_id = rp.PopRaw<u64>();
-
- LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
-
- if (title_id == 0) {
- LOG_ERROR(Service_BCAT, "Invalid title ID!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- return;
- }
-
- if (!backend.Clear(title_id)) {
- LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_FAILED_CLEAR_CACHE);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- ProgressServiceBackend& GetProgressBackend(SyncType type) {
- return progress.at(static_cast<size_t>(type));
- }
-
- const ProgressServiceBackend& GetProgressBackend(SyncType type) const {
- return progress.at(static_cast<size_t>(type));
- }
-
- Backend& backend;
- std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress;
-};
-
-void Module::Interface::CreateBcatService(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IBcatService>(system, *backend);
-}
-
-class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
-public:
- explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_)
- : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IDeliveryCacheFileService::Open, "Open"},
- {1, &IDeliveryCacheFileService::Read, "Read"},
- {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
- {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void Open(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto dir_name_raw = rp.PopRaw<DirectoryName>();
- const auto file_name_raw = rp.PopRaw<FileName>();
-
- const auto dir_name =
- Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
- const auto file_name =
- Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
-
- LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
-
- if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
- return;
-
- if (current_file != nullptr) {
- LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_ENTITY_ALREADY_OPEN);
- return;
- }
-
- const auto dir = root->GetSubdirectory(dir_name);
-
- if (dir == nullptr) {
- LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_FAILED_OPEN_ENTITY);
- return;
- }
-
- current_file = dir->GetFile(file_name);
-
- if (current_file == nullptr) {
- LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_FAILED_OPEN_ENTITY);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void Read(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto offset{rp.PopRaw<u64>()};
-
- auto size = ctx.GetWriteBufferSize();
-
- LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
-
- if (current_file == nullptr) {
- LOG_ERROR(Service_BCAT, "There is no file currently open!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NO_OPEN_ENTITY);
- }
-
- size = std::min<u64>(current_file->GetSize() - offset, size);
- const auto buffer = current_file->ReadBytes(size, offset);
- ctx.WriteBuffer(buffer);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push<u64>(buffer.size());
- }
-
- void GetSize(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- if (current_file == nullptr) {
- LOG_ERROR(Service_BCAT, "There is no file currently open!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NO_OPEN_ENTITY);
- }
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push<u64>(current_file->GetSize());
- }
-
- void GetDigest(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- if (current_file == nullptr) {
- LOG_ERROR(Service_BCAT, "There is no file currently open!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NO_OPEN_ENTITY);
- }
-
- IPC::ResponseBuilder rb{ctx, 6};
- rb.Push(ResultSuccess);
- rb.PushRaw(DigestFile(current_file));
- }
-
- FileSys::VirtualDir root;
- FileSys::VirtualFile current_file;
-};
-
-class IDeliveryCacheDirectoryService final
- : public ServiceFramework<IDeliveryCacheDirectoryService> {
-public:
- explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_)
- : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IDeliveryCacheDirectoryService::Open, "Open"},
- {1, &IDeliveryCacheDirectoryService::Read, "Read"},
- {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void Open(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto name_raw = rp.PopRaw<DirectoryName>();
- const auto name =
- Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
-
- LOG_DEBUG(Service_BCAT, "called, name={}", name);
-
- if (!VerifyNameValidDir(ctx, name_raw))
- return;
-
- if (current_dir != nullptr) {
- LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_ENTITY_ALREADY_OPEN);
- return;
- }
-
- current_dir = root->GetSubdirectory(name);
-
- if (current_dir == nullptr) {
- LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_FAILED_OPEN_ENTITY);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void Read(HLERequestContext& ctx) {
- auto write_size = ctx.GetWriteBufferNumElements<DeliveryCacheDirectoryEntry>();
-
- LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
-
- if (current_dir == nullptr) {
- LOG_ERROR(Service_BCAT, "There is no open directory!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NO_OPEN_ENTITY);
- return;
- }
-
- const auto files = current_dir->GetFiles();
- write_size = std::min<u64>(write_size, files.size());
- std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
- std::transform(
- files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
- FileName name{};
- std::memcpy(name.data(), file->GetName().data(),
- std::min(file->GetName().size(), name.size()));
- return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
- });
-
- ctx.WriteBuffer(entries);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry)));
- }
-
- void GetCount(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- if (current_dir == nullptr) {
- LOG_ERROR(Service_BCAT, "There is no open directory!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NO_OPEN_ENTITY);
- return;
- }
-
- const auto files = current_dir->GetFiles();
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(files.size()));
- }
-
- FileSys::VirtualDir root;
- FileSys::VirtualDir current_dir;
-};
-
-class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
-public:
- explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_)
- : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
- {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
- {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-
- for (const auto& subdir : root->GetSubdirectories()) {
- DirectoryName name{};
- std::memcpy(name.data(), subdir->GetName().data(),
- std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
- entries.push_back(name);
- }
- }
-
-private:
- void CreateFileService(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDeliveryCacheFileService>(system, root);
- }
-
- void CreateDirectoryService(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDeliveryCacheDirectoryService>(system, root);
- }
-
- void EnumerateDeliveryCacheDirectory(HLERequestContext& ctx) {
- auto size = ctx.GetWriteBufferNumElements<DirectoryName>();
-
- LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
-
- size = std::min<u64>(size, entries.size() - next_read_index);
- ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
- next_read_index += size;
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(size));
- }
-
- FileSys::VirtualDir root;
- std::vector<DirectoryName> entries;
- u64 next_read_index = 0;
-};
-
-void Module::Interface::CreateDeliveryCacheStorageService(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BCAT, "called");
-
- const auto title_id = system.GetApplicationProcessProgramID();
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
-}
-
-void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto title_id = rp.PopRaw<u64>();
-
- LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
-}
-
-std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
- DirectoryGetter getter) {
- return std::make_unique<NullBackend>(std::move(getter));
-}
-
-Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
- FileSystem::FileSystemController& fsc_, const char* name)
- : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)},
- backend{CreateBackendFromSettings(system_,
- [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {}
-
-Module::Interface::~Interface() = default;
-
-void LoopProcess(Core::System& system) {
- auto server_manager = std::make_unique<ServerManager>(system);
- auto module = std::make_shared<Module>();
-
- server_manager->RegisterNamedService(
- "bcat:a",
- std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:a"));
- server_manager->RegisterNamedService(
- "bcat:m",
- std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m"));
- server_manager->RegisterNamedService(
- "bcat:u",
- std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u"));
- server_manager->RegisterNamedService(
- "bcat:s",
- std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s"));
- ServerManager::RunServer(std::move(server_manager));
-}
-
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include "core/hle/service/service.h"
-
-namespace Core {
-class System;
-}
-
-namespace Service {
-
-namespace FileSystem {
-class FileSystemController;
-} // namespace FileSystem
-
-namespace BCAT {
-
-class Backend;
-
-class Module final {
-public:
- class Interface : public ServiceFramework<Interface> {
- public:
- explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
- FileSystem::FileSystemController& fsc_, const char* name);
- ~Interface() override;
-
- void CreateBcatService(HLERequestContext& ctx);
- void CreateDeliveryCacheStorageService(HLERequestContext& ctx);
- void CreateDeliveryCacheStorageServiceWithApplicationId(HLERequestContext& ctx);
-
- protected:
- FileSystem::FileSystemController& fsc;
-
- std::shared_ptr<Module> module;
- std::unique_ptr<Backend> backend;
- };
-};
-
-void LoopProcess(Core::System& system);
-
-} // namespace BCAT
-
-} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::BCAT {
+
+constexpr Result ResultInvalidArgument{ErrorModule::BCAT, 1};
+constexpr Result ResultFailedOpenEntity{ErrorModule::BCAT, 2};
+constexpr Result ResultEntityAlreadyOpen{ErrorModule::BCAT, 6};
+constexpr Result ResultNoOpenEntry{ErrorModule::BCAT, 7};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/hex_util.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/file_sys/errors.h"
+#include "core/hle/service/bcat/backend/backend.h"
+#include "core/hle/service/bcat/bcat_result.h"
+#include "core/hle/service/bcat/bcat_service.h"
+#include "core/hle/service/bcat/bcat_util.h"
+#include "core/hle/service/bcat/delivery_cache_progress_service.h"
+#include "core/hle/service/bcat/delivery_cache_storage_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::BCAT {
+
+static u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
+ u64 out{};
+ std::memcpy(&out, id.data(), sizeof(u64));
+ return out;
+}
+
+IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_)
+ : ServiceFramework{system_, "IBcatService"}, backend{backend_},
+ progress{{
+ ProgressServiceBackend{system_, "Normal"},
+ ProgressServiceBackend{system_, "Directory"},
+ }} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {10100, D<&IBcatService::RequestSyncDeliveryCache>, "RequestSyncDeliveryCache"},
+ {10101, D<&IBcatService::RequestSyncDeliveryCacheWithDirectoryName>, "RequestSyncDeliveryCacheWithDirectoryName"},
+ {10200, nullptr, "CancelSyncDeliveryCacheRequest"},
+ {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
+ {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
+ {20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"},
+ {20301, nullptr, "RequestSuspendDeliveryTask"},
+ {20400, nullptr, "RegisterSystemApplicationDeliveryTask"},
+ {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
+ {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
+ {30100, D<&IBcatService::SetPassphrase>, "SetPassphrase"},
+ {30101, nullptr, "Unknown30101"},
+ {30102, nullptr, "Unknown30102"},
+ {30200, nullptr, "RegisterBackgroundDeliveryTask"},
+ {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
+ {30202, nullptr, "BlockDeliveryTask"},
+ {30203, nullptr, "UnblockDeliveryTask"},
+ {30210, nullptr, "SetDeliveryTaskTimer"},
+ {30300, D<&IBcatService::RegisterSystemApplicationDeliveryTasks>, "RegisterSystemApplicationDeliveryTasks"},
+ {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
+ {90101, nullptr, "Unknown90101"},
+ {90200, nullptr, "GetDeliveryList"},
+ {90201, D<&IBcatService::ClearDeliveryCacheStorage>, "ClearDeliveryCacheStorage"},
+ {90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"},
+ {90300, nullptr, "GetPushNotificationLog"},
+ {90301, nullptr, "Unknown90301"},
+ };
+ // clang-format on
+ RegisterHandlers(functions);
+}
+
+IBcatService::~IBcatService() = default;
+
+Result IBcatService::RequestSyncDeliveryCache(
+ OutInterface<IDeliveryCacheProgressService> out_interface) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ auto& progress_backend{GetProgressBackend(SyncType::Normal)};
+ backend.Synchronize({system.GetApplicationProcessProgramID(),
+ GetCurrentBuildID(system.GetApplicationProcessBuildID())},
+ GetProgressBackend(SyncType::Normal));
+
+ *out_interface = std::make_shared<IDeliveryCacheProgressService>(
+ system, progress_backend.GetEvent(), progress_backend.GetImpl());
+ R_SUCCEED();
+}
+
+Result IBcatService::RequestSyncDeliveryCacheWithDirectoryName(
+ const DirectoryName& name_raw, OutInterface<IDeliveryCacheProgressService> out_interface) {
+ const auto name = Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, name={}", name);
+
+ auto& progress_backend{GetProgressBackend(SyncType::Directory)};
+ backend.SynchronizeDirectory({system.GetApplicationProcessProgramID(),
+ GetCurrentBuildID(system.GetApplicationProcessBuildID())},
+ name, progress_backend);
+
+ *out_interface = std::make_shared<IDeliveryCacheProgressService>(
+ system, progress_backend.GetEvent(), progress_backend.GetImpl());
+ R_SUCCEED();
+}
+
+Result IBcatService::SetPassphrase(u64 application_id,
+ InBuffer<BufferAttr_HipcPointer> passphrase_buffer) {
+ LOG_DEBUG(Service_BCAT, "called, application_id={:016X}, passphrase={}", application_id,
+ Common::HexToString(passphrase_buffer));
+
+ R_UNLESS(application_id != 0, ResultInvalidArgument);
+ R_UNLESS(passphrase_buffer.size() <= 0x40, ResultInvalidArgument);
+
+ Passphrase passphrase{};
+ std::memcpy(passphrase.data(), passphrase_buffer.data(),
+ std::min(passphrase.size(), passphrase_buffer.size()));
+
+ backend.SetPassphrase(application_id, passphrase);
+ R_SUCCEED();
+}
+
+Result IBcatService::RegisterSystemApplicationDeliveryTasks() {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IBcatService::ClearDeliveryCacheStorage(u64 application_id) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", application_id);
+
+ R_UNLESS(application_id != 0, ResultInvalidArgument);
+ R_UNLESS(backend.Clear(application_id), FileSys::ResultPermissionDenied);
+ R_SUCCEED();
+}
+
+ProgressServiceBackend& IBcatService::GetProgressBackend(SyncType type) {
+ return progress.at(static_cast<size_t>(type));
+}
+
+const ProgressServiceBackend& IBcatService::GetProgressBackend(SyncType type) const {
+ return progress.at(static_cast<size_t>(type));
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/bcat/backend/backend.h"
+#include "core/hle/service/bcat/bcat_types.h"
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::BCAT {
+class BcatBackend;
+class IDeliveryCacheStorageService;
+class IDeliveryCacheProgressService;
+
+class IBcatService final : public ServiceFramework<IBcatService> {
+public:
+ explicit IBcatService(Core::System& system_, BcatBackend& backend_);
+ ~IBcatService() override;
+
+private:
+ Result RequestSyncDeliveryCache(OutInterface<IDeliveryCacheProgressService> out_interface);
+
+ Result RequestSyncDeliveryCacheWithDirectoryName(
+ const DirectoryName& name, OutInterface<IDeliveryCacheProgressService> out_interface);
+
+ Result SetPassphrase(u64 application_id, InBuffer<BufferAttr_HipcPointer> passphrase_buffer);
+
+ Result RegisterSystemApplicationDeliveryTasks();
+
+ Result ClearDeliveryCacheStorage(u64 application_id);
+
+private:
+ ProgressServiceBackend& GetProgressBackend(SyncType type);
+ const ProgressServiceBackend& GetProgressBackend(SyncType type) const;
+
+ BcatBackend& backend;
+ std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <functional>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
+#include "core/hle/result.h"
+
+namespace Service::BCAT {
+
+using DirectoryName = std::array<char, 0x20>;
+using FileName = std::array<char, 0x20>;
+using BcatDigest = std::array<u8, 0x10>;
+using Passphrase = std::array<u8, 0x20>;
+using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
+
+enum class SyncType {
+ Normal,
+ Directory,
+ Count,
+};
+
+enum class DeliveryCacheProgressStatus : s32 {
+ None = 0x0,
+ Queued = 0x1,
+ Connecting = 0x2,
+ ProcessingDataList = 0x3,
+ Downloading = 0x4,
+ Committing = 0x5,
+ Done = 0x9,
+};
+
+struct DeliveryCacheDirectoryEntry {
+ FileName name;
+ u64 size;
+ BcatDigest digest;
+};
+
+struct TitleIDVersion {
+ u64 title_id;
+ u64 build_id;
+};
+
+struct DeliveryCacheProgressImpl {
+ DeliveryCacheProgressStatus status;
+ Result result;
+ DirectoryName current_directory;
+ FileName current_file;
+ s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
+ s64 current_total_bytes; ///< Bytes total on current file.
+ s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
+ s64 total_bytes; ///< Bytes total on overall download.
+ INSERT_PADDING_BYTES_NOINIT(
+ 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
+};
+static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
+ "DeliveryCacheProgressImpl has incorrect size.");
+static_assert(std::is_trivial_v<DeliveryCacheProgressImpl>,
+ "DeliveryCacheProgressImpl type must be trivially copyable.");
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cctype>
+#include <mbedtls/md5.h>
+
+#include "core/hle/service/bcat/bcat_result.h"
+#include "core/hle/service/bcat/bcat_types.h"
+
+namespace Service::BCAT {
+
+// For a name to be valid it must be non-empty, must have a null terminating character as the final
+// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
+// file.
+constexpr Result VerifyNameValidInternal(std::array<char, 0x20> name, char match_char) {
+ const auto null_chars = std::count(name.begin(), name.end(), 0);
+ const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
+ return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
+ });
+ if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
+ LOG_ERROR(Service_BCAT, "Name passed was invalid!");
+ return ResultInvalidArgument;
+ }
+
+ return ResultSuccess;
+}
+
+constexpr Result VerifyNameValidDir(DirectoryName name) {
+ return VerifyNameValidInternal(name, '-');
+}
+
+constexpr Result VerifyNameValidFile(FileName name) {
+ return VerifyNameValidInternal(name, '.');
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/string_util.h"
+#include "core/file_sys/vfs/vfs_types.h"
+#include "core/hle/service/bcat/bcat_result.h"
+#include "core/hle/service/bcat/bcat_util.h"
+#include "core/hle/service/bcat/delivery_cache_directory_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::BCAT {
+
+// The digest is only used to determine if a file is unique compared to others of the same name.
+// Since the algorithm isn't ever checked in game, MD5 is safe.
+static BcatDigest DigestFile(const FileSys::VirtualFile& file) {
+ BcatDigest out{};
+ const auto bytes = file->ReadAllBytes();
+ mbedtls_md5_ret(bytes.data(), bytes.size(), out.data());
+ return out;
+}
+
+IDeliveryCacheDirectoryService::IDeliveryCacheDirectoryService(Core::System& system_,
+ FileSys::VirtualDir root_)
+ : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IDeliveryCacheDirectoryService::Open>, "Open"},
+ {1, D<&IDeliveryCacheDirectoryService::Read>, "Read"},
+ {2, D<&IDeliveryCacheDirectoryService::GetCount>, "GetCount"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDeliveryCacheDirectoryService::~IDeliveryCacheDirectoryService() = default;
+
+Result IDeliveryCacheDirectoryService::Open(const DirectoryName& dir_name_raw) {
+ const auto dir_name =
+ Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, dir_name={}", dir_name);
+
+ R_TRY(VerifyNameValidDir(dir_name_raw));
+ R_UNLESS(current_dir == nullptr, ResultEntityAlreadyOpen);
+
+ const auto dir = root->GetSubdirectory(dir_name);
+ R_UNLESS(dir != nullptr, ResultFailedOpenEntity);
+
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheDirectoryService::Read(
+ Out<s32> out_count, OutArray<DeliveryCacheDirectoryEntry, BufferAttr_HipcMapAlias> out_buffer) {
+ LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", out_buffer.size());
+
+ R_UNLESS(current_dir != nullptr, ResultNoOpenEntry);
+
+ const auto files = current_dir->GetFiles();
+ *out_count = static_cast<s32>(std::min(files.size(), out_buffer.size()));
+ std::transform(files.begin(), files.begin() + *out_count, out_buffer.begin(),
+ [](const auto& file) {
+ FileName name{};
+ std::memcpy(name.data(), file->GetName().data(),
+ std::min(file->GetName().size(), name.size()));
+ return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
+ });
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheDirectoryService::GetCount(Out<s32> out_count) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ R_UNLESS(current_dir != nullptr, ResultNoOpenEntry);
+
+ *out_count = static_cast<s32>(current_dir->GetFiles().size());
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/bcat/bcat_types.h"
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::BCAT {
+
+class IDeliveryCacheDirectoryService final
+ : public ServiceFramework<IDeliveryCacheDirectoryService> {
+public:
+ explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_);
+ ~IDeliveryCacheDirectoryService() override;
+
+private:
+ Result Open(const DirectoryName& dir_name_raw);
+ Result Read(Out<s32> out_count,
+ OutArray<DeliveryCacheDirectoryEntry, BufferAttr_HipcMapAlias> out_buffer);
+ Result GetCount(Out<s32> out_count);
+
+ FileSys::VirtualDir root;
+ FileSys::VirtualDir current_dir;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/string_util.h"
+#include "core/hle/service/bcat/bcat_result.h"
+#include "core/hle/service/bcat/bcat_util.h"
+#include "core/hle/service/bcat/delivery_cache_file_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::BCAT {
+
+IDeliveryCacheFileService::IDeliveryCacheFileService(Core::System& system_,
+ FileSys::VirtualDir root_)
+ : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IDeliveryCacheFileService::Open>, "Open"},
+ {1, D<&IDeliveryCacheFileService::Read>, "Read"},
+ {2, D<&IDeliveryCacheFileService::GetSize>, "GetSize"},
+ {3, D<&IDeliveryCacheFileService::GetDigest>, "GetDigest"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDeliveryCacheFileService::~IDeliveryCacheFileService() = default;
+
+Result IDeliveryCacheFileService::Open(const DirectoryName& dir_name_raw,
+ const FileName& file_name_raw) {
+ const auto dir_name =
+ Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
+ const auto file_name =
+ Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
+
+ R_TRY(VerifyNameValidDir(dir_name_raw));
+ R_TRY(VerifyNameValidDir(file_name_raw));
+ R_UNLESS(current_file == nullptr, ResultEntityAlreadyOpen);
+
+ const auto dir = root->GetSubdirectory(dir_name);
+ R_UNLESS(dir != nullptr, ResultFailedOpenEntity);
+
+ current_file = dir->GetFile(file_name);
+ R_UNLESS(current_file != nullptr, ResultFailedOpenEntity);
+
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheFileService::Read(Out<u64> out_buffer_size, u64 offset,
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer) {
+ LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, out_buffer.size());
+
+ R_UNLESS(current_file != nullptr, ResultNoOpenEntry);
+
+ *out_buffer_size = std::min<u64>(current_file->GetSize() - offset, out_buffer.size());
+ const auto buffer = current_file->ReadBytes(*out_buffer_size, offset);
+ memcpy(out_buffer.data(), buffer.data(), buffer.size());
+
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheFileService::GetSize(Out<u64> out_size) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ R_UNLESS(current_file != nullptr, ResultNoOpenEntry);
+
+ *out_size = current_file->GetSize();
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheFileService::GetDigest(Out<BcatDigest> out_digest) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ R_UNLESS(current_file != nullptr, ResultNoOpenEntry);
+
+ //*out_digest = DigestFile(current_file);
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/bcat/bcat_types.h"
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::BCAT {
+
+class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
+public:
+ explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_);
+ ~IDeliveryCacheFileService() override;
+
+private:
+ Result Open(const DirectoryName& dir_name_raw, const FileName& file_name_raw);
+ Result Read(Out<u64> out_buffer_size, u64 offset,
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer);
+ Result GetSize(Out<u64> out_size);
+ Result GetDigest(Out<BcatDigest> out_digest);
+
+ FileSys::VirtualDir root;
+ FileSys::VirtualFile current_file;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/bcat_types.h"
+#include "core/hle/service/bcat/delivery_cache_progress_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::BCAT {
+
+IDeliveryCacheProgressService::IDeliveryCacheProgressService(Core::System& system_,
+ Kernel::KReadableEvent& event_,
+ const DeliveryCacheProgressImpl& impl_)
+ : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{event_}, impl{impl_} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IDeliveryCacheProgressService::GetEvent>, "Get"},
+ {1, D<&IDeliveryCacheProgressService::GetImpl>, "Get"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDeliveryCacheProgressService::~IDeliveryCacheProgressService() = default;
+
+Result IDeliveryCacheProgressService::GetEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ *out_event = &event;
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheProgressService::GetImpl(
+ OutLargeData<DeliveryCacheProgressImpl, BufferAttr_HipcPointer> out_impl) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ *out_impl = impl;
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::BCAT {
+struct DeliveryCacheProgressImpl;
+
+class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
+public:
+ explicit IDeliveryCacheProgressService(Core::System& system_, Kernel::KReadableEvent& event_,
+ const DeliveryCacheProgressImpl& impl_);
+ ~IDeliveryCacheProgressService() override;
+
+private:
+ Result GetEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+ Result GetImpl(OutLargeData<DeliveryCacheProgressImpl, BufferAttr_HipcPointer> out_impl);
+
+ Kernel::KReadableEvent& event;
+ const DeliveryCacheProgressImpl& impl;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/bcat_result.h"
+#include "core/hle/service/bcat/delivery_cache_directory_service.h"
+#include "core/hle/service/bcat/delivery_cache_file_service.h"
+#include "core/hle/service/bcat/delivery_cache_storage_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::BCAT {
+
+IDeliveryCacheStorageService::IDeliveryCacheStorageService(Core::System& system_,
+ FileSys::VirtualDir root_)
+ : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IDeliveryCacheStorageService::CreateFileService>, "CreateFileService"},
+ {1, D<&IDeliveryCacheStorageService::CreateDirectoryService>, "CreateDirectoryService"},
+ {10, D<&IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory>, "EnumerateDeliveryCacheDirectory"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDeliveryCacheStorageService::~IDeliveryCacheStorageService() = default;
+
+Result IDeliveryCacheStorageService::CreateFileService(
+ OutInterface<IDeliveryCacheFileService> out_interface) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ *out_interface = std::make_shared<IDeliveryCacheFileService>(system, root);
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheStorageService::CreateDirectoryService(
+ OutInterface<IDeliveryCacheDirectoryService> out_interface) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ *out_interface = std::make_shared<IDeliveryCacheDirectoryService>(system, root);
+ R_SUCCEED();
+}
+
+Result IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory(
+ Out<s32> out_directory_count,
+ OutArray<DirectoryName, BufferAttr_HipcMapAlias> out_directories) {
+ LOG_DEBUG(Service_BCAT, "called, size={:016X}", out_directories.size());
+
+ *out_directory_count =
+ static_cast<s32>(std::min(out_directories.size(), entries.size() - next_read_index));
+ memcpy(out_directories.data(), entries.data() + next_read_index,
+ *out_directory_count * sizeof(DirectoryName));
+ next_read_index += *out_directory_count;
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/bcat/bcat_types.h"
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::BCAT {
+class IDeliveryCacheFileService;
+class IDeliveryCacheDirectoryService;
+
+class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
+public:
+ explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_);
+ ~IDeliveryCacheStorageService() override;
+
+private:
+ Result CreateFileService(OutInterface<IDeliveryCacheFileService> out_interface);
+ Result CreateDirectoryService(OutInterface<IDeliveryCacheDirectoryService> out_interface);
+ Result EnumerateDeliveryCacheDirectory(
+ Out<s32> out_directory_count,
+ OutArray<DirectoryName, BufferAttr_HipcMapAlias> out_directories);
+
+ FileSys::VirtualDir root;
+ std::vector<DirectoryName> entries;
+ std::size_t next_read_index = 0;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/news/newly_arrived_event_holder.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::News {
+
+INewlyArrivedEventHolder::INewlyArrivedEventHolder(Core::System& system_)
+ : ServiceFramework{system_, "INewlyArrivedEventHolder"}, service_context{
+ system_,
+ "INewlyArrivedEventHolder"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&INewlyArrivedEventHolder::Get>, "Get"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ arrived_event = service_context.CreateEvent("INewlyArrivedEventHolder::ArrivedEvent");
+}
+
+INewlyArrivedEventHolder::~INewlyArrivedEventHolder() {
+ service_context.CloseEvent(arrived_event);
+}
+
+Result INewlyArrivedEventHolder::Get(OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_INFO(Service_BCAT, "called");
+
+ *out_event = &arrived_event->GetReadableEvent();
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::News {
+
+class INewlyArrivedEventHolder final : public ServiceFramework<INewlyArrivedEventHolder> {
+public:
+ explicit INewlyArrivedEventHolder(Core::System& system_);
+ ~INewlyArrivedEventHolder() override;
+
+private:
+ Result Get(OutCopyHandle<Kernel::KReadableEvent> out_event);
+
+ Kernel::KEvent* arrived_event;
+ KernelHelpers::ServiceContext service_context;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/news/news_data_service.h"
+
+namespace Service::News {
+
+INewsDataService::INewsDataService(Core::System& system_)
+ : ServiceFramework{system_, "INewsDataService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Open"},
+ {1, nullptr, "OpenWithNewsRecordV1"},
+ {2, nullptr, "Read"},
+ {3, nullptr, "GetSize"},
+ {1001, nullptr, "OpenWithNewsRecord"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+INewsDataService::~INewsDataService() = default;
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::News {
+
+class INewsDataService final : public ServiceFramework<INewsDataService> {
+public:
+ explicit INewsDataService(Core::System& system_);
+ ~INewsDataService() override;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/news/news_database_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::News {
+
+INewsDatabaseService::INewsDatabaseService(Core::System& system_)
+ : ServiceFramework{system_, "INewsDatabaseService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetListV1"},
+ {1, D<&INewsDatabaseService::Count>, "Count"},
+ {2, nullptr, "CountWithKey"},
+ {3, nullptr, "UpdateIntegerValue"},
+ {4, D<&INewsDatabaseService::UpdateIntegerValueWithAddition>, "UpdateIntegerValueWithAddition"},
+ {5, nullptr, "UpdateStringValue"},
+ {1000, D<&INewsDatabaseService::GetList>, "GetList"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+INewsDatabaseService::~INewsDatabaseService() = default;
+
+Result INewsDatabaseService::Count(Out<s32> out_count,
+ InBuffer<BufferAttr_HipcPointer> buffer_data) {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called, buffer_size={}", buffer_data.size());
+ *out_count = 0;
+ R_SUCCEED();
+}
+
+Result INewsDatabaseService::UpdateIntegerValueWithAddition(
+ u32 value, InBuffer<BufferAttr_HipcPointer> buffer_data_1,
+ InBuffer<BufferAttr_HipcPointer> buffer_data_2) {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called, value={}, buffer_size_1={}, buffer_data_2={}",
+ value, buffer_data_1.size(), buffer_data_2.size());
+ R_SUCCEED();
+}
+
+Result INewsDatabaseService::GetList(Out<s32> out_count, u32 value,
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer_data,
+ InBuffer<BufferAttr_HipcPointer> buffer_data_1,
+ InBuffer<BufferAttr_HipcPointer> buffer_data_2) {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called, value={}, buffer_size_1={}, buffer_data_2={}",
+ value, buffer_data_1.size(), buffer_data_2.size());
+ *out_count = 0;
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::News {
+
+class INewsDatabaseService final : public ServiceFramework<INewsDatabaseService> {
+public:
+ explicit INewsDatabaseService(Core::System& system_);
+ ~INewsDatabaseService() override;
+
+private:
+ Result Count(Out<s32> out_count, InBuffer<BufferAttr_HipcPointer> buffer_data);
+
+ Result UpdateIntegerValueWithAddition(u32 value, InBuffer<BufferAttr_HipcPointer> buffer_data_1,
+ InBuffer<BufferAttr_HipcPointer> buffer_data_2);
+
+ Result GetList(Out<s32> out_count, u32 value,
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer_data,
+ InBuffer<BufferAttr_HipcPointer> buffer_data_1,
+ InBuffer<BufferAttr_HipcPointer> buffer_data_2);
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/news/news_service.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::News {
+
+INewsService::INewsService(Core::System& system_) : ServiceFramework{system_, "INewsService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {10100, nullptr, "PostLocalNews"},
+ {20100, nullptr, "SetPassphrase"},
+ {30100, D<&INewsService::GetSubscriptionStatus>, "GetSubscriptionStatus"},
+ {30101, nullptr, "GetTopicList"},
+ {30110, nullptr, "Unknown30110"},
+ {30200, D<&INewsService::IsSystemUpdateRequired>, "IsSystemUpdateRequired"},
+ {30201, nullptr, "Unknown30201"},
+ {30210, nullptr, "Unknown30210"},
+ {30300, nullptr, "RequestImmediateReception"},
+ {30400, nullptr, "DecodeArchiveFile"},
+ {30500, nullptr, "Unknown30500"},
+ {30900, nullptr, "Unknown30900"},
+ {30901, nullptr, "Unknown30901"},
+ {30902, nullptr, "Unknown30902"},
+ {40100, nullptr, "SetSubscriptionStatus"},
+ {40101, D<&INewsService::RequestAutoSubscription>, "RequestAutoSubscription"},
+ {40200, nullptr, "ClearStorage"},
+ {40201, nullptr, "ClearSubscriptionStatusAll"},
+ {90100, nullptr, "GetNewsDatabaseDump"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+INewsService::~INewsService() = default;
+
+Result INewsService::GetSubscriptionStatus(Out<u32> out_status,
+ InBuffer<BufferAttr_HipcPointer> buffer_data) {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called, buffer_size={}", buffer_data.size());
+ *out_status = 0;
+ R_SUCCEED();
+}
+
+Result INewsService::IsSystemUpdateRequired(Out<bool> out_is_system_update_required) {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called");
+ *out_is_system_update_required = false;
+ R_SUCCEED();
+}
+
+Result INewsService::RequestAutoSubscription(u64 value) {
+ LOG_WARNING(Service_BCAT, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::News {
+
+class INewsService final : public ServiceFramework<INewsService> {
+public:
+ explicit INewsService(Core::System& system_);
+ ~INewsService() override;
+
+private:
+ Result GetSubscriptionStatus(Out<u32> out_status, InBuffer<BufferAttr_HipcPointer> buffer_data);
+
+ Result IsSystemUpdateRequired(Out<bool> out_is_system_update_required);
+
+ Result RequestAutoSubscription(u64 value);
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/news/overwrite_event_holder.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::News {
+
+IOverwriteEventHolder::IOverwriteEventHolder(Core::System& system_)
+ : ServiceFramework{system_, "IOverwriteEventHolder"}, service_context{system_,
+ "IOverwriteEventHolder"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IOverwriteEventHolder::Get>, "Get"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ overwrite_event = service_context.CreateEvent("IOverwriteEventHolder::OverwriteEvent");
+}
+
+IOverwriteEventHolder::~IOverwriteEventHolder() {
+ service_context.CloseEvent(overwrite_event);
+}
+
+Result IOverwriteEventHolder::Get(OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_INFO(Service_BCAT, "called");
+
+ *out_event = &overwrite_event->GetReadableEvent();
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::News {
+
+class IOverwriteEventHolder final : public ServiceFramework<IOverwriteEventHolder> {
+public:
+ explicit IOverwriteEventHolder(Core::System& system_);
+ ~IOverwriteEventHolder() override;
+
+private:
+ Result Get(OutCopyHandle<Kernel::KReadableEvent> out_event);
+
+ Kernel::KEvent* overwrite_event;
+ KernelHelpers::ServiceContext service_context;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/bcat/news/newly_arrived_event_holder.h"
+#include "core/hle/service/bcat/news/news_data_service.h"
+#include "core/hle/service/bcat/news/news_database_service.h"
+#include "core/hle/service/bcat/news/news_service.h"
+#include "core/hle/service/bcat/news/overwrite_event_holder.h"
+#include "core/hle/service/bcat/news/service_creator.h"
+#include "core/hle/service/cmif_serialization.h"
+
+namespace Service::News {
+
+IServiceCreator::IServiceCreator(Core::System& system_, u32 permissions_, const char* name_)
+ : ServiceFramework{system_, name_}, permissions{permissions_} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IServiceCreator::CreateNewsService>, "CreateNewsService"},
+ {1, D<&IServiceCreator::CreateNewlyArrivedEventHolder>, "CreateNewlyArrivedEventHolder"},
+ {2, D<&IServiceCreator::CreateNewsDataService>, "CreateNewsDataService"},
+ {3, D<&IServiceCreator::CreateNewsDatabaseService>, "CreateNewsDatabaseService"},
+ {4, D<&IServiceCreator::CreateOverwriteEventHolder>, "CreateOverwriteEventHolder"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IServiceCreator::~IServiceCreator() = default;
+
+Result IServiceCreator::CreateNewsService(OutInterface<INewsService> out_interface) {
+ LOG_INFO(Service_BCAT, "called");
+ *out_interface = std::make_shared<INewsService>(system);
+ R_SUCCEED();
+}
+
+Result IServiceCreator::CreateNewlyArrivedEventHolder(
+ OutInterface<INewlyArrivedEventHolder> out_interface) {
+ LOG_INFO(Service_BCAT, "called");
+ *out_interface = std::make_shared<INewlyArrivedEventHolder>(system);
+ R_SUCCEED();
+}
+
+Result IServiceCreator::CreateNewsDataService(OutInterface<INewsDataService> out_interface) {
+ LOG_INFO(Service_BCAT, "called");
+ *out_interface = std::make_shared<INewsDataService>(system);
+ R_SUCCEED();
+}
+
+Result IServiceCreator::CreateNewsDatabaseService(
+ OutInterface<INewsDatabaseService> out_interface) {
+ LOG_INFO(Service_BCAT, "called");
+ *out_interface = std::make_shared<INewsDatabaseService>(system);
+ R_SUCCEED();
+}
+
+Result IServiceCreator::CreateOverwriteEventHolder(
+ OutInterface<IOverwriteEventHolder> out_interface) {
+ LOG_INFO(Service_BCAT, "called");
+ *out_interface = std::make_shared<IOverwriteEventHolder>(system);
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::News {
+class INewsService;
+class INewlyArrivedEventHolder;
+class INewsDataService;
+class INewsDatabaseService;
+class IOverwriteEventHolder;
+
+class IServiceCreator final : public ServiceFramework<IServiceCreator> {
+public:
+ explicit IServiceCreator(Core::System& system_, u32 permissions_, const char* name_);
+ ~IServiceCreator() override;
+
+private:
+ Result CreateNewsService(OutInterface<INewsService> out_interface);
+ Result CreateNewlyArrivedEventHolder(OutInterface<INewlyArrivedEventHolder> out_interface);
+ Result CreateNewsDataService(OutInterface<INewsDataService> out_interface);
+ Result CreateNewsDatabaseService(OutInterface<INewsDatabaseService> out_interface);
+ Result CreateOverwriteEventHolder(OutInterface<IOverwriteEventHolder> out_interface);
+
+ u32 permissions;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/bcat/bcat_service.h"
+#include "core/hle/service/bcat/delivery_cache_storage_service.h"
+#include "core/hle/service/bcat/service_creator.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/filesystem/filesystem.h"
+
+namespace Service::BCAT {
+
+std::unique_ptr<BcatBackend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
+ DirectoryGetter getter) {
+ return std::make_unique<NullBcatBackend>(std::move(getter));
+}
+
+IServiceCreator::IServiceCreator(Core::System& system_, const char* name_)
+ : ServiceFramework{system_, name_}, fsc{system.GetFileSystemController()} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IServiceCreator::CreateBcatService>, "CreateBcatService"},
+ {1, D<&IServiceCreator::CreateDeliveryCacheStorageService>, "CreateDeliveryCacheStorageService"},
+ {2, D<&IServiceCreator::CreateDeliveryCacheStorageServiceWithApplicationId>, "CreateDeliveryCacheStorageServiceWithApplicationId"},
+ {3, nullptr, "CreateDeliveryCacheProgressService"},
+ {4, nullptr, "CreateDeliveryCacheProgressServiceWithApplicationId"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ backend =
+ CreateBackendFromSettings(system_, [this](u64 tid) { return fsc.GetBCATDirectory(tid); });
+}
+
+IServiceCreator::~IServiceCreator() = default;
+
+Result IServiceCreator::CreateBcatService(ClientProcessId process_id,
+ OutInterface<IBcatService> out_interface) {
+ LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
+ *out_interface = std::make_shared<IBcatService>(system, *backend);
+ R_SUCCEED();
+}
+
+Result IServiceCreator::CreateDeliveryCacheStorageService(
+ ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface) {
+ LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
+
+ const auto title_id = system.GetApplicationProcessProgramID();
+ *out_interface =
+ std::make_shared<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
+ R_SUCCEED();
+}
+
+Result IServiceCreator::CreateDeliveryCacheStorageServiceWithApplicationId(
+ u64 application_id, OutInterface<IDeliveryCacheStorageService> out_interface) {
+ LOG_DEBUG(Service_BCAT, "called, application_id={:016X}", application_id);
+ *out_interface = std::make_shared<IDeliveryCacheStorageService>(
+ system, fsc.GetBCATDirectory(application_id));
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
+namespace Service::BCAT {
+class BcatBackend;
+class IBcatService;
+class IDeliveryCacheStorageService;
+
+class IServiceCreator final : public ServiceFramework<IServiceCreator> {
+public:
+ explicit IServiceCreator(Core::System& system_, const char* name_);
+ ~IServiceCreator() override;
+
+private:
+ Result CreateBcatService(ClientProcessId process_id, OutInterface<IBcatService> out_interface);
+
+ Result CreateDeliveryCacheStorageService(
+ ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface);
+
+ Result CreateDeliveryCacheStorageServiceWithApplicationId(
+ u64 application_id, OutInterface<IDeliveryCacheStorageService> out_interface);
+
+ std::unique_ptr<BcatBackend> backend;
+ Service::FileSystem::FileSystemController& fsc;
+};
+
+} // 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 @@
#include "core/hle/service/glue/time/file_timestamp_worker.h"
#include "core/hle/service/glue/time/standard_steady_clock_resource.h"
#include "core/hle/service/glue/time/worker.h"
+#include "core/hle/service/os/multi_wait_utils.h"
#include "core/hle/service/psc/time/common.h"
#include "core/hle/service/psc/time/service_manager.h"
#include "core/hle/service/psc/time/static.h"
@@ -143,82 +144,46 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
Common::SetCurrentThreadName("TimeWorker");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
- enum class EventType {
- Exit = 0,
- IpmModuleService_GetEvent = 1,
- PowerStateChange = 2,
- SignalAlarms = 3,
- UpdateLocalSystemClock = 4,
- UpdateNetworkSystemClock = 5,
- UpdateEphemeralSystemClock = 6,
- UpdateSteadyClock = 7,
- UpdateFileTimestamp = 8,
- AutoCorrect = 9,
- Max = 10,
- };
-
- s32 num_objs{};
- std::array<Kernel::KSynchronizationObject*, static_cast<u32>(EventType::Max)> wait_objs{};
- std::array<EventType, static_cast<u32>(EventType::Max)> wait_indices{};
-
- const auto AddWaiter{
- [&](Kernel::KSynchronizationObject* synchronization_object, EventType type) {
- // Open a new reference to the object.
- synchronization_object->Open();
-
- // Insert into the list.
- wait_indices[num_objs] = type;
- wait_objs[num_objs++] = synchronization_object;
- }};
-
while (!stop_token.stop_requested()) {
- SCOPE_EXIT({
- for (s32 i = 0; i < num_objs; i++) {
- wait_objs[i]->Close();
- }
- });
+ enum class EventType : s32 {
+ Exit = 0,
+ PowerStateChange = 1,
+ SignalAlarms = 2,
+ UpdateLocalSystemClock = 3,
+ UpdateNetworkSystemClock = 4,
+ UpdateEphemeralSystemClock = 5,
+ UpdateSteadyClock = 6,
+ UpdateFileTimestamp = 7,
+ AutoCorrect = 8,
+ };
+
+ s32 index{};
- num_objs = {};
- wait_objs = {};
if (m_pm_state_change_handler.m_priority != 0) {
- AddWaiter(&m_event->GetReadableEvent(), EventType::Exit);
- // TODO
- // AddWaiter(gIPmModuleService::GetEvent(), 1);
- AddWaiter(&m_alarm_worker.GetEvent(), EventType::PowerStateChange);
+ // TODO: gIPmModuleService::GetEvent() 1
+ index = WaitAny(m_system.Kernel(),
+ &m_event->GetReadableEvent(), // 0
+ &m_alarm_worker.GetEvent() // 1
+ );
} else {
- AddWaiter(&m_event->GetReadableEvent(), EventType::Exit);
- // TODO
- // AddWaiter(gIPmModuleService::GetEvent(), 1);
- AddWaiter(&m_alarm_worker.GetEvent(), EventType::PowerStateChange);
- AddWaiter(&m_alarm_worker.GetTimerEvent().GetReadableEvent(), EventType::SignalAlarms);
- AddWaiter(m_local_clock_event, EventType::UpdateLocalSystemClock);
- AddWaiter(m_network_clock_event, EventType::UpdateNetworkSystemClock);
- AddWaiter(m_ephemeral_clock_event, EventType::UpdateEphemeralSystemClock);
- AddWaiter(&m_timer_steady_clock->GetReadableEvent(), EventType::UpdateSteadyClock);
- AddWaiter(&m_timer_file_system->GetReadableEvent(), EventType::UpdateFileTimestamp);
- AddWaiter(m_standard_user_auto_correct_clock_event, EventType::AutoCorrect);
+ // TODO: gIPmModuleService::GetEvent() 1
+ index = WaitAny(m_system.Kernel(),
+ &m_event->GetReadableEvent(), // 0
+ &m_alarm_worker.GetEvent(), // 1
+ &m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
+ m_local_clock_event, // 3
+ m_network_clock_event, // 4
+ m_ephemeral_clock_event, // 5
+ &m_timer_steady_clock->GetReadableEvent(), // 6
+ &m_timer_file_system->GetReadableEvent(), // 7
+ m_standard_user_auto_correct_clock_event // 8
+ );
}
- s32 out_index{-1};
- Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index, wait_objs.data(),
- num_objs, -1);
- ASSERT(out_index >= 0 && out_index < num_objs);
-
- if (stop_token.stop_requested()) {
- return;
- }
-
- switch (wait_indices[out_index]) {
+ switch (static_cast<EventType>(index)) {
case EventType::Exit:
return;
- case EventType::IpmModuleService_GetEvent:
- // TODO
- // IPmModuleService::GetEvent()
- // clear the event
- // Handle power state change event
- break;
-
case EventType::PowerStateChange:
m_alarm_worker.GetEvent().Clear();
if (m_pm_state_change_handler.m_priority <= 1) {
@@ -235,19 +200,19 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
m_local_clock_event->Clear();
Service::PSC::Time::SystemClockContext context{};
- auto res = m_local_clock->GetSystemClockContext(&context);
- ASSERT(res == ResultSuccess);
+ R_ASSERT(m_local_clock->GetSystemClockContext(&context));
m_set_sys->SetUserSystemClockContext(context);
-
m_file_timestamp_worker.SetFilesystemPosixTime();
- } break;
+ break;
+ }
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear();
+
Service::PSC::Time::SystemClockContext context{};
- auto res = m_network_clock->GetSystemClockContext(&context);
- ASSERT(res == ResultSuccess);
+ R_ASSERT(m_network_clock->GetSystemClockContext(&context));
+
m_set_sys->SetNetworkSystemClockContext(context);
s64 time{};
@@ -267,7 +232,8 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
}
m_file_timestamp_worker.SetFilesystemPosixTime();
- } break;
+ break;
+ }
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear();
@@ -295,7 +261,8 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
if (!g_ig_report_ephemeral_clock_context_set) {
g_ig_report_ephemeral_clock_context_set = true;
}
- } break;
+ break;
+ }
case EventType::UpdateSteadyClock:
m_timer_steady_clock->Clear();
@@ -314,21 +281,20 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) {
m_standard_user_auto_correct_clock_event->Clear();
bool automatic_correction{};
- auto res = m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
- &automatic_correction);
- ASSERT(res == ResultSuccess);
+ R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
+ &automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
- res = m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point);
- ASSERT(res == ResultSuccess);
+ R_ASSERT(
+ m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
- } break;
+ break;
+ }
default:
UNREACHABLE();
- break;
}
}
}
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) {
continue;
}
if (session.process == process) {
+ session.ref_count++;
return session.id;
}
}
@@ -66,6 +67,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
}
auto& session = impl->sessions[new_id];
session.is_active = true;
+ session.ref_count = 1;
// Optimization
if (process->IsApplication()) {
auto& page_table = process->GetPageTable().GetBasePageTable();
@@ -114,8 +116,11 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
void Container::CloseSession(SessionId session_id) {
std::scoped_lock lk(impl->session_guard);
- impl->file.UnmapAllHandles(session_id);
auto& session = impl->sessions[session_id.id];
+ if (--session.ref_count > 0) {
+ return;
+ }
+ impl->file.UnmapAllHandles(session_id);
auto& smmu = impl->host1x.MemoryManager();
if (session.has_preallocated_area) {
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 {
bool has_preallocated_area{};
std::unique_ptr<HeapMapper> mapper{};
bool is_active{};
+ s32 ref_count{};
};
class 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) {
}();
for (auto& [id, handle] : handles_copy) {
- if (handle->session_id.id == session_id.id) {
- FreeHandle(id, false);
+ {
+ std::scoped_lock lk{handle->mutex};
+ if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
+ continue;
+ }
}
+ FreeHandle(id, false);
}
}
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 @@
namespace Service::Nvidia::Devices {
+namespace {
+
+Tegra::BlendMode ConvertBlending(Service::Nvnflinger::LayerBlending blending) {
+ switch (blending) {
+ case Service::Nvnflinger::LayerBlending::None:
+ default:
+ return Tegra::BlendMode::Opaque;
+ case Service::Nvnflinger::LayerBlending::Premultiplied:
+ return Tegra::BlendMode::Premultiplied;
+ case Service::Nvnflinger::LayerBlending::Coverage:
+ return Tegra::BlendMode::Coverage;
+ }
+}
+
+} // namespace
+
nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
: nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
nvdisp_disp0::~nvdisp_disp0() = default;
@@ -56,6 +72,7 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
.pixel_format = layer.format,
.transform_flags = layer.transform,
.crop_rect = layer.crop_rect,
+ .blending = ConvertBlending(layer.blending),
});
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 @@
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
#include "core/hle/service/vi/layer/vi_layer.h"
#include "core/hle/service/vi/vi_results.h"
+#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
namespace Service::Nvnflinger {
namespace {
-Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
- std::unique_ptr<Kernel::KPageGroup>* out_page_group,
- Core::System& system, u32 size) {
+Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_group,
+ Core::System& system, u32 size) {
using Core::Memory::YUZU_PAGESIZE;
// Allocate memory for the system shared buffer.
- // FIXME: Because the gmmu can only point to cpu addresses, we need
- // to map this in the application space to allow it to be used.
- // FIXME: Add proper smmu emulation.
// FIXME: This memory belongs to vi's .data section.
auto& kernel = system.Kernel();
- auto* process = system.ApplicationProcess();
- auto& page_table = process->GetPageTable();
// Hold a temporary page group reference while we try to map it.
auto pg = std::make_unique<Kernel::KPageGroup>(
@@ -43,6 +39,30 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
Kernel::KMemoryManager::Direction::FromBack)));
+ // Fill the output data with red.
+ for (auto& block : *pg) {
+ u32* start = system.DeviceMemory().GetPointer<u32>(block.GetAddress());
+ u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
+
+ for (; start < end; start++) {
+ *start = 0xFF0000FF;
+ }
+ }
+
+ // Return the mapped page group.
+ *out_page_group = std::move(pg);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result MapSharedBufferIntoProcessAddressSpace(Common::ProcessAddress* out_map_address,
+ std::unique_ptr<Kernel::KPageGroup>& pg,
+ Kernel::KProcess* process, Core::System& system) {
+ using Core::Memory::YUZU_PAGESIZE;
+
+ auto& page_table = process->GetPageTable();
+
// Get bounds of where mapping is possible.
const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
@@ -64,9 +84,6 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
// Return failure, if necessary
R_UNLESS(i < 64, res);
- // Return the mapped page group.
- *out_page_group = std::move(pg);
-
// We succeeded.
R_SUCCEED();
}
@@ -135,6 +152,13 @@ Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv, Nvidia::D
R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size, nvmap_fd));
}
+void FreeHandle(u32 handle, Nvidia::Module& nvdrv, Nvidia::DeviceFD nvmap_fd) {
+ auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
+ ASSERT(nvmap != nullptr);
+
+ R_ASSERT(FreeNvMapHandle(*nvmap, handle, nvmap_fd));
+}
+
constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
constexpr u32 SharedBufferBlockLinearBpp = 4;
@@ -186,53 +210,97 @@ FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& fli
FbShareBufferManager::~FbShareBufferManager() = default;
-Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
+Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id,
+ u64* out_layer_handle, u64 display_id,
+ LayerBlending blending) {
std::scoped_lock lk{m_guard};
- // Ensure we have not already created a buffer.
- R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
+ // Ensure we haven't already created.
+ const u64 aruid = owner_process->GetProcessId();
+ R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied);
+
+ // Allocate memory for the shared buffer if needed.
+ if (!m_buffer_page_group) {
+ R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system,
+ SharedBufferSize));
+
+ // Record buffer id.
+ m_buffer_id = m_next_buffer_id++;
+
+ // Record display id.
+ m_display_id = display_id;
+ }
+
+ // Map into process.
+ Common::ProcessAddress map_address{};
+ R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
+ owner_process, m_system));
- // Allocate memory and space for the shared buffer.
- Common::ProcessAddress map_address;
- R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address),
- std::addressof(m_buffer_page_group), m_system,
- SharedBufferSize));
+ // Create new session.
+ auto [it, was_emplaced] = m_sessions.emplace(aruid, FbShareSession{});
+ auto& session = it->second;
auto& container = m_nvdrv->GetContainer();
- m_session_id = container.OpenSession(m_system.ApplicationProcess());
- m_nvmap_fd = m_nvdrv->Open("/dev/nvmap", m_session_id);
+ session.session_id = container.OpenSession(owner_process);
+ session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id);
// Create an nvmap handle for the buffer and assign the memory to it.
- R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, m_nvmap_fd,
- map_address, SharedBufferSize));
-
- // Record the display id.
- m_display_id = display_id;
+ R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv,
+ session.nvmap_fd, map_address, SharedBufferSize));
// Create and open a layer for the display.
- m_layer_id = m_flinger.CreateLayer(m_display_id).value();
- m_flinger.OpenLayer(m_layer_id);
-
- // Set up the buffer.
- m_buffer_id = m_next_buffer_id++;
+ session.layer_id = m_flinger.CreateLayer(m_display_id, blending).value();
+ m_flinger.OpenLayer(session.layer_id);
// Get the layer.
- VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
+ VI::Layer* layer = m_flinger.FindLayer(m_display_id, session.layer_id);
ASSERT(layer != nullptr);
// Get the producer and set preallocated buffers.
auto& producer = layer->GetBufferQueue();
- MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle);
- MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle);
+ MakeGraphicBuffer(producer, 0, session.buffer_nvmap_handle);
+ MakeGraphicBuffer(producer, 1, session.buffer_nvmap_handle);
// Assign outputs.
*out_buffer_id = m_buffer_id;
- *out_layer_id = m_layer_id;
+ *out_layer_handle = session.layer_id;
// We succeeded.
R_SUCCEED();
}
+void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
+ std::scoped_lock lk{m_guard};
+
+ if (m_buffer_id == 0) {
+ return;
+ }
+
+ const u64 aruid = owner_process->GetProcessId();
+ const auto it = m_sessions.find(aruid);
+ if (it == m_sessions.end()) {
+ return;
+ }
+
+ auto& session = it->second;
+
+ // Destroy the layer.
+ m_flinger.DestroyLayer(session.layer_id);
+
+ // Close nvmap handle.
+ FreeHandle(session.buffer_nvmap_handle, *m_nvdrv, session.nvmap_fd);
+
+ // Close nvmap device.
+ m_nvdrv->Close(session.nvmap_fd);
+
+ // Close session.
+ auto& container = m_nvdrv->GetContainer();
+ container.CloseSession(session.session_id);
+
+ // Erase.
+ m_sessions.erase(it);
+}
+
Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
s32* out_nvmap_handle,
SharedMemoryPoolLayout* out_pool_layout,
@@ -242,17 +310,18 @@ Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
+ R_UNLESS(m_sessions.contains(applet_resource_user_id), VI::ResultNotFound);
*out_pool_layout = SharedBufferPoolLayout;
*out_buffer_size = SharedBufferSize;
- *out_nvmap_handle = m_buffer_nvmap_handle;
+ *out_nvmap_handle = m_sessions[applet_resource_user_id].buffer_nvmap_handle;
R_SUCCEED();
}
Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
// Ensure the layer id is valid.
- R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound);
+ R_UNLESS(layer_id > 0, VI::ResultNotFound);
// Get the layer.
VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
@@ -309,6 +378,10 @@ Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
android::Status::NoError,
VI::ResultOperationFailed);
+ ON_RESULT_FAILURE {
+ producer.CancelBuffer(static_cast<s32>(slot), fence);
+ };
+
// Queue the buffer to the producer.
android::QueueBufferInput input{};
android::QueueBufferOutput output{};
@@ -342,4 +415,33 @@ Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadab
R_SUCCEED();
}
+Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) {
+ std::vector<u8> capture_buffer(m_system.GPU().GetAppletCaptureBuffer());
+ Common::ScratchBuffer<u32> scratch;
+
+ // TODO: this could be optimized
+ s64 e = -1280 * 768 * 4;
+ for (auto& block : *m_buffer_page_group) {
+ u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
+ u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
+
+ for (; start < end; start++) {
+ *start = 0;
+
+ if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
+ *start = capture_buffer[e];
+ }
+ e++;
+ }
+
+ m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
+ m_system.GPU().InvalidateRegion(addr, end - start);
+ });
+ }
+
+ *out_was_written = true;
+ *out_layer_index = 1;
+ R_SUCCEED();
+}
+
} // 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 @@
#pragma once
+#include <map>
+
#include "common/math_util.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
@@ -29,13 +32,18 @@ struct SharedMemoryPoolLayout {
};
static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
+struct FbShareSession;
+
class FbShareBufferManager final {
public:
explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
std::shared_ptr<Nvidia::Module> nvdrv);
~FbShareBufferManager();
- Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id);
+ Result Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle,
+ u64 display_id, LayerBlending blending);
+ void Finalize(Kernel::KProcess* owner_process);
+
Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
u64 applet_resource_user_id);
@@ -45,6 +53,8 @@ public:
u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
+ Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index);
+
private:
Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
@@ -52,11 +62,8 @@ private:
u64 m_next_buffer_id = 1;
u64 m_display_id = 0;
u64 m_buffer_id = 0;
- u64 m_layer_id = 0;
- u32 m_buffer_nvmap_handle = 0;
SharedMemoryPoolLayout m_pool_layout = {};
- Nvidia::DeviceFD m_nvmap_fd = {};
- Nvidia::NvCore::SessionId m_session_id = {};
+ std::map<u64, FbShareSession> m_sessions;
std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
std::mutex m_guard;
@@ -65,4 +72,11 @@ private:
std::shared_ptr<Nvidia::Module> m_nvdrv;
};
+struct FbShareSession {
+ Nvidia::DeviceFD nvmap_fd = {};
+ Nvidia::NvCore::SessionId session_id = {};
+ u64 layer_id = {};
+ u32 buffer_nvmap_handle = 0;
+};
+
} // 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,
.height = igbp_buffer.Height(),
.stride = igbp_buffer.Stride(),
.z_index = 0,
+ .blending = layer.GetBlending(),
.transform = static_cast<android::BufferTransformFlags>(item.transform),
.crop_rect = item.crop,
.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 @@
namespace Service::Nvnflinger {
+// hwc_layer_t::blending values
+enum class LayerBlending : u32 {
+ // No blending
+ None = 0x100,
+
+ // ONE / ONE_MINUS_SRC_ALPHA
+ Premultiplied = 0x105,
+
+ // SRC_ALPHA / ONE_MINUS_SRC_ALPHA
+ Coverage = 0x405,
+};
+
struct HwcLayer {
u32 buffer_handle;
u32 offset;
@@ -19,6 +31,7 @@ struct HwcLayer {
u32 height;
u32 stride;
s32 z_index;
+ LayerBlending blending;
android::BufferTransformFlags transform;
Common::Rectangle<int> crop_rect;
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) {
return true;
}
-std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
+std::optional<u64> Nvnflinger::CreateLayer(u64 display_id, LayerBlending blending) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
@@ -166,13 +166,14 @@ std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
}
const u64 layer_id = next_layer_id++;
- CreateLayerAtId(*display, layer_id);
+ CreateLayerAtId(*display, layer_id, blending);
return layer_id;
}
-void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
+void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending) {
const auto buffer_id = next_buffer_queue_id++;
display.CreateLayer(layer_id, buffer_id, nvdrv->container);
+ display.FindLayer(layer_id)->SetBlending(blending);
}
bool 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 @@
#include "common/thread.h"
#include "core/hle/result.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
namespace Common {
class Event;
@@ -72,7 +73,8 @@ public:
/// Creates a layer on the specified display and returns the layer ID.
///
/// If an invalid display ID is specified, then an empty optional is returned.
- [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
+ [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id,
+ LayerBlending blending = LayerBlending::None);
/// Opens a layer on all displays for the given layer ID.
bool OpenLayer(u64 layer_id);
@@ -128,7 +130,7 @@ private:
[[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
/// Creates a layer with the specified layer ID in the desired display.
- void CreateLayerAtId(VI::Display& display, u64 layer_id);
+ void CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending);
void SplitVSync(std::stop_token stop_token);
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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/event.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
namespace Service {
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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/kernel/k_hardware_timer.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_common.h"
+#include "core/hle/service/os/multi_wait.h"
+
+namespace Service {
+
+MultiWait::MultiWait() = default;
+MultiWait::~MultiWait() = default;
+
+MultiWaitHolder* MultiWait::WaitAny(Kernel::KernelCore& kernel) {
+ return this->TimedWaitImpl(kernel, -1);
+}
+
+MultiWaitHolder* MultiWait::TryWaitAny(Kernel::KernelCore& kernel) {
+ return this->TimedWaitImpl(kernel, 0);
+}
+
+MultiWaitHolder* MultiWait::TimedWaitAny(Kernel::KernelCore& kernel, s64 timeout_ns) {
+ return this->TimedWaitImpl(kernel, kernel.HardwareTimer().GetTick() + timeout_ns);
+}
+
+MultiWaitHolder* MultiWait::TimedWaitImpl(Kernel::KernelCore& kernel, s64 timeout_tick) {
+ std::array<MultiWaitHolder*, Kernel::Svc::ArgumentHandleCountMax> holders{};
+ std::array<Kernel::KSynchronizationObject*, Kernel::Svc::ArgumentHandleCountMax> objects{};
+
+ s32 out_index = -1;
+ s32 num_objects = 0;
+
+ for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it++) {
+ ASSERT(num_objects < Kernel::Svc::ArgumentHandleCountMax);
+ holders[num_objects] = std::addressof(*it);
+ objects[num_objects] = it->GetNativeHandle();
+ num_objects++;
+ }
+
+ Kernel::KSynchronizationObject::Wait(kernel, std::addressof(out_index), objects.data(),
+ num_objects, timeout_tick);
+
+ if (out_index == -1) {
+ return nullptr;
+ } else {
+ return holders[out_index];
+ }
+}
+
+void MultiWait::MoveAll(MultiWait* other) {
+ while (!other->m_wait_list.empty()) {
+ MultiWaitHolder& holder = other->m_wait_list.front();
+ holder.UnlinkFromMultiWait();
+ holder.LinkToMultiWait(this);
+ }
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/os/multi_wait_holder.h"
+
+namespace Kernel {
+class KernelCore;
+}
+
+namespace Service {
+
+class MultiWait final {
+public:
+ explicit MultiWait();
+ ~MultiWait();
+
+public:
+ MultiWaitHolder* WaitAny(Kernel::KernelCore& kernel);
+ MultiWaitHolder* TryWaitAny(Kernel::KernelCore& kernel);
+ MultiWaitHolder* TimedWaitAny(Kernel::KernelCore& kernel, s64 timeout_ns);
+ // TODO: SdkReplyAndReceive?
+
+ void MoveAll(MultiWait* other);
+
+private:
+ MultiWaitHolder* TimedWaitImpl(Kernel::KernelCore& kernel, s64 timeout_tick);
+
+private:
+ friend class MultiWaitHolder;
+ using ListType = Common::IntrusiveListMemberTraits<&MultiWaitHolder::m_list_node>::ListType;
+ ListType m_wait_list{};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/os/multi_wait.h"
+#include "core/hle/service/os/multi_wait_holder.h"
+
+namespace Service {
+
+void MultiWaitHolder::LinkToMultiWait(MultiWait* multi_wait) {
+ if (m_multi_wait != nullptr) {
+ UNREACHABLE();
+ }
+
+ m_multi_wait = multi_wait;
+ m_multi_wait->m_wait_list.push_back(*this);
+}
+
+void MultiWaitHolder::UnlinkFromMultiWait() {
+ if (m_multi_wait) {
+ m_multi_wait->m_wait_list.erase(m_multi_wait->m_wait_list.iterator_to(*this));
+ m_multi_wait = nullptr;
+ }
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/intrusive_list.h"
+
+namespace Kernel {
+class KSynchronizationObject;
+} // namespace Kernel
+
+namespace Service {
+
+class MultiWait;
+
+class MultiWaitHolder {
+public:
+ explicit MultiWaitHolder(Kernel::KSynchronizationObject* native_handle)
+ : m_native_handle(native_handle) {}
+
+ void LinkToMultiWait(MultiWait* multi_wait);
+ void UnlinkFromMultiWait();
+
+ void SetUserData(uintptr_t user_data) {
+ m_user_data = user_data;
+ }
+
+ uintptr_t GetUserData() const {
+ return m_user_data;
+ }
+
+ Kernel::KSynchronizationObject* GetNativeHandle() const {
+ return m_native_handle;
+ }
+
+private:
+ friend class MultiWait;
+ Common::IntrusiveListNode m_list_node{};
+ MultiWait* m_multi_wait{};
+ Kernel::KSynchronizationObject* m_native_handle{};
+ uintptr_t m_user_data{};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/os/multi_wait.h"
+
+namespace Service {
+
+namespace impl {
+
+class AutoMultiWaitHolder {
+private:
+ MultiWaitHolder m_holder;
+
+public:
+ template <typename T>
+ explicit AutoMultiWaitHolder(MultiWait* multi_wait, T&& arg) : m_holder(arg) {
+ m_holder.LinkToMultiWait(multi_wait);
+ }
+
+ ~AutoMultiWaitHolder() {
+ m_holder.UnlinkFromMultiWait();
+ }
+
+ std::pair<MultiWaitHolder*, int> ConvertResult(const std::pair<MultiWaitHolder*, int> result,
+ int index) {
+ if (result.first == std::addressof(m_holder)) {
+ return std::make_pair(static_cast<MultiWaitHolder*>(nullptr), index);
+ } else {
+ return result;
+ }
+ }
+};
+
+using WaitAnyFunction = decltype(&MultiWait::WaitAny);
+
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+ MultiWait* multi_wait, WaitAnyFunction func,
+ int) {
+ return std::pair<MultiWaitHolder*, int>((multi_wait->*func)(kernel), -1);
+}
+
+template <typename T, typename... Args>
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+ MultiWait* multi_wait, WaitAnyFunction func,
+ int index, T&& x, Args&&... args) {
+ AutoMultiWaitHolder holder(multi_wait, std::forward<T>(x));
+ return holder.ConvertResult(
+ WaitAnyImpl(kernel, multi_wait, func, index + 1, std::forward<Args>(args)...), index);
+}
+
+template <typename... Args>
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+ MultiWait* multi_wait, WaitAnyFunction func,
+ Args&&... args) {
+ return WaitAnyImpl(kernel, multi_wait, func, 0, std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+ WaitAnyFunction func, Args&&... args) {
+ MultiWait temp_multi_wait;
+ return WaitAnyImpl(kernel, std::addressof(temp_multi_wait), func, 0,
+ std::forward<Args>(args)...);
+}
+
+class NotBoolButInt {
+public:
+ constexpr NotBoolButInt(int v) : m_value(v) {}
+ constexpr operator int() const {
+ return m_value;
+ }
+ explicit operator bool() const = delete;
+
+private:
+ int m_value;
+};
+
+} // namespace impl
+
+template <typename... Args>
+ requires(sizeof...(Args) > 0)
+inline std::pair<MultiWaitHolder*, int> WaitAny(Kernel::KernelCore& kernel, MultiWait* multi_wait,
+ Args&&... args) {
+ return impl::WaitAnyImpl(kernel, &MultiWait::WaitAny, multi_wait, std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+ requires(sizeof...(Args) > 0)
+inline int WaitAny(Kernel::KernelCore& kernel, Args&&... args) {
+ return impl::WaitAnyImpl(kernel, &MultiWait::WaitAny, std::forward<Args>(args)...).second;
+}
+
+template <typename... Args>
+ requires(sizeof...(Args) > 0)
+inline std::pair<MultiWaitHolder*, int> TryWaitAny(Kernel::KernelCore& kernel,
+ MultiWait* multi_wait, Args&&... args) {
+ return impl::WaitAnyImpl(kernel, &MultiWait::TryWaitAny, multi_wait,
+ std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+ requires(sizeof...(Args) > 0)
+inline impl::NotBoolButInt TryWaitAny(Kernel::KernelCore& kernel, Args&&... args) {
+ return impl::WaitAnyImpl(kernel, &MultiWait::TryWaitAny, std::forward<Args>(args)...).second;
+}
+
+} // 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 @@
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_synchronization_object.h"
-#include "core/hle/service/mutex.h"
+#include "core/hle/service/os/mutex.h"
namespace Service {
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 @@
namespace Service {
-constexpr size_t MaximumWaitObjects = 0x40;
-
-enum HandleType {
+enum class UserDataTag {
Port,
Session,
DeferEvent,
- Event,
};
-ServerManager::ServerManager(Core::System& system) : m_system{system}, m_serve_mutex{system} {
+class Port : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Port> {
+public:
+ explicit Port(Kernel::KServerPort* server_port, SessionRequestHandlerFactory&& handler_factory)
+ : MultiWaitHolder(server_port), m_handler_factory(std::move(handler_factory)) {
+ this->SetUserData(static_cast<uintptr_t>(UserDataTag::Port));
+ }
+
+ ~Port() {
+ this->GetNativeHandle()->Close();
+ }
+
+ SessionRequestHandlerPtr CreateHandler() {
+ return m_handler_factory();
+ }
+
+private:
+ const SessionRequestHandlerFactory m_handler_factory;
+};
+
+class Session : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Session> {
+public:
+ explicit Session(Kernel::KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager>&& manager)
+ : MultiWaitHolder(server_session), m_manager(std::move(manager)) {
+ this->SetUserData(static_cast<uintptr_t>(UserDataTag::Session));
+ }
+
+ ~Session() {
+ this->GetNativeHandle()->Close();
+ }
+
+ std::shared_ptr<SessionRequestManager>& GetManager() {
+ return m_manager;
+ }
+
+ std::shared_ptr<HLERequestContext>& GetContext() {
+ return m_context;
+ }
+
+private:
+ std::shared_ptr<SessionRequestManager> m_manager;
+ std::shared_ptr<HLERequestContext> m_context;
+};
+
+ServerManager::ServerManager(Core::System& system) : m_system{system}, m_selection_mutex{system} {
// Initialize event.
- m_event = Kernel::KEvent::Create(system.Kernel());
- m_event->Initialize(nullptr);
+ m_wakeup_event = Kernel::KEvent::Create(system.Kernel());
+ m_wakeup_event->Initialize(nullptr);
// Register event.
- Kernel::KEvent::Register(system.Kernel(), m_event);
+ Kernel::KEvent::Register(system.Kernel(), m_wakeup_event);
+
+ // Link to holder.
+ m_wakeup_holder.emplace(std::addressof(m_wakeup_event->GetReadableEvent()));
+ m_wakeup_holder->LinkToMultiWait(std::addressof(m_deferred_list));
}
ServerManager::~ServerManager() {
// Signal stop.
m_stop_source.request_stop();
- m_event->Signal();
+ m_wakeup_event->Signal();
// Wait for processing to stop.
m_stopped.Wait();
m_threads.clear();
- // Clean up server ports.
- for (const auto& [port, handler] : m_ports) {
- port->Close();
+ // Clean up ports.
+ for (auto it = m_servers.begin(); it != m_servers.end(); it = m_servers.erase(it)) {
+ delete std::addressof(*it);
}
// Clean up sessions.
- for (const auto& [session, manager] : m_sessions) {
- session->Close();
- }
-
- for (const auto& request : m_deferrals) {
- request.session->Close();
+ for (auto it = m_sessions.begin(); it != m_sessions.end(); it = m_sessions.erase(it)) {
+ delete std::addressof(*it);
}
- // Close event.
- m_event->GetReadableEvent().Close();
- m_event->Close();
+ // Close wakeup event.
+ m_wakeup_event->GetReadableEvent().Close();
+ m_wakeup_event->Close();
if (m_deferral_event) {
m_deferral_event->GetReadableEvent().Close();
@@ -75,19 +116,19 @@ void ServerManager::RunServer(std::unique_ptr<ServerManager>&& server_manager) {
server_manager->m_system.RunServer(std::move(server_manager));
}
-Result ServerManager::RegisterSession(Kernel::KServerSession* session,
+Result ServerManager::RegisterSession(Kernel::KServerSession* server_session,
std::shared_ptr<SessionRequestManager> manager) {
- ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
-
// We are taking ownership of the server session, so don't open it.
+ auto* session = new Session(server_session, std::move(manager));
+
// Begin tracking the server session.
{
- std::scoped_lock ll{m_list_mutex};
- m_sessions.emplace(session, std::move(manager));
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_sessions.push_back(*session);
}
- // Signal the wakeup event.
- m_event->Signal();
+ // Register to wait on the session.
+ this->LinkToDeferredList(session);
R_SUCCEED();
}
@@ -95,21 +136,22 @@ Result ServerManager::RegisterSession(Kernel::KServerSession* session,
Result ServerManager::RegisterNamedService(const std::string& service_name,
SessionRequestHandlerFactory&& handler_factory,
u32 max_sessions) {
- ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
-
// Add the new server to sm: and get the moved server port.
Kernel::KServerPort* server_port{};
R_ASSERT(m_system.ServiceManager().RegisterService(std::addressof(server_port), service_name,
max_sessions, handler_factory));
+ // We are taking ownership of the server port, so don't open it.
+ auto* server = new Port(server_port, std::move(handler_factory));
+
// Begin tracking the server port.
{
- std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(server_port, std::move(handler_factory));
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_servers.push_back(*server);
}
- // Signal the wakeup event.
- m_event->Signal();
+ // Register to wait on the server port.
+ this->LinkToDeferredList(server);
R_SUCCEED();
}
@@ -127,8 +169,6 @@ Result ServerManager::RegisterNamedService(const std::string& service_name,
Result ServerManager::ManageNamedPort(const std::string& service_name,
SessionRequestHandlerFactory&& handler_factory,
u32 max_sessions) {
- ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
-
// Create a new port.
auto* port = Kernel::KPort::Create(m_system.Kernel());
port->Initialize(max_sessions, false, 0);
@@ -149,12 +189,18 @@ Result ServerManager::ManageNamedPort(const std::string& service_name,
// Open a new reference to the server port.
port->GetServerPort().Open();
- // Begin tracking the server port.
+ // Transfer ownership into a new port object.
+ auto* server = new Port(std::addressof(port->GetServerPort()), std::move(handler_factory));
+
+ // Begin tracking the port.
{
- std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler_factory));
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_servers.push_back(*server);
}
+ // Register to wait on the port.
+ this->LinkToDeferredList(server);
+
// We succeeded.
R_SUCCEED();
}
@@ -173,6 +219,11 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
// Set the output.
*out_event = m_deferral_event;
+ // Register to wait on the event.
+ m_deferral_holder.emplace(std::addressof(m_deferral_event->GetReadableEvent()));
+ m_deferral_holder->SetUserData(static_cast<uintptr_t>(UserDataTag::DeferEvent));
+ this->LinkToDeferredList(std::addressof(*m_deferral_holder));
+
// We succeeded.
R_SUCCEED();
}
@@ -191,270 +242,185 @@ Result ServerManager::LoopProcess() {
R_RETURN(this->LoopProcessImpl());
}
-Result ServerManager::LoopProcessImpl() {
- while (!m_stop_source.stop_requested()) {
- R_TRY(this->WaitAndProcessImpl());
+void ServerManager::LinkToDeferredList(MultiWaitHolder* holder) {
+ // Link.
+ {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ holder->LinkToMultiWait(std::addressof(m_deferred_list));
}
- R_SUCCEED();
+ // Signal the wakeup event.
+ m_wakeup_event->Signal();
}
-Result ServerManager::WaitAndProcessImpl() {
- Kernel::KScopedAutoObject<Kernel::KSynchronizationObject> wait_obj;
- HandleType wait_type{};
+void ServerManager::LinkDeferred() {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ m_multi_wait.MoveAll(std::addressof(m_deferred_list));
+}
+MultiWaitHolder* ServerManager::WaitSignaled() {
// Ensure we are the only thread waiting for this server.
- std::unique_lock sl{m_serve_mutex};
+ std::scoped_lock lk{m_selection_mutex};
- // If we're done, return before we start waiting.
- R_SUCCEED_IF(m_stop_source.stop_requested());
+ while (true) {
+ this->LinkDeferred();
- // Wait for a tracked object to become signaled.
- {
- s32 num_objs{};
- std::array<HandleType, MaximumWaitObjects> wait_types{};
- std::array<Kernel::KSynchronizationObject*, MaximumWaitObjects> wait_objs{};
-
- const auto AddWaiter{
- [&](Kernel::KSynchronizationObject* synchronization_object, HandleType type) {
- // Open a new reference to the object.
- synchronization_object->Open();
-
- // Insert into the list.
- wait_types[num_objs] = type;
- wait_objs[num_objs++] = synchronization_object;
- }};
-
- {
- std::scoped_lock ll{m_list_mutex};
-
- // Add all of our ports.
- for (const auto& [port, handler] : m_ports) {
- AddWaiter(port, HandleType::Port);
- }
-
- // Add all of our sessions.
- for (const auto& [session, manager] : m_sessions) {
- AddWaiter(session, HandleType::Session);
- }
+ // If we're done, return before we start waiting.
+ if (m_stop_source.stop_requested()) {
+ return nullptr;
}
- // Add the deferral wakeup event.
- if (m_deferral_event != nullptr) {
- AddWaiter(std::addressof(m_deferral_event->GetReadableEvent()), HandleType::DeferEvent);
+ auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
+ if (selected == std::addressof(*m_wakeup_holder)) {
+ // Clear and restart if we were woken up.
+ m_wakeup_event->Clear();
+ } else {
+ // Unlink and handle the event.
+ selected->UnlinkFromMultiWait();
+ return selected;
}
+ }
+}
- // Add the wakeup event.
- AddWaiter(std::addressof(m_event->GetReadableEvent()), HandleType::Event);
-
- // Clean up extra references on exit.
- SCOPE_EXIT({
- for (s32 i = 0; i < num_objs; i++) {
- wait_objs[i]->Close();
- }
- });
-
- // Wait for a signal.
- s32 out_index{-1};
- R_TRY_CATCH(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index,
- wait_objs.data(), num_objs, -1)) {
- R_CATCH(Kernel::ResultSessionClosed) {
- // On session closed, index is updated and we don't want to return an error.
- }
- }
- R_END_TRY_CATCH;
- ASSERT(out_index >= 0 && out_index < num_objs);
+Result ServerManager::Process(MultiWaitHolder* holder) {
+ switch (static_cast<UserDataTag>(holder->GetUserData())) {
+ case UserDataTag::Session:
+ R_RETURN(this->OnSessionEvent(static_cast<Session*>(holder)));
+ case UserDataTag::Port:
+ R_RETURN(this->OnPortEvent(static_cast<Port*>(holder)));
+ case UserDataTag::DeferEvent:
+ R_RETURN(this->OnDeferralEvent());
+ default:
+ UNREACHABLE();
+ }
+}
- // Set the output index.
- wait_obj = wait_objs[out_index];
- wait_type = wait_types[out_index];
+bool ServerManager::WaitAndProcessImpl() {
+ if (auto* signaled_holder = this->WaitSignaled(); signaled_holder != nullptr) {
+ R_ASSERT(this->Process(signaled_holder));
+ return true;
+ } else {
+ return false;
}
+}
- // Process what we just received, temporarily removing the object so it is
- // not processed concurrently by another thread.
- {
- switch (wait_type) {
- case HandleType::Port: {
- // Port signaled.
- auto* port = wait_obj->DynamicCast<Kernel::KServerPort*>();
- SessionRequestHandlerFactory handler_factory;
-
- // Remove from tracking.
- {
- std::scoped_lock ll{m_list_mutex};
- ASSERT(m_ports.contains(port));
- m_ports.at(port).swap(handler_factory);
- m_ports.erase(port);
- }
-
- // Allow other threads to serve.
- sl.unlock();
-
- // Finish.
- R_RETURN(this->OnPortEvent(port, std::move(handler_factory)));
- }
- case HandleType::Session: {
- // Session signaled.
- auto* session = wait_obj->DynamicCast<Kernel::KServerSession*>();
- std::shared_ptr<SessionRequestManager> manager;
-
- // Remove from tracking.
- {
- std::scoped_lock ll{m_list_mutex};
- ASSERT(m_sessions.contains(session));
- m_sessions.at(session).swap(manager);
- m_sessions.erase(session);
- }
-
- // Allow other threads to serve.
- sl.unlock();
-
- // Finish.
- R_RETURN(this->OnSessionEvent(session, std::move(manager)));
- }
- case HandleType::DeferEvent: {
- // Clear event.
- ASSERT(R_SUCCEEDED(m_deferral_event->Clear()));
-
- // Drain the list of deferrals while we process.
- std::list<RequestState> deferrals;
- {
- std::scoped_lock ll{m_list_mutex};
- m_deferrals.swap(deferrals);
- }
-
- // Allow other threads to serve.
- sl.unlock();
-
- // Finish.
- R_RETURN(this->OnDeferralEvent(std::move(deferrals)));
- }
- case HandleType::Event: {
- // Clear event and finish.
- R_RETURN(m_event->Clear());
- }
- default: {
- UNREACHABLE();
- }
- }
+Result ServerManager::LoopProcessImpl() {
+ while (!m_stop_source.stop_requested()) {
+ this->WaitAndProcessImpl();
}
+
+ R_SUCCEED();
}
-Result ServerManager::OnPortEvent(Kernel::KServerPort* port,
- SessionRequestHandlerFactory&& handler_factory) {
+Result ServerManager::OnPortEvent(Port* server) {
// Accept a new server session.
- Kernel::KServerSession* session = port->AcceptSession();
- ASSERT(session != nullptr);
+ auto* server_port = static_cast<Kernel::KServerPort*>(server->GetNativeHandle());
+ Kernel::KServerSession* server_session = server_port->AcceptSession();
+ ASSERT(server_session != nullptr);
// Create the session manager and install the handler.
auto manager = std::make_shared<SessionRequestManager>(m_system.Kernel(), *this);
- manager->SetSessionHandler(handler_factory());
+ manager->SetSessionHandler(server->CreateHandler());
- // Track the server session.
- {
- std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(port, std::move(handler_factory));
- m_sessions.emplace(session, std::move(manager));
- }
+ // Create and register the new session.
+ this->RegisterSession(server_session, std::move(manager));
- // Signal the wakeup event.
- m_event->Signal();
+ // Resume tracking the port.
+ this->LinkToDeferredList(server);
// We succeeded.
R_SUCCEED();
}
-Result ServerManager::OnSessionEvent(Kernel::KServerSession* session,
- std::shared_ptr<SessionRequestManager>&& manager) {
- Result rc{ResultSuccess};
+Result ServerManager::OnSessionEvent(Session* session) {
+ Result res = ResultSuccess;
// Try to receive a message.
- std::shared_ptr<HLERequestContext> context;
- rc = session->ReceiveRequestHLE(&context, manager);
+ auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
+ res = server_session->ReceiveRequestHLE(&session->GetContext(), session->GetManager());
// If the session has been closed, we're done.
- if (rc == Kernel::ResultSessionClosed) {
- // Close the session.
- session->Close();
-
- // Finish.
+ if (res == Kernel::ResultSessionClosed) {
+ this->DestroySession(session);
R_SUCCEED();
}
- ASSERT(R_SUCCEEDED(rc));
- RequestState request{
- .session = session,
- .context = std::move(context),
- .manager = std::move(manager),
- };
+ R_ASSERT(res);
// Complete the sync request with deferral handling.
- R_RETURN(this->CompleteSyncRequest(std::move(request)));
+ R_RETURN(this->CompleteSyncRequest(session));
}
-Result ServerManager::CompleteSyncRequest(RequestState&& request) {
- Result rc{ResultSuccess};
- Result service_rc{ResultSuccess};
+Result ServerManager::CompleteSyncRequest(Session* session) {
+ Result res = ResultSuccess;
+ Result service_res = ResultSuccess;
// Mark the request as not deferred.
- request.context->SetIsDeferred(false);
+ session->GetContext()->SetIsDeferred(false);
// Complete the request. We have exclusive access to this session.
- service_rc = request.manager->CompleteSyncRequest(request.session, *request.context);
+ auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
+ service_res =
+ session->GetManager()->CompleteSyncRequest(server_session, *session->GetContext());
// If we've been deferred, we're done.
- if (request.context->GetIsDeferred()) {
- // Insert into deferral list.
- std::scoped_lock ll{m_list_mutex};
- m_deferrals.emplace_back(std::move(request));
+ if (session->GetContext()->GetIsDeferred()) {
+ // Insert into deferred session list.
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_deferred_sessions.push_back(session);
// Finish.
R_SUCCEED();
}
// Send the reply.
- rc = request.session->SendReplyHLE();
+ res = server_session->SendReplyHLE();
// If the session has been closed, we're done.
- if (rc == Kernel::ResultSessionClosed || service_rc == IPC::ResultSessionClosed) {
- // Close the session.
- request.session->Close();
-
- // Finish.
+ if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
+ this->DestroySession(session);
R_SUCCEED();
}
- ASSERT(R_SUCCEEDED(rc));
- ASSERT(R_SUCCEEDED(service_rc));
-
- // Reinsert the session.
- {
- std::scoped_lock ll{m_list_mutex};
- m_sessions.emplace(request.session, std::move(request.manager));
- }
+ R_ASSERT(res);
+ R_ASSERT(service_res);
- // Signal the wakeup event.
- m_event->Signal();
+ // We succeeded, so we can process future messages on this session.
+ this->LinkToDeferredList(session);
- // We succeeded.
R_SUCCEED();
}
-Result ServerManager::OnDeferralEvent(std::list<RequestState>&& deferrals) {
- ON_RESULT_FAILURE {
- std::scoped_lock ll{m_list_mutex};
- m_deferrals.splice(m_deferrals.end(), deferrals);
- };
+Result ServerManager::OnDeferralEvent() {
+ // Clear event before grabbing the list.
+ m_deferral_event->Clear();
- while (!deferrals.empty()) {
- RequestState request = deferrals.front();
- deferrals.pop_front();
+ // Get and clear list.
+ const auto deferrals = [&] {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ return std::move(m_deferred_sessions);
+ }();
- // Try again to complete the request.
- R_TRY(this->CompleteSyncRequest(std::move(request)));
+ // Relink deferral event.
+ this->LinkToDeferredList(std::addressof(*m_deferral_holder));
+
+ // For each session, try again to complete the request.
+ for (auto* session : deferrals) {
+ R_ASSERT(this->CompleteSyncRequest(session));
}
R_SUCCEED();
}
+void ServerManager::DestroySession(Session* session) {
+ // Unlink.
+ {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ m_sessions.erase(m_sessions.iterator_to(*session));
+ }
+
+ // Free the session.
+ delete session;
+}
+
} // 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 @@
#pragma once
-#include <functional>
#include <list>
-#include <map>
#include <mutex>
-#include <string_view>
+#include <optional>
#include <vector>
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/hle/result.h"
#include "core/hle/service/hle_ipc.h"
-#include "core/hle/service/mutex.h"
+#include "core/hle/service/os/multi_wait.h"
+#include "core/hle/service/os/mutex.h"
namespace Core {
class System;
@@ -24,11 +23,13 @@ namespace Kernel {
class KEvent;
class KServerPort;
class KServerSession;
-class KSynchronizationObject;
} // namespace Kernel
namespace Service {
+class Port;
+class Session;
+
class ServerManager {
public:
explicit ServerManager(Core::System& system);
@@ -52,34 +53,40 @@ public:
static void RunServer(std::unique_ptr<ServerManager>&& server);
private:
- struct RequestState;
-
+ void LinkToDeferredList(MultiWaitHolder* holder);
+ void LinkDeferred();
+ MultiWaitHolder* WaitSignaled();
+ Result Process(MultiWaitHolder* holder);
+ bool WaitAndProcessImpl();
Result LoopProcessImpl();
- Result WaitAndProcessImpl();
- Result OnPortEvent(Kernel::KServerPort* port, SessionRequestHandlerFactory&& handler_factory);
- Result OnSessionEvent(Kernel::KServerSession* session,
- std::shared_ptr<SessionRequestManager>&& manager);
- Result OnDeferralEvent(std::list<RequestState>&& deferrals);
- Result CompleteSyncRequest(RequestState&& state);
+
+ Result OnPortEvent(Port* port);
+ Result OnSessionEvent(Session* session);
+ Result OnDeferralEvent();
+ Result CompleteSyncRequest(Session* session);
+
+private:
+ void DestroySession(Session* session);
private:
Core::System& m_system;
- Mutex m_serve_mutex;
- std::mutex m_list_mutex;
+ Mutex m_selection_mutex;
- // Guest state tracking
- std::map<Kernel::KServerPort*, SessionRequestHandlerFactory> m_ports{};
- std::map<Kernel::KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions{};
- Kernel::KEvent* m_event{};
+ // Events
+ Kernel::KEvent* m_wakeup_event{};
Kernel::KEvent* m_deferral_event{};
- // Deferral tracking
- struct RequestState {
- Kernel::KServerSession* session;
- std::shared_ptr<HLERequestContext> context;
- std::shared_ptr<SessionRequestManager> manager;
- };
- std::list<RequestState> m_deferrals{};
+ // Deferred wait list
+ std::mutex m_deferred_list_mutex{};
+ MultiWait m_deferred_list{};
+
+ // Guest state tracking
+ MultiWait m_multi_wait{};
+ Common::IntrusiveListBaseTraits<Port>::ListType m_servers{};
+ Common::IntrusiveListBaseTraits<Session>::ListType m_sessions{};
+ std::list<Session*> m_deferred_sessions{};
+ std::optional<MultiWaitHolder> m_wakeup_holder{};
+ std::optional<MultiWaitHolder> m_deferral_holder{};
// Host state tracking
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 @@
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/audio/audio.h"
-#include "core/hle/service/bcat/bcat_module.h"
+#include "core/hle/service/bcat/bcat.h"
#include "core/hle/service/bpc/bpc.h"
#include "core/hle/service/btdrv/btdrv.h"
#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 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/vi/layer/vi_layer.h"
namespace Service::VI {
@@ -8,8 +9,9 @@ namespace Service::VI {
Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
android::BufferQueueProducer& binder_,
std::shared_ptr<android::BufferItemConsumer>&& consumer_)
- : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_},
- consumer{std::move(consumer_)}, open{false}, visible{true} {}
+ : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move(
+ consumer_)},
+ blending{Nvnflinger::LayerBlending::None}, open{false}, visible{true} {}
Layer::~Layer() = default;
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;
class BufferQueueProducer;
} // namespace Service::android
+namespace Service::Nvnflinger {
+enum class LayerBlending : u32;
+}
+
namespace Service::VI {
/// Represents a single display layer.
@@ -92,12 +96,21 @@ public:
return !std::exchange(open, true);
}
+ Nvnflinger::LayerBlending GetBlending() {
+ return blending;
+ }
+
+ void SetBlending(Nvnflinger::LayerBlending b) {
+ blending = b;
+ }
+
private:
const u64 layer_id;
const u32 binder_id;
android::BufferQueueCore& core;
android::BufferQueueProducer& binder;
std::shared_ptr<android::BufferItemConsumer> consumer;
+ Service::Nvnflinger::LayerBlending blending;
bool open;
bool visible;
};
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() {
EndGroup();
}
+void Config::ReadLibraryAppletValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
+
+ ReadCategory(Settings::Category::LibraryApplet);
+
+ EndGroup();
+}
+
void Config::ReadValues() {
if (global) {
ReadDataStorageValues();
@@ -410,6 +418,7 @@ void Config::ReadValues() {
ReadServiceValues();
ReadWebServiceValues();
ReadMiscellaneousValues();
+ ReadLibraryAppletValues();
}
ReadControlValues();
ReadCoreValues();
@@ -511,6 +520,7 @@ void Config::SaveValues() {
SaveNetworkValues();
SaveWebServiceValues();
SaveMiscellaneousValues();
+ SaveLibraryAppletValues();
} else {
LOG_DEBUG(Config, "Saving only generic configuration values");
}
@@ -691,6 +701,14 @@ void Config::SaveWebServiceValues() {
EndGroup();
}
+void Config::SaveLibraryAppletValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
+
+ WriteCategory(Settings::Category::LibraryApplet);
+
+ EndGroup();
+}
+
bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) {
std::string full_key = GetFullKey(key, false);
if (!default_value.has_value()) {
@@ -867,15 +885,9 @@ void Config::Reload() {
}
void Config::ClearControlPlayerValues() const {
- // If key is an empty string, all keys in the current group() are removed.
+ // Removes the entire [Controls] section
const char* section = Settings::TranslateCategory(Settings::Category::Controls);
- CSimpleIniA::TNamesDepend keys;
- config->GetAllKeys(section, keys);
- for (const auto& key : keys) {
- if (std::string(config->GetValue(section, key.pItem)).empty()) {
- config->Delete(section, key.pItem);
- }
- }
+ config->Delete(section, nullptr, true);
}
const 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:
void ReadSystemValues();
void ReadWebServiceValues();
void ReadNetworkValues();
+ void ReadLibraryAppletValues();
// Read platform specific sections
virtual void ReadHidbusValues() = 0;
@@ -121,6 +122,7 @@ protected:
void SaveScreenshotValues();
void SaveSystemValues();
void SaveWebServiceValues();
+ void SaveLibraryAppletValues();
// Save platform specific sections
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
buffer_cache/usage_tracker.h
buffer_cache/word_manager.h
cache_types.h
+ capture.h
cdma_pusher.cpp
cdma_pusher.h
compatible_formats.cpp
@@ -101,6 +102,7 @@ add_library(video_core STATIC
memory_manager.cpp
memory_manager.h
precompiled_headers.h
+ present.h
pte_kind.h
query_cache/bank_base.h
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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "common/bit_util.h"
+#include "common/common_types.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "video_core/surface.h"
+
+namespace VideoCore::Capture {
+
+constexpr u32 BlockHeight = 4;
+constexpr u32 BlockDepth = 0;
+constexpr u32 BppLog2 = 2;
+
+constexpr auto PixelFormat = Surface::PixelFormat::B8G8R8A8_UNORM;
+
+constexpr auto LinearWidth = Layout::ScreenUndocked::Width;
+constexpr auto LinearHeight = Layout::ScreenUndocked::Height;
+constexpr auto LinearDepth = 1U;
+constexpr auto BytesPerPixel = 4U;
+
+constexpr auto TiledWidth = LinearWidth;
+constexpr auto TiledHeight = Common::AlignUpLog2(LinearHeight, BlockHeight + BlockDepth + BppLog2);
+constexpr auto TiledSize = TiledWidth * TiledHeight * (1 << BppLog2);
+
+constexpr Layout::FramebufferLayout Layout{
+ .width = LinearWidth,
+ .height = LinearHeight,
+ .screen = {0, 0, LinearWidth, LinearHeight},
+ .is_srgb = false,
+};
+
+} // 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 @@
namespace Tegra {
+enum class BlendMode {
+ Opaque,
+ Premultiplied,
+ Coverage,
+};
+
/**
* Struct describing framebuffer configuration
*/
@@ -23,6 +29,7 @@ struct FramebufferConfig {
Service::android::PixelFormat pixel_format{};
Service::android::BufferTransformFlags transform_flags{};
Common::Rectangle<int> crop_rect{};
+ BlendMode blending{};
};
Common::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 {
WaitForSyncOperation(wait_fence);
}
+ std::vector<u8> GetAppletCaptureBuffer() {
+ std::vector<u8> out;
+
+ const auto wait_fence =
+ RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); });
+ gpu_thread.TickGPU();
+ WaitForSyncOperation(wait_fence);
+
+ return out;
+ }
+
GPU& gpu;
Core::System& system;
Host1x::Host1x& host1x;
@@ -505,6 +516,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
impl->RequestComposite(std::move(layers), std::move(fences));
}
+std::vector<u8> GPU::GetAppletCaptureBuffer() {
+ return impl->GetAppletCaptureBuffer();
+}
+
u64 GPU::GetTicks() const {
return impl->GetTicks();
}
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:
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
std::vector<Service::Nvidia::NvFence>&& fences);
+ std::vector<u8> GetAppletCaptureBuffer();
+
/// Performs any additional setup necessary in order to begin GPU emulation.
/// This can be used to launch any necessary threads and register any necessary
/// 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;
#define A_GPU 1
#define A_GLSL 1
+#define FSR_RCAS_PASSTHROUGH_ALPHA 1
#ifndef YUZU_USE_FP16
#include "ffx_a.h"
@@ -71,9 +72,7 @@ layout(set=0,binding=0) uniform sampler2D InputTexture;
#include "ffx_fsr1.h"
-#if USE_RCAS
- layout(location = 0) in vec2 frag_texcoord;
-#endif
+layout (location = 0) in vec2 frag_texcoord;
layout (location = 0) out vec4 frag_color;
void CurrFilter(AU2 pos) {
@@ -81,22 +80,22 @@ void CurrFilter(AU2 pos) {
#ifndef YUZU_USE_FP16
AF3 c;
FsrEasuF(c, pos, Const0, Const1, Const2, Const3);
- frag_color = AF4(c, 1.0);
+ frag_color = AF4(c, texture(InputTexture, frag_texcoord).a);
#else
AH3 c;
FsrEasuH(c, pos, Const0, Const1, Const2, Const3);
- frag_color = AH4(c, 1.0);
+ frag_color = AH4(c, texture(InputTexture, frag_texcoord).a);
#endif
#endif
#if USE_RCAS
#ifndef YUZU_USE_FP16
- AF3 c;
- FsrRcasF(c.r, c.g, c.b, pos, Const0);
- frag_color = AF4(c, 1.0);
+ AF4 c;
+ FsrRcasF(c.r, c.g, c.b, c.a, pos, Const0);
+ frag_color = c;
#else
- AH3 c;
- FsrRcasH(c.r, c.g, c.b, pos, Const0);
- frag_color = AH4(c, 1.0);
+ AH4 c;
+ FsrRcasH(c.r, c.g, c.b, c.a, pos, Const0);
+ frag_color = c;
#endif
#endif
}
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) {
}
void main() {
- frag_color = vec4(FxaaPixelShader(posPos, input_texture), 1.0);
+ frag_color = vec4(FxaaPixelShader(posPos, input_texture), texture(input_texture, posPos.xy).a);
}
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];
#define A_GPU 1
#define A_GLSL 1
+#define FSR_RCAS_PASSTHROUGH_ALPHA 1
#ifdef YUZU_USE_FP16
#define A_HALF
@@ -67,9 +68,7 @@ layout (location = 0) uniform uvec4 constants[4];
#include "ffx_fsr1.h"
-#if USE_RCAS
- layout(location = 0) in vec2 frag_texcoord;
-#endif
+layout (location = 0) in vec2 frag_texcoord;
layout (location = 0) out vec4 frag_color;
void CurrFilter(AU2 pos)
@@ -78,22 +77,22 @@ void CurrFilter(AU2 pos)
#ifndef YUZU_USE_FP16
AF3 c;
FsrEasuF(c, pos, constants[0], constants[1], constants[2], constants[3]);
- frag_color = AF4(c, 1.0);
+ frag_color = AF4(c, texture(InputTexture, frag_texcoord).a);
#else
AH3 c;
FsrEasuH(c, pos, constants[0], constants[1], constants[2], constants[3]);
- frag_color = AH4(c, 1.0);
+ frag_color = AH4(c, texture(InputTexture, frag_texcoord).a);
#endif
#endif
#if USE_RCAS
#ifndef YUZU_USE_FP16
- AF3 c;
- FsrRcasF(c.r, c.g, c.b, pos, constants[0]);
- frag_color = AF4(c, 1.0);
+ AF4 c;
+ FsrRcasF(c.r, c.g, c.b, c.a, pos, constants[0]);
+ frag_color = c;
#else
AH3 c;
- FsrRcasH(c.r, c.g, c.b, pos, constants[0]);
- frag_color = AH4(c, 1.0);
+ FsrRcasH(c.r, c.g, c.b, c.a, pos, constants[0]);
+ frag_color = c;
#endif
#endif
}
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;
layout (binding = 0) uniform sampler2D color_texture;
void main() {
- color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f);
+ color = vec4(texture(color_texture, frag_tex_coord));
}
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 ) {
}
void main() {
- color = vec4(textureBicubic(color_texture, frag_tex_coord).rgb, 1.0f);
+ color = textureBicubic(color_texture, frag_tex_coord);
}
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) {
}
void main() {
- vec3 base = texture(color_texture, vec2(frag_tex_coord)).rgb * weight[0];
+ vec4 base = texture(color_texture, vec2(frag_tex_coord)) * weight[0];
vec2 tex_offset = 1.0f / textureSize(color_texture, 0);
// TODO(Blinkhawk): This code can be optimized through shader group instructions.
- vec3 horizontal = blurHorizontal(color_texture, frag_tex_coord, tex_offset).rgb;
- vec3 vertical = blurVertical(color_texture, frag_tex_coord, tex_offset).rgb;
- vec3 diagonalA = blurDiagonal(color_texture, frag_tex_coord, tex_offset).rgb;
- vec3 diagonalB = blurDiagonal(color_texture, frag_tex_coord, tex_offset * vec2(1.0, -1.0)).rgb;
- vec3 combination = mix(mix(horizontal, vertical, 0.5f), mix(diagonalA, diagonalB, 0.5f), 0.5f);
- color = vec4(combination + base, 1.0f);
+ vec4 horizontal = blurHorizontal(color_texture, frag_tex_coord, tex_offset);
+ vec4 vertical = blurVertical(color_texture, frag_tex_coord, tex_offset);
+ vec4 diagonalA = blurDiagonal(color_texture, frag_tex_coord, tex_offset);
+ vec4 diagonalB = blurDiagonal(color_texture, frag_tex_coord, tex_offset * vec2(1.0, -1.0));
+ vec4 combination = mix(mix(horizontal, vertical, 0.5f), mix(diagonalA, diagonalB, 0.5f), 0.5f);
+ color = combination + base;
}
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 @@
#define YUZU_USE_FP16
#define USE_EASU 1
+#define VERSION 1
#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 @@
#extension GL_GOOGLE_include_directive : enable
#define USE_EASU 1
+#define VERSION 1
#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 @@
#define YUZU_USE_FP16
#define USE_RCAS 1
+#define VERSION 1
#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 @@
#extension GL_GOOGLE_include_directive : enable
#define USE_RCAS 1
+#define VERSION 1
#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 {
// Any member of a push constant block that is declared as an
// array must only be accessed with dynamically uniform indices.
ScreenRectVertex GetVertex(int index) {
- switch (index) {
- case 0:
- default:
+ if (index < 1) {
return vertices[0];
- case 1:
+ } else if (index < 2) {
return vertices[1];
- case 2:
+ } else if (index < 3) {
return vertices[2];
- case 3:
+ } else {
return vertices[3];
}
}
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 @@
#extension GL_GOOGLE_include_directive : enable
-#define VERSION 1
+#define VERSION 2
#define YUZU_USE_FP16
#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 @@
#extension GL_GOOGLE_include_directive : enable
-#define VERSION 1
+#define VERSION 2
#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 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/settings.h"
+
+static inline Settings::ScalingFilter GetScalingFilter() {
+ return Settings::values.scaling_filter.GetValue();
+}
+
+static inline Settings::AntiAliasing GetAntiAliasing() {
+ return Settings::values.anti_aliasing.GetValue();
+}
+
+static inline Settings::ScalingFilter GetScalingFilterForAppletCapture() {
+ return Settings::ScalingFilter::Bilinear;
+}
+
+static inline Settings::AntiAliasing GetAntiAliasingForAppletCapture() {
+ return Settings::AntiAliasing::None;
+}
+
+struct PresentFilters {
+ Settings::ScalingFilter (*get_scaling_filter)();
+ Settings::AntiAliasing (*get_anti_aliasing)();
+};
+
+constexpr PresentFilters PresentFiltersForDisplay{
+ .get_scaling_filter = &GetScalingFilter,
+ .get_anti_aliasing = &GetAntiAliasing,
+};
+
+constexpr PresentFilters PresentFiltersForAppletCapture{
+ .get_scaling_filter = &GetScalingFilterForAppletCapture,
+ .get_anti_aliasing = &GetAntiAliasingForAppletCapture,
+};
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:
/// Finalize rendering the guest frame and draw into the presentation texture
virtual void Composite(std::span<const Tegra::FramebufferConfig> layers) = 0;
+ /// Get the tiled applet layer capture buffer
+ virtual std::vector<u8> GetAppletCaptureBuffer() = 0;
+
[[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
[[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 @@
#include "core/frontend/emu_window.h"
#include "core/frontend/graphics_context.h"
+#include "video_core/capture.h"
#include "video_core/renderer_null/renderer_null.h"
namespace Null {
@@ -22,4 +23,8 @@ void RendererNull::Composite(std::span<const Tegra::FramebufferConfig> framebuff
render_window.OnFrameDisplayed();
}
+std::vector<u8> RendererNull::GetAppletCaptureBuffer() {
+ return std::vector<u8>(VideoCore::Capture::TiledSize);
+}
+
} // 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:
void Composite(std::span<const Tegra::FramebufferConfig> framebuffer) override;
+ std::vector<u8> GetAppletCaptureBuffer() override;
+
VideoCore::RasterizerInterface* ReadRasterizer() override {
return &m_rasterizer;
}
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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
+#include "video_core/present.h"
#include "video_core/renderer_opengl/gl_blit_screen.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/present/filters.h"
@@ -13,14 +14,14 @@ namespace OpenGL {
BlitScreen::BlitScreen(RasterizerOpenGL& rasterizer_,
Tegra::MaxwellDeviceMemoryManager& device_memory_,
StateTracker& state_tracker_, ProgramManager& program_manager_,
- Device& device_)
+ Device& device_, const PresentFilters& filters_)
: rasterizer(rasterizer_), device_memory(device_memory_), state_tracker(state_tracker_),
- program_manager(program_manager_), device(device_) {}
+ program_manager(program_manager_), device(device_), filters(filters_) {}
BlitScreen::~BlitScreen() = default;
void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers,
- const Layout::FramebufferLayout& layout) {
+ const Layout::FramebufferLayout& layout, bool invert_y) {
// TODO: Signal state tracker about these changes
state_tracker.NotifyScreenDrawVertexArray();
state_tracker.NotifyPolygonModes();
@@ -56,22 +57,22 @@ void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffe
glDepthRangeIndexed(0, 0.0, 0.0);
while (layers.size() < framebuffers.size()) {
- layers.emplace_back(rasterizer, device_memory);
+ layers.emplace_back(rasterizer, device_memory, filters);
}
CreateWindowAdapt();
- window_adapt->DrawToFramebuffer(program_manager, layers, framebuffers, layout);
+ window_adapt->DrawToFramebuffer(program_manager, layers, framebuffers, layout, invert_y);
// TODO
// program_manager.RestoreGuestPipeline();
}
void BlitScreen::CreateWindowAdapt() {
- if (window_adapt && Settings::values.scaling_filter.GetValue() == current_window_adapt) {
+ if (window_adapt && filters.get_scaling_filter() == current_window_adapt) {
return;
}
- current_window_adapt = Settings::values.scaling_filter.GetValue();
+ current_window_adapt = filters.get_scaling_filter();
switch (current_window_adapt) {
case Settings::ScalingFilter::NearestNeighbor:
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 {
struct FramebufferLayout;
}
+struct PresentFilters;
+
namespace Tegra {
struct FramebufferConfig;
}
@@ -46,12 +48,12 @@ public:
explicit BlitScreen(RasterizerOpenGL& rasterizer,
Tegra::MaxwellDeviceMemoryManager& device_memory,
StateTracker& state_tracker, ProgramManager& program_manager,
- Device& device);
+ Device& device, const PresentFilters& filters);
~BlitScreen();
/// Draws the emulated screens to the emulator window.
void DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers,
- const Layout::FramebufferLayout& layout);
+ const Layout::FramebufferLayout& layout, bool invert_y);
private:
void CreateWindowAdapt();
@@ -61,6 +63,7 @@ private:
StateTracker& state_tracker;
ProgramManager& program_manager;
Device& device;
+ const PresentFilters& filters;
Settings::ScalingFilter current_window_adapt{};
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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/framebuffer_config.h"
+#include "video_core/present.h"
#include "video_core/renderer_opengl/gl_blit_screen.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/present/fsr.h"
@@ -14,8 +15,9 @@
namespace OpenGL {
-Layer::Layer(RasterizerOpenGL& rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_)
- : rasterizer(rasterizer_), device_memory(device_memory_) {
+Layer::Layer(RasterizerOpenGL& rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_,
+ const PresentFilters& filters_)
+ : rasterizer(rasterizer_), device_memory(device_memory_), filters(filters_) {
// Allocate textures for the screen
framebuffer_texture.resource.Create(GL_TEXTURE_2D);
@@ -34,12 +36,12 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
std::array<ScreenRectVertex, 4>& out_vertices,
ProgramManager& program_manager,
const Tegra::FramebufferConfig& framebuffer,
- const Layout::FramebufferLayout& layout) {
+ const Layout::FramebufferLayout& layout, bool invert_y) {
FramebufferTextureInfo info = PrepareRenderTarget(framebuffer);
auto crop = Tegra::NormalizeCrop(framebuffer, info.width, info.height);
GLuint texture = info.display_texture;
- auto anti_aliasing = Settings::values.anti_aliasing.GetValue();
+ auto anti_aliasing = filters.get_anti_aliasing();
if (anti_aliasing != Settings::AntiAliasing::None) {
glEnablei(GL_SCISSOR_TEST, 0);
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,
glDisablei(GL_SCISSOR_TEST, 0);
- if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
+ if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
if (!fsr || fsr->NeedsRecreation(layout.screen)) {
fsr = std::make_unique<FSR>(layout.screen.GetWidth(), layout.screen.GetHeight());
}
@@ -83,10 +85,15 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
const auto w = screen.GetWidth();
const auto h = screen.GetHeight();
- out_vertices[0] = ScreenRectVertex(x, y, crop.left, crop.top);
- out_vertices[1] = ScreenRectVertex(x + w, y, crop.right, crop.top);
- out_vertices[2] = ScreenRectVertex(x, y + h, crop.left, crop.bottom);
- out_vertices[3] = ScreenRectVertex(x + w, y + h, crop.right, crop.bottom);
+ const auto left = crop.left;
+ const auto right = crop.right;
+ const auto top = invert_y ? crop.bottom : crop.top;
+ const auto bottom = invert_y ? crop.top : crop.bottom;
+
+ out_vertices[0] = ScreenRectVertex(x, y, left, top);
+ out_vertices[1] = ScreenRectVertex(x + w, y, right, top);
+ out_vertices[2] = ScreenRectVertex(x, y + h, left, bottom);
+ out_vertices[3] = ScreenRectVertex(x + w, y + h, right, bottom);
return texture;
}
@@ -131,10 +138,12 @@ FramebufferTextureInfo Layer::LoadFBToScreenInfo(const Tegra::FramebufferConfig&
const u64 size_in_bytes{Tegra::Texture::CalculateSize(
true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
const u8* const host_ptr{device_memory.GetPointer<u8>(framebuffer_addr)};
- const std::span<const u8> input_data(host_ptr, size_in_bytes);
- Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel,
- framebuffer.width, framebuffer.height, 1, block_height_log2,
- 0);
+ if (host_ptr) {
+ const std::span<const u8> input_data(host_ptr, size_in_bytes);
+ Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel,
+ framebuffer.width, framebuffer.height, 1,
+ block_height_log2, 0);
+ }
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
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 {
struct FramebufferLayout;
}
+struct PresentFilters;
+
namespace Service::android {
enum class PixelFormat : u32;
};
@@ -44,14 +46,15 @@ struct ScreenRectVertex;
class Layer {
public:
- explicit Layer(RasterizerOpenGL& rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory);
+ explicit Layer(RasterizerOpenGL& rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory,
+ const PresentFilters& filters);
~Layer();
GLuint ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
std::array<ScreenRectVertex, 4>& out_vertices,
ProgramManager& program_manager,
const Tegra::FramebufferConfig& framebuffer,
- const Layout::FramebufferLayout& layout);
+ const Layout::FramebufferLayout& layout, bool invert_y);
private:
/// Loads framebuffer from emulated memory into the active OpenGL texture.
@@ -65,6 +68,7 @@ private:
private:
RasterizerOpenGL& rasterizer;
Tegra::MaxwellDeviceMemoryManager& device_memory;
+ const PresentFilters& filters;
/// OpenGL framebuffer data
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;
void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers,
std::span<const Tegra::FramebufferConfig> framebuffers,
- const Layout::FramebufferLayout& layout) {
+ const Layout::FramebufferLayout& layout, bool invert_y) {
GLint old_read_fb;
GLint old_draw_fb;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -51,7 +51,7 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
auto layer_it = layers.begin();
for (size_t i = 0; i < layer_count; i++) {
textures[i] = layer_it->ConfigureDraw(matrices[i], vertices[i], program_manager,
- framebuffers[i], layout);
+ framebuffers[i], layout, invert_y);
layer_it++;
}
@@ -92,6 +92,21 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
glClear(GL_COLOR_BUFFER_BIT);
for (size_t i = 0; i < layer_count; i++) {
+ switch (framebuffers[i].blending) {
+ case Tegra::BlendMode::Opaque:
+ default:
+ glDisablei(GL_BLEND, 0);
+ break;
+ case Tegra::BlendMode::Premultiplied:
+ glEnablei(GL_BLEND, 0);
+ glBlendFuncSeparatei(0, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
+ break;
+ case Tegra::BlendMode::Coverage:
+ glEnablei(GL_BLEND, 0);
+ glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
+ break;
+ }
+
glBindTextureUnit(0, textures[i]);
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
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:
void DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers,
std::span<const Tegra::FramebufferConfig> framebuffers,
- const Layout::FramebufferLayout& layout);
+ const Layout::FramebufferLayout& layout, bool invert_y);
private:
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 @@
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/telemetry_session.h"
+#include "video_core/capture.h"
+#include "video_core/present.h"
#include "video_core/renderer_opengl/gl_blit_screen.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -120,7 +122,15 @@ RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
glEnableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV);
}
blit_screen = std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker,
- program_manager, device);
+ program_manager, device, PresentFiltersForDisplay);
+ blit_applet =
+ std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker, program_manager,
+ device, PresentFiltersForAppletCapture);
+ capture_framebuffer.Create();
+ capture_renderbuffer.Create();
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_renderbuffer.handle);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, VideoCore::Capture::LinearWidth,
+ VideoCore::Capture::LinearHeight);
}
RendererOpenGL::~RendererOpenGL() = default;
@@ -130,10 +140,11 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
return;
}
+ RenderAppletCaptureLayer(framebuffers);
RenderScreenshot(framebuffers);
state_tracker.BindFramebuffer(0);
- blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout());
+ blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout(), false);
++m_current_frame;
@@ -159,11 +170,8 @@ void RendererOpenGL::AddTelemetryFields() {
telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
}
-void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
- if (!renderer_settings.screenshot_requested) {
- return;
- }
-
+void RendererOpenGL::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+ const Layout::FramebufferLayout& layout, void* dst) {
GLint old_read_fb;
GLint old_draw_fb;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -173,29 +181,86 @@ void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
screenshot_framebuffer.Create();
glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
- const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
-
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, layout.width, layout.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
- blit_screen->DrawScreen(framebuffers, layout);
+ blit_screen->DrawScreen(framebuffers, layout, false);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
- glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
- renderer_settings.screenshot_bits);
+ glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, dst);
screenshot_framebuffer.Release();
glDeleteRenderbuffers(1, &renderbuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+}
+
+void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
+ if (!renderer_settings.screenshot_requested) {
+ return;
+ }
+
+ RenderToBuffer(framebuffers, renderer_settings.screenshot_framebuffer_layout,
+ renderer_settings.screenshot_bits);
renderer_settings.screenshot_complete_callback(true);
renderer_settings.screenshot_requested = false;
}
+void RendererOpenGL::RenderAppletCaptureLayer(
+ std::span<const Tegra::FramebufferConfig> framebuffers) {
+ GLint old_read_fb;
+ GLint old_draw_fb;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_framebuffer.handle);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
+ capture_renderbuffer.handle);
+
+ blit_applet->DrawScreen(framebuffers, VideoCore::Capture::Layout, true);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+}
+
+std::vector<u8> RendererOpenGL::GetAppletCaptureBuffer() {
+ using namespace VideoCore::Capture;
+
+ std::vector<u8> linear(TiledSize);
+ std::vector<u8> out(TiledSize);
+
+ GLint old_read_fb;
+ GLint old_draw_fb;
+ GLint old_pixel_pack_buffer;
+ GLint old_pack_row_length;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
+ glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &old_pixel_pack_buffer);
+ glGetIntegerv(GL_PACK_ROW_LENGTH, &old_pack_row_length);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_framebuffer.handle);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
+ capture_renderbuffer.handle);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ glReadPixels(0, 0, LinearWidth, LinearHeight, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ linear.data());
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, old_pixel_pack_buffer);
+ glPixelStorei(GL_PACK_ROW_LENGTH, old_pack_row_length);
+
+ Tegra::Texture::SwizzleTexture(out, linear, BytesPerPixel, LinearWidth, LinearHeight,
+ LinearDepth, BlockHeight, BlockDepth);
+
+ return out;
+}
+
} // 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:
void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override;
+ std::vector<u8> GetAppletCaptureBuffer() override;
+
VideoCore::RasterizerInterface* ReadRasterizer() override {
return &rasterizer;
}
@@ -52,7 +54,11 @@ public:
private:
void AddTelemetryFields();
+
+ void RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+ const Layout::FramebufferLayout& layout, void* dst);
void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
+ void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
Core::TelemetrySession& telemetry_session;
Core::Frontend::EmuWindow& emu_window;
@@ -64,8 +70,11 @@ private:
ProgramManager program_manager;
RasterizerOpenGL rasterizer;
OGLFramebuffer screenshot_framebuffer;
+ OGLFramebuffer capture_framebuffer;
+ OGLRenderbuffer capture_renderbuffer;
std::unique_ptr<BlitScreen> blit_screen;
+ std::unique_ptr<BlitScreen> blit_applet;
};
} // 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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "video_core/present.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "common/settings.h"
@@ -48,12 +49,12 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_,
- VkExtent2D output_size, VkDescriptorSetLayout layout)
+ VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_)
: device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_),
- device_memory(device_memory_), image_count(image_count_) {
+ device_memory(device_memory_), filters(filters_), image_count(image_count_) {
CreateDescriptorPool();
CreateDescriptorSets(layout);
- if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
+ if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
CreateFSR(output_size);
}
}
@@ -171,11 +172,11 @@ void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
}
void Layer::SetAntiAliasPass() {
- if (anti_alias && anti_alias_setting == Settings::values.anti_aliasing.GetValue()) {
+ if (anti_alias && anti_alias_setting == filters.get_anti_aliasing()) {
return;
}
- anti_alias_setting = Settings::values.anti_aliasing.GetValue();
+ anti_alias_setting = filters.get_anti_aliasing();
const VkExtent2D render_area{
.width = Settings::values.resolution_info.ScaleUp(raw_width),
@@ -270,9 +271,11 @@ void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t i
const u64 linear_size{GetSizeInBytes(framebuffer)};
const u64 tiled_size{Tegra::Texture::CalculateSize(
true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
- Tegra::Texture::UnswizzleTexture(
- mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
- bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
+ if (host_ptr) {
+ Tegra::Texture::UnswizzleTexture(
+ mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
+ bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
+ }
const VkBufferImageCopy copy{
.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 {
struct FramebufferLayout;
}
+struct PresentFilters;
+
namespace Tegra {
struct FramebufferConfig;
}
@@ -37,7 +39,8 @@ class Layer final {
public:
explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler,
Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count,
- VkExtent2D output_size, VkDescriptorSetLayout layout);
+ VkExtent2D output_size, VkDescriptorSetLayout layout,
+ const PresentFilters& filters);
~Layer();
void ConfigureDraw(PresentPushConstants* out_push_constants,
@@ -71,6 +74,7 @@ private:
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
Tegra::MaxwellDeviceMemoryManager& device_memory;
+ const PresentFilters& filters;
const size_t image_count{};
vk::DescriptorPool descriptor_pool{};
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,
});
}
-vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
- vk::PipelineLayout& layout,
- std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
- bool enable_blending) {
+static vk::Pipeline CreateWrappedPipelineImpl(
+ const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
+ VkPipelineColorBlendAttachmentState blending) {
const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
@@ -443,30 +443,6 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
.alphaToOneEnable = VK_FALSE,
};
- constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{
- .blendEnable = VK_FALSE,
- .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
- .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
- .colorBlendOp = VK_BLEND_OP_ADD,
- .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
- .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
- .alphaBlendOp = VK_BLEND_OP_ADD,
- .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
- VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
- };
-
- constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_enabled{
- .blendEnable = VK_TRUE,
- .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
- .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
- .colorBlendOp = VK_BLEND_OP_ADD,
- .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
- .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
- .alphaBlendOp = VK_BLEND_OP_ADD,
- .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
- VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
- };
-
const VkPipelineColorBlendStateCreateInfo color_blend_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.pNext = nullptr,
@@ -474,8 +450,7 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
- .pAttachments =
- enable_blending ? &color_blend_attachment_enabled : &color_blend_attachment_disabled,
+ .pAttachments = &blending,
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
};
@@ -515,6 +490,63 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
});
}
+vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
+ vk::PipelineLayout& layout,
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
+ constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{
+ .blendEnable = VK_FALSE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ };
+
+ return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
+ color_blend_attachment_disabled);
+}
+
+vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
+ const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
+ constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_premultiplied{
+ .blendEnable = VK_TRUE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ };
+
+ return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
+ color_blend_attachment_premultiplied);
+}
+
+vk::Pipeline CreateWrappedCoverageBlendingPipeline(
+ const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
+ constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_coverage{
+ .blendEnable = VK_TRUE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ };
+
+ return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
+ color_blend_attachment_coverage);
+}
+
VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images,
VkSampler sampler, VkImageView view,
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,
vk::DescriptorSetLayout& layout);
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
vk::PipelineLayout& layout,
- std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
- bool enable_blending = false);
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
+vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
+ const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
+vk::Pipeline CreateWrappedCoverageBlendingPipeline(
+ const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+ std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images,
VkSampler sampler, VkImageView view,
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,
CreatePipelineLayout();
CreateVertexShader();
CreateRenderPass(frame_format);
- CreatePipeline();
+ CreatePipelines();
}
WindowAdaptPass::~WindowAdaptPass() = default;
@@ -34,7 +34,6 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
const VkFramebuffer host_framebuffer{*dst->framebuffer};
const VkRenderPass renderpass{*render_pass};
- const VkPipeline graphics_pipeline{*pipeline};
const VkPipelineLayout graphics_pipeline_layout{*pipeline_layout};
const VkExtent2D render_area{
.width = dst->width,
@@ -44,9 +43,23 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
const size_t layer_count = configs.size();
std::vector<PresentPushConstants> push_constants(layer_count);
std::vector<VkDescriptorSet> descriptor_sets(layer_count);
+ std::vector<VkPipeline> graphics_pipelines(layer_count);
auto layer_it = layers.begin();
for (size_t i = 0; i < layer_count; i++) {
+ switch (configs[i].blending) {
+ case Tegra::BlendMode::Opaque:
+ default:
+ graphics_pipelines[i] = *opaque_pipeline;
+ break;
+ case Tegra::BlendMode::Premultiplied:
+ graphics_pipelines[i] = *premultiplied_pipeline;
+ break;
+ case Tegra::BlendMode::Coverage:
+ graphics_pipelines[i] = *coverage_pipeline;
+ break;
+ }
+
layer_it->ConfigureDraw(&push_constants[i], &descriptor_sets[i], rasterizer, *sampler,
image_index, configs[i], layout);
layer_it++;
@@ -77,8 +90,8 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
BeginRenderPass(cmdbuf, renderpass, host_framebuffer, render_area);
cmdbuf.ClearAttachments({clear_attachment}, {clear_rect});
- cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
for (size_t i = 0; i < layer_count; i++) {
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipelines[i]);
cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
push_constants[i]);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0,
@@ -129,9 +142,13 @@ void WindowAdaptPass::CreateRenderPass(VkFormat frame_format) {
render_pass = CreateWrappedRenderPass(device, frame_format, VK_IMAGE_LAYOUT_UNDEFINED);
}
-void WindowAdaptPass::CreatePipeline() {
- pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout,
- std::tie(vertex_shader, fragment_shader), false);
+void WindowAdaptPass::CreatePipelines() {
+ opaque_pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout,
+ std::tie(vertex_shader, fragment_shader));
+ premultiplied_pipeline = CreateWrappedPremultipliedBlendingPipeline(
+ device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader));
+ coverage_pipeline = CreateWrappedCoverageBlendingPipeline(
+ device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader));
}
} // 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:
void CreatePipelineLayout();
void CreateVertexShader();
void CreateRenderPass(VkFormat frame_format);
- void CreatePipeline();
+ void CreatePipelines();
private:
const Device& device;
@@ -52,7 +52,9 @@ private:
vk::ShaderModule vertex_shader;
vk::ShaderModule fragment_shader;
vk::RenderPass render_pass;
- vk::Pipeline pipeline;
+ vk::Pipeline opaque_pipeline;
+ vk::Pipeline premultiplied_pipeline;
+ vk::Pipeline coverage_pipeline;
};
} // 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 @@
#include "core/core_timing.h"
#include "core/frontend/graphics_context.h"
#include "core/telemetry_session.h"
+#include "video_core/capture.h"
#include "video_core/gpu.h"
+#include "video_core/present.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -38,6 +40,20 @@
namespace Vulkan {
namespace {
+
+constexpr VkExtent2D CaptureImageSize{
+ .width = VideoCore::Capture::LinearWidth,
+ .height = VideoCore::Capture::LinearHeight,
+};
+
+constexpr VkExtent3D CaptureImageExtent{
+ .width = VideoCore::Capture::LinearWidth,
+ .height = VideoCore::Capture::LinearHeight,
+ .depth = VideoCore::Capture::LinearDepth,
+};
+
+constexpr VkFormat CaptureFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+
std::string GetReadableVersion(u32 version) {
return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
VK_VERSION_PATCH(version));
@@ -99,10 +115,15 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
render_window.GetFramebufferLayout().height),
present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain,
surface),
- blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler),
- blit_screenshot(device_memory, device, memory_allocator, present_manager, scheduler),
+ blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler,
+ PresentFiltersForDisplay),
+ blit_capture(device_memory, device, memory_allocator, present_manager, scheduler,
+ PresentFiltersForDisplay),
+ blit_applet(device_memory, device, memory_allocator, present_manager, scheduler,
+ PresentFiltersForAppletCapture),
rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
- scheduler) {
+ scheduler),
+ applet_frame() {
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
turbo_mode.emplace(instance, dld);
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
@@ -125,6 +146,8 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
SCOPE_EXIT({ render_window.OnFrameDisplayed(); });
+ RenderAppletCaptureLayer(framebuffers);
+
if (!render_window.IsShown()) {
return;
}
@@ -167,30 +190,20 @@ void RendererVulkan::Report() const {
telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
}
-void Vulkan::RendererVulkan::RenderScreenshot(
- std::span<const Tegra::FramebufferConfig> framebuffers) {
- if (!renderer_settings.screenshot_requested) {
- return;
- }
-
- constexpr VkFormat ScreenshotFormat{VK_FORMAT_B8G8R8A8_UNORM};
- const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
-
+vk::Buffer RendererVulkan::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+ const Layout::FramebufferLayout& layout, VkFormat format,
+ VkDeviceSize buffer_size) {
auto frame = [&]() {
Frame f{};
- f.image = CreateWrappedImage(memory_allocator, VkExtent2D{layout.width, layout.height},
- ScreenshotFormat);
- f.image_view = CreateWrappedImageView(device, f.image, ScreenshotFormat);
- f.framebuffer = blit_screenshot.CreateFramebuffer(layout, *f.image_view, ScreenshotFormat);
+ f.image =
+ CreateWrappedImage(memory_allocator, VkExtent2D{layout.width, layout.height}, format);
+ f.image_view = CreateWrappedImageView(device, f.image, format);
+ f.framebuffer = blit_capture.CreateFramebuffer(layout, *f.image_view, format);
return f;
}();
- blit_screenshot.DrawToFrame(rasterizer, &frame, framebuffers, layout, 1,
- VK_FORMAT_B8G8R8A8_UNORM);
-
- const auto dst_buffer = CreateWrappedBuffer(
- memory_allocator, static_cast<VkDeviceSize>(layout.width * layout.height * 4),
- MemoryUsage::Download);
+ auto dst_buffer = CreateWrappedBuffer(memory_allocator, buffer_size, MemoryUsage::Download);
+ blit_capture.DrawToFrame(rasterizer, &frame, framebuffers, layout, 1, format);
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([&](vk::CommandBuffer cmdbuf) {
@@ -198,15 +211,68 @@ void Vulkan::RendererVulkan::RenderScreenshot(
VkExtent3D{layout.width, layout.height, 1});
});
- // Ensure the copy is fully completed before saving the screenshot
+ // Ensure the copy is fully completed before saving the capture
scheduler.Finish();
- // Copy backing image data to the QImage screenshot buffer
+ // Copy backing image data to the capture buffer
dst_buffer.Invalidate();
+ return dst_buffer;
+}
+
+void RendererVulkan::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
+ if (!renderer_settings.screenshot_requested) {
+ return;
+ }
+
+ const auto& layout{renderer_settings.screenshot_framebuffer_layout};
+ const auto dst_buffer = RenderToBuffer(framebuffers, layout, VK_FORMAT_B8G8R8A8_UNORM,
+ layout.width * layout.height * 4);
+
std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(),
dst_buffer.Mapped().size());
renderer_settings.screenshot_complete_callback(false);
renderer_settings.screenshot_requested = false;
}
+std::vector<u8> RendererVulkan::GetAppletCaptureBuffer() {
+ using namespace VideoCore::Capture;
+
+ std::vector<u8> out(VideoCore::Capture::TiledSize);
+
+ if (!applet_frame.image) {
+ return out;
+ }
+
+ const auto dst_buffer =
+ CreateWrappedBuffer(memory_allocator, VideoCore::Capture::TiledSize, MemoryUsage::Download);
+
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([&](vk::CommandBuffer cmdbuf) {
+ DownloadColorImage(cmdbuf, *applet_frame.image, *dst_buffer, CaptureImageExtent);
+ });
+
+ // Ensure the copy is fully completed before writing the capture
+ scheduler.Finish();
+
+ // Swizzle image data to the capture buffer
+ dst_buffer.Invalidate();
+ Tegra::Texture::SwizzleTexture(out, dst_buffer.Mapped(), BytesPerPixel, LinearWidth,
+ LinearHeight, LinearDepth, BlockHeight, BlockDepth);
+
+ return out;
+}
+
+void RendererVulkan::RenderAppletCaptureLayer(
+ std::span<const Tegra::FramebufferConfig> framebuffers) {
+ if (!applet_frame.image) {
+ applet_frame.image = CreateWrappedImage(memory_allocator, CaptureImageSize, CaptureFormat);
+ applet_frame.image_view = CreateWrappedImageView(device, applet_frame.image, CaptureFormat);
+ applet_frame.framebuffer = blit_applet.CreateFramebuffer(
+ VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat);
+ }
+
+ blit_applet.DrawToFrame(rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1,
+ CaptureFormat);
+}
+
} // 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:
void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override;
+ std::vector<u8> GetAppletCaptureBuffer() override;
+
VideoCore::RasterizerInterface* ReadRasterizer() override {
return &rasterizer;
}
@@ -59,7 +61,11 @@ public:
private:
void Report() const;
+ vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+ const Layout::FramebufferLayout& layout, VkFormat format,
+ VkDeviceSize buffer_size);
void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
+ void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
Core::TelemetrySession& telemetry_session;
Tegra::MaxwellDeviceMemoryManager& device_memory;
@@ -79,9 +85,12 @@ private:
Swapchain swapchain;
PresentManager present_manager;
BlitScreen blit_swapchain;
- BlitScreen blit_screenshot;
+ BlitScreen blit_capture;
+ BlitScreen blit_applet;
RasterizerVulkan rasterizer;
std::optional<TurboMode> turbo_mode;
+
+ Frame applet_frame;
};
} // 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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/framebuffer_config.h"
+#include "video_core/present.h"
#include "video_core/renderer_vulkan/present/filters.h"
#include "video_core/renderer_vulkan/present/layer.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -12,9 +13,9 @@ namespace Vulkan {
BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_,
MemoryAllocator& memory_allocator_, PresentManager& present_manager_,
- Scheduler& scheduler_)
+ Scheduler& scheduler_, const PresentFilters& filters_)
: device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_},
- present_manager{present_manager_}, scheduler{scheduler_}, image_count{1},
+ present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1},
swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {}
BlitScreen::~BlitScreen() = default;
@@ -27,7 +28,7 @@ void BlitScreen::WaitIdle() {
void BlitScreen::SetWindowAdaptPass() {
layers.clear();
- scaling_filter = Settings::values.scaling_filter.GetValue();
+ scaling_filter = filters.get_scaling_filter();
switch (scaling_filter) {
case Settings::ScalingFilter::NearestNeighbor:
@@ -59,7 +60,7 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
bool presentation_recreate_required = false;
// Recreate dynamic resources if the adapting filter changed
- if (!window_adapt || scaling_filter != Settings::values.scaling_filter.GetValue()) {
+ if (!window_adapt || scaling_filter != filters.get_scaling_filter()) {
resource_update_required = true;
}
@@ -102,7 +103,7 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
while (layers.size() < framebuffers.size()) {
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
- window_size, window_adapt->GetDescriptorSetLayout());
+ window_size, window_adapt->GetDescriptorSetLayout(), filters);
}
// Perform the draw
@@ -119,8 +120,7 @@ vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& l
VkFormat current_view_format) {
const bool format_updated =
std::exchange(swapchain_view_format, current_view_format) != current_view_format;
- if (!window_adapt || scaling_filter != Settings::values.scaling_filter.GetValue() ||
- format_updated) {
+ if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) {
WaitIdle();
SetWindowAdaptPass();
}
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 {
class System;
}
+struct PresentFilters;
+
namespace Tegra {
struct FramebufferConfig;
}
@@ -47,7 +49,7 @@ class BlitScreen {
public:
explicit BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory, const Device& device,
MemoryAllocator& memory_allocator, PresentManager& present_manager,
- Scheduler& scheduler);
+ Scheduler& scheduler, const PresentFilters& filters);
~BlitScreen();
void DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
@@ -70,6 +72,7 @@ private:
MemoryAllocator& memory_allocator;
PresentManager& present_manager;
Scheduler& scheduler;
+ const PresentFilters& filters;
std::size_t image_count{};
std::size_t image_index{};
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
}();
const auto GetImageViewForFramebuffer = [&](ImageId image_id) {
- const ImageViewInfo info{ImageViewType::e2D, view_format};
+ ImageViewInfo info{ImageViewType::e2D, view_format};
+ if (config.blending == Tegra::BlendMode::Opaque) {
+ info.x_source = static_cast<u8>(SwizzleSource::R);
+ info.y_source = static_cast<u8>(SwizzleSource::G);
+ info.z_source = static_cast<u8>(SwizzleSource::B);
+ info.w_source = static_cast<u8>(SwizzleSource::OneFloat);
+ }
return std::make_pair(&slot_image_views[FindOrEmplaceImageView(image_id, info)],
slot_images[image_id].IsRescaled());
};
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
configuration/configuration_shared.cpp
configuration/configuration_shared.h
configuration/configure.ui
+ configuration/configure_applets.cpp
+ configuration/configure_applets.h
+ configuration/configure_applets.ui
configuration/configure_audio.cpp
configuration/configure_audio.h
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 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/settings.h"
+#include "core/core.h"
+#include "ui_configure_applets.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/configure_applets.h"
+#include "yuzu/configuration/shared_widget.h"
+
+ConfigureApplets::ConfigureApplets(Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureApplets>()}, system{system_} {
+ ui->setupUi(this);
+
+ Setup(builder);
+
+ SetConfiguration();
+}
+
+ConfigureApplets::~ConfigureApplets() = default;
+
+void ConfigureApplets::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureApplets::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureApplets::Setup(const ConfigurationShared::Builder& builder) {
+ auto& library_applets_layout = *ui->group_library_applet_modes->layout();
+ std::map<u32, QWidget*> applets_hold{};
+
+ std::vector<Settings::BasicSetting*> settings;
+ auto push = [&settings](auto& list) {
+ for (auto setting : list) {
+ settings.push_back(setting);
+ }
+ };
+
+ push(Settings::values.linkage.by_category[Settings::Category::LibraryApplet]);
+
+ for (auto setting : settings) {
+ ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
+
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ // Untested applets
+ if (setting->Id() == Settings::values.data_erase_applet_mode.Id() ||
+ setting->Id() == Settings::values.error_applet_mode.Id() ||
+ setting->Id() == Settings::values.net_connect_applet_mode.Id() ||
+ setting->Id() == Settings::values.web_applet_mode.Id() ||
+ setting->Id() == Settings::values.shop_applet_mode.Id() ||
+ setting->Id() == Settings::values.login_share_applet_mode.Id() ||
+ setting->Id() == Settings::values.wifi_web_auth_applet_mode.Id() ||
+ setting->Id() == Settings::values.my_page_applet_mode.Id()) {
+ widget->setHidden(true);
+ }
+
+ applets_hold.emplace(setting->Id(), widget);
+ }
+ for (const auto& [label, widget] : applets_hold) {
+ library_applets_layout.addWidget(widget);
+ }
+}
+
+void ConfigureApplets::SetConfiguration() {}
+
+void ConfigureApplets::ApplyConfiguration() {
+ const bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+
+class QCheckBox;
+class QLineEdit;
+class QComboBox;
+class QDateTimeEdit;
+namespace Core {
+class System;
+}
+
+namespace Ui {
+class ConfigureApplets;
+}
+
+namespace ConfigurationShared {
+class Builder;
+}
+
+class ConfigureApplets : public ConfigurationShared::Tab {
+public:
+ explicit ConfigureApplets(Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder,
+ QWidget* parent = nullptr);
+ ~ConfigureApplets() override;
+
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void Setup(const ConfigurationShared::Builder& builder);
+
+ std::vector<std::function<void(bool)>> apply_funcs{};
+
+ std::unique_ptr<Ui::ConfigureApplets> ui;
+ bool enabled = false;
+
+ Core::System& system;
+};
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureApplets</class>
+ <widget class="QWidget" name="ConfigureApplets">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>605</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <property name="accessibleName">
+ <string>Applets</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_1">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="group_library_applet_modes">
+ <property name="title">
+ <string>Applet mode preference</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QWidget" name="applets_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/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 @@
#include "core/core.h"
#include "ui_configure.h"
#include "vk_device_info.h"
+#include "yuzu/configuration/configure_applets.h"
#include "yuzu/configuration/configure_audio.h"
#include "yuzu/configuration/configure_cpu.h"
#include "yuzu/configuration/configure_debug_tab.h"
@@ -34,6 +35,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
this, !system_.IsPoweredOn())},
+ applets_tab{std::make_unique<ConfigureApplets>(system_, nullptr, *builder, this)},
audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},
debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
@@ -58,6 +60,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
ui->setupUi(this);
+ ui->tabWidget->addTab(applets_tab.get(), tr("Applets"));
ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
ui->tabWidget->addTab(cpu_tab.get(), tr("CPU"));
ui->tabWidget->addTab(debug_tab_tab.get(), tr("Debug"));
@@ -124,6 +127,7 @@ void ConfigureDialog::ApplyConfiguration() {
debug_tab_tab->ApplyConfiguration();
web_tab->ApplyConfiguration();
network_tab->ApplyConfiguration();
+ applets_tab->ApplyConfiguration();
system.ApplySettings();
Settings::LogSettings();
}
@@ -161,7 +165,8 @@ void ConfigureDialog::PopulateSelectionList() {
{{tr("General"),
{general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}},
{tr("System"),
- {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get()}},
+ {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(),
+ applets_tab.get()}},
{tr("CPU"), {cpu_tab.get()}},
{tr("Graphics"), {graphics_tab.get(), graphics_advanced_tab.get()}},
{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 {
class System;
}
+class ConfigureApplets;
class ConfigureAudio;
class ConfigureCpu;
class ConfigureDebugTab;
@@ -75,6 +76,7 @@ private:
std::unique_ptr<ConfigurationShared::Builder> builder;
std::vector<ConfigurationShared::Tab*> tab_group;
+ std::unique_ptr<ConfigureApplets> applets_tab;
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureCpu> cpu_tab;
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) {
// A setting can be ignored by giving it a blank name
+ // Applets
+ INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QStringLiteral());
+ INSERT(Settings, controller_applet_mode, tr("Controller configuration"), QStringLiteral());
+ INSERT(Settings, data_erase_applet_mode, tr("Data erase"), QStringLiteral());
+ INSERT(Settings, error_applet_mode, tr("Error"), QStringLiteral());
+ INSERT(Settings, net_connect_applet_mode, tr("Net connect"), QStringLiteral());
+ INSERT(Settings, player_select_applet_mode, tr("Player select"), QStringLiteral());
+ INSERT(Settings, swkbd_applet_mode, tr("Software keyboard"), QStringLiteral());
+ INSERT(Settings, mii_edit_applet_mode, tr("Mii Edit"), QStringLiteral());
+ INSERT(Settings, web_applet_mode, tr("Online web"), QStringLiteral());
+ INSERT(Settings, shop_applet_mode, tr("Shop"), QStringLiteral());
+ INSERT(Settings, photo_viewer_applet_mode, tr("Photo viewer"), QStringLiteral());
+ INSERT(Settings, offline_web_applet_mode, tr("Offline web"), QStringLiteral());
+ INSERT(Settings, login_share_applet_mode, tr("Login share"), QStringLiteral());
+ INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QStringLiteral());
+ INSERT(Settings, my_page_applet_mode, tr("My page"), QStringLiteral());
+
// Audio
INSERT(Settings, sink_id, tr("Output Engine:"), QStringLiteral());
INSERT(Settings, audio_output_device_id, tr("Output Device:"), QStringLiteral());
@@ -37,13 +54,28 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
QStringLiteral());
// Core
- INSERT(Settings, use_multi_core, tr("Multicore CPU Emulation"), QStringLiteral());
- INSERT(Settings, memory_layout_mode, tr("Memory Layout"), QStringLiteral());
+ INSERT(
+ Settings, use_multi_core, tr("Multicore CPU Emulation"),
+ tr("This option increases CPU emulation thread use from 1 to the Switch’s maximum of 4.\n"
+ "This is mainly a debug option and shouldn’t be disabled."));
+ INSERT(
+ Settings, memory_layout_mode, tr("Memory Layout"),
+ tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the "
+ "developer kit's 8/6GB.\nIt’s doesn’t improve stability or performance and is intended "
+ "to let big texture mods fit in emulated RAM.\nEnabling it will increase memory "
+ "use. It is not recommended to enable unless a specific game with a texture mod needs "
+ "it."));
INSERT(Settings, use_speed_limit, QStringLiteral(), QStringLiteral());
- INSERT(Settings, speed_limit, tr("Limit Speed Percent"), QStringLiteral());
+ INSERT(Settings, speed_limit, tr("Limit Speed Percent"),
+ tr("Controls the game's maximum rendering speed, but it’s up to each game if it runs "
+ "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a "
+ "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the "
+ "maximum your PC can reach."));
// Cpu
- INSERT(Settings, cpu_accuracy, tr("Accuracy:"), QStringLiteral());
+ INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
+ tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless "
+ "you know what you are doing."));
INSERT(Settings, cpu_backend, tr("Backend:"), QStringLiteral());
// Cpu Debug
@@ -63,34 +95,75 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
tr("This option improves the speed of 32 bits ASIMD floating-point functions by running "
"with incorrect rounding modes."));
INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"),
- tr("This option improves speed by removing NaN checking. Please note this also reduces "
+ tr("This option improves speed by removing NaN checking.\nPlease note this also reduces "
"accuracy of certain floating-point instructions."));
INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"),
tr("This option improves speed by eliminating a safety check before every memory "
- "read/write "
- "in guest. Disabling it may allow a game to read/write the emulator's memory."));
+ "read/write in guest.\nDisabling it may allow a game to read/write the emulator's "
+ "memory."));
INSERT(
Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"),
tr("This option improves speed by relying only on the semantics of cmpxchg to ensure "
- "safety of exclusive access instructions. Please note this may result in deadlocks and "
+ "safety of exclusive access instructions.\nPlease note this may result in deadlocks and "
"other race conditions."));
// Renderer
- INSERT(Settings, renderer_backend, tr("API:"), QStringLiteral());
- INSERT(Settings, vulkan_device, tr("Device:"), QStringLiteral());
- INSERT(Settings, shader_backend, tr("Shader Backend:"), QStringLiteral());
- INSERT(Settings, resolution_setup, tr("Resolution:"), QStringLiteral());
+ INSERT(
+ Settings, renderer_backend, tr("API:"),
+ tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases."));
+ INSERT(Settings, vulkan_device, tr("Device:"),
+ tr("This setting selects the GPU to use with the Vulkan backend."));
+ INSERT(Settings, shader_backend, tr("Shader Backend:"),
+ tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in "
+ "performance and the best in rendering accuracy.\n"
+ "GLASM is a deprecated NVIDIA-only backend that offers much better shader building "
+ "performance at the cost of FPS and rendering accuracy.\n"
+ "SPIR-V compiles the fastest, but yields poor results on most GPU drivers."));
+ INSERT(Settings, resolution_setup, tr("Resolution:"),
+ tr("Forces the game to render at a different resolution.\nHigher resolutions require "
+ "much more VRAM and bandwidth.\n"
+ "Options lower than 1X can cause rendering issues."));
INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QStringLiteral());
- INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), QStringLiteral());
- INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), QStringLiteral());
- INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), QStringLiteral());
- INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), QStringLiteral());
- INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"), QStringLiteral());
- INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
- QStringLiteral());
- INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), QStringLiteral());
- INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), QStringLiteral());
- INSERT(Settings, astc_recompression, tr("ASTC Recompression Method:"), QStringLiteral());
+ INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"),
+ tr("Determines how sharpened the image will look while using FSR’s dynamic contrast."));
+ INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
+ tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a "
+ "lower performance impact and can produce a better and more stable picture under "
+ "very low resolutions."));
+ INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
+ tr("The method used to render the window in fullscreen.\nBorderless offers the best "
+ "compatibility with the on-screen keyboard that some games request for "
+ "input.\nExclusive "
+ "fullscreen may offer better performance and better Freesync/Gsync support."));
+ INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"),
+ tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support "
+ "16:9, so custom game mods are required to get other ratios.\nAlso controls the "
+ "aspect ratio of captured screenshots."));
+ INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"),
+ tr("Allows saving shaders to storage for faster loading on following game "
+ "boots.\nDisabling "
+ "it is only intended for debugging."));
+ INSERT(
+ Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
+ tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
+ INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
+ tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
+ "decoding, or perform no decoding at all (black screen on videos).\n"
+ "In most cases, GPU decoding provides the best performance."));
+ INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"),
+ tr("This option controls how ASTC textures should be decoded.\n"
+ "CPU: Use the CPU for decoding, slowest but safest method.\n"
+ "GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most "
+ "games and users.\n"
+ "CPU Asynchronously: Use the CPU to decode ASTC textures as they arrive. Completely "
+ "eliminates ASTC decoding\nstuttering at the cost of rendering issues while the "
+ "texture is being decoded."));
+ INSERT(
+ Settings, astc_recompression, tr("ASTC Recompression Method:"),
+ tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing "
+ "the emulator to decompress to an intermediate format any card supports, RGBA8.\n"
+ "This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but "
+ "negatively affecting image quality."));
INSERT(
Settings, vsync_mode, tr("VSync Mode:"),
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) {
// Renderer (Advanced Graphics)
INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
- QStringLiteral());
+ tr("Slightly improves performance by moving presentation to a separate CPU thread."));
INSERT(
Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"),
tr("Runs work in the background while waiting for graphics commands to keep the GPU from "
"lowering its clock speed."));
- INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), QStringLiteral());
- INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"), QStringLiteral());
- INSERT(
- Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"),
- tr("Enables asynchronous shader compilation, which may reduce shader stutter. This feature "
- "is experimental."));
+ INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"),
+ tr("Controls the quality of texture rendering at oblique angles.\nIt’s a light setting "
+ "and safe to set at 16x on most GPUs."));
+ INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"),
+ tr("GPU emulation accuracy.\nMost games render fine with Normal, but High is still "
+ "required for some.\nParticles tend to only render correctly with High "
+ "accuracy.\nExtreme should only be used for debugging.\nThis option can "
+ "be changed while playing.\nSome games may require booting on high to render "
+ "properly."));
+ INSERT(Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"),
+ tr("Enables asynchronous shader compilation, which may reduce shader stutter.\nThis "
+ "feature "
+ "is experimental."));
INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"),
tr("Enables Fast GPU Time. This option will force most games to run at their highest "
"native resolution."));
INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"),
- tr("Enables GPU vendor-specific pipeline cache. This option can improve shader loading "
+ tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading "
"time significantly in cases where the Vulkan driver does not store pipeline cache "
"files internally."));
INSERT(
@@ -140,19 +220,27 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
// Renderer (Debug)
// System
- INSERT(Settings, rng_seed, tr("RNG Seed"), QStringLiteral());
+ INSERT(Settings, rng_seed, tr("RNG Seed"),
+ tr("Controls the seed of the random number generator.\nMainly used for speedrunning "
+ "purposes."));
INSERT(Settings, rng_seed_enabled, QStringLiteral(), QStringLiteral());
- INSERT(Settings, device_name, tr("Device Name"), QStringLiteral());
- INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), QStringLiteral());
+ INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch."));
+ INSERT(Settings, custom_rtc, tr("Custom RTC Date:"),
+ tr("This option allows to change the emulated clock of the Switch.\n"
+ "Can be used to manipulate time in games."));
INSERT(Settings, custom_rtc_enabled, QStringLiteral(), QStringLiteral());
INSERT(Settings, custom_rtc_offset, QStringLiteral(" "),
QStringLiteral("The number of seconds from the current unix time"));
INSERT(Settings, language_index, tr("Language:"),
tr("Note: this can be overridden when region setting is auto-select"));
- INSERT(Settings, region_index, tr("Region:"), QStringLiteral());
- INSERT(Settings, time_zone_index, tr("Time Zone:"), QStringLiteral());
+ INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch."));
+ INSERT(Settings, time_zone_index, tr("Time Zone:"),
+ tr("The time zone of the emulated Switch."));
INSERT(Settings, sound_index, tr("Sound Output Mode:"), QStringLiteral());
- INSERT(Settings, use_docked_mode, tr("Console Mode:"), QStringLiteral());
+ INSERT(Settings, use_docked_mode, tr("Console Mode:"),
+ tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change "
+ "their resolution, details and supported controllers and depending on this setting.\n"
+ "Setting to Handheld can help improve performance for low end systems."));
INSERT(Settings, current_user, QStringLiteral(), QStringLiteral());
// Controls
@@ -170,14 +258,19 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
// Ui
// Ui General
- INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"), QStringLiteral());
+ INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"),
+ tr("Ask to select a user profile on each boot, useful if multiple people use yuzu on "
+ "the same PC."));
INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"),
- QStringLiteral());
+ tr("This setting pauses yuzu when focusing other windows."));
INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"),
- QStringLiteral());
- INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), QStringLiteral());
+ tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling "
+ "it bypasses such prompts and directly exits the emulation."));
+ INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"),
+ tr("This setting hides the mouse after 2.5s of inactivity."));
INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
- QStringLiteral());
+ tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest "
+ "attempts to open the controller applet, it is immediately closed."));
// Linux
INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral());
@@ -203,6 +296,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
// Intentionally skipping VSyncMode to let the UI fill that one out
+ translations->insert({Settings::EnumMetadata<Settings::AppletMode>::Index(),
+ {
+ PAIR(AppletMode, HLE, tr("Custom frontend")),
+ PAIR(AppletMode, LLE, tr("Real applet")),
+ }});
translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
{
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
if (type != Core::HID::ControllerTriggerType::Button) {
return;
}
- if (!Settings::values.controller_navigation) {
- return;
- }
- if (button_sequence.npad.raw == Core::HID::NpadButton::None) {
+ if (button_sequence.npad.raw == Core::HID::NpadButton::None &&
+ button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) {
return;
}