summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt45
-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/android/app/src/main/jni/android_settings.h7
-rw-r--r--src/android/app/src/main/res/values/arrays.xml11
-rw-r--r--src/android/app/src/main/res/values/strings.xml8
-rw-r--r--src/core/CMakeLists.txt12
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp20
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h5
-rw-r--r--src/core/hle/service/ldn/ldn.cpp802
-rw-r--r--src/core/hle/service/ldn/ldn.h6
-rw-r--r--src/core/hle/service/ldn/ldn_types.h68
-rw-r--r--src/core/hle/service/ldn/monitor_service.cpp43
-rw-r--r--src/core/hle/service/ldn/monitor_service.h28
-rw-r--r--src/core/hle/service/ldn/sf_monitor_service.cpp40
-rw-r--r--src/core/hle/service/ldn/sf_monitor_service.h26
-rw-r--r--src/core/hle/service/ldn/sf_service.cpp37
-rw-r--r--src/core/hle/service/ldn/sf_service.h21
-rw-r--r--src/core/hle/service/ldn/sf_service_monitor.cpp50
-rw-r--r--src/core/hle/service/ldn/sf_service_monitor.h26
-rw-r--r--src/core/hle/service/ldn/system_local_communication_service.cpp56
-rw-r--r--src/core/hle/service/ldn/system_local_communication_service.h25
-rw-r--r--src/core/hle/service/ldn/user_local_communication_service.cpp320
-rw-r--r--src/core/hle/service/ldn/user_local_communication_service.h103
-rw-r--r--src/video_core/texture_cache/image_info.cpp1
-rw-r--r--src/video_core/texture_cache/image_info.h1
-rw-r--r--src/video_core/texture_cache/texture_cache.h15
-rw-r--r--src/yuzu/configuration/shared_translation.cpp158
-rw-r--r--src/yuzu/hotkeys.cpp6
35 files changed, 1257 insertions, 857 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 71be2d0b2..0165cb2d1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -24,7 +24,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
THEME_MODE("theme_mode"),
OVERLAY_SCALE("control_scale"),
OVERLAY_OPACITY("control_opacity"),
- LOCK_DRAWER("lock_drawer");
+ LOCK_DRAWER("lock_drawer"),
+ VERTICAL_ALIGNMENT("vertical_alignment"),
+ FSR_SHARPENING_SLIDER("fsr_sharpening_slider");
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index fee80bb21..862c6c483 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -93,4 +93,15 @@ object Settings {
entries.firstOrNull { it.int == int } ?: Unspecified
}
}
+
+ enum class EmulationVerticalAlignment(val int: Int) {
+ Top(1),
+ Center(0),
+ Bottom(2);
+
+ companion object {
+ fun from(int: Int): EmulationVerticalAlignment =
+ entries.firstOrNull { it.int == int } ?: Center
+ }
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 12f7aa1ab..21ca97bc1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -189,6 +189,16 @@ abstract class SettingsItem(
)
)
put(
+ SliderSetting(
+ IntSetting.FSR_SHARPENING_SLIDER,
+ R.string.fsr_sharpness,
+ R.string.fsr_sharpness_description,
+ 0,
+ 100,
+ "%"
+ )
+ )
+ put(
SingleChoiceSetting(
IntSetting.RENDERER_ANTI_ALIASING,
R.string.renderer_anti_aliasing,
@@ -216,6 +226,15 @@ abstract class SettingsItem(
)
)
put(
+ SingleChoiceSetting(
+ IntSetting.VERTICAL_ALIGNMENT,
+ R.string.vertical_alignment,
+ 0,
+ R.array.verticalAlignmentEntries,
+ R.array.verticalAlignmentValues
+ )
+ )
+ put(
SwitchSetting(
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
R.string.use_disk_shader_cache,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 2ad2f4966..db1a58147 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -143,10 +143,12 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_RESOLUTION.key)
add(IntSetting.RENDERER_VSYNC.key)
add(IntSetting.RENDERER_SCALING_FILTER.key)
+ add(IntSetting.FSR_SHARPENING_SLIDER.key)
add(IntSetting.RENDERER_ANTI_ALIASING.key)
add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
add(IntSetting.RENDERER_ASPECT_RATIO.key)
+ add(IntSetting.VERTICAL_ALIGNMENT.key)
add(BooleanSetting.PICTURE_IN_PICTURE.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
index f5647fa95..872553ac4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
@@ -104,7 +104,10 @@ class AddonsFragment : Fragment() {
requireActivity(),
titleId = R.string.addon_notice,
descriptionId = R.string.addon_notice_description,
- positiveAction = { addonViewModel.showModInstallPicker(true) }
+ dismissible = false,
+ positiveAction = { addonViewModel.showModInstallPicker(true) },
+ negativeAction = {},
+ negativeButtonTitleId = R.string.close
).show(parentFragmentManager, MessageDialogFragment.TAG)
addonViewModel.showModNoticeDialog(false)
}
@@ -119,7 +122,8 @@ class AddonsFragment : Fragment() {
requireActivity(),
titleId = R.string.confirm_uninstall,
descriptionId = R.string.confirm_uninstall_description,
- positiveAction = { addonViewModel.onDeleteAddon(it) }
+ positiveAction = { addonViewModel.onDeleteAddon(it) },
+ negativeAction = {}
).show(parentFragmentManager, MessageDialogFragment.TAG)
addonViewModel.setAddonToDelete(null)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 44af896da..6b25cc525 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -15,7 +15,9 @@ import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.os.SystemClock
+import android.util.Rational
import android.view.*
+import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
@@ -24,6 +26,7 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
@@ -52,6 +55,7 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationOrientation
+import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationVerticalAlignment
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
@@ -617,7 +621,46 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun updateScreenLayout() {
- binding.surfaceEmulation.setAspectRatio(null)
+ val verticalAlignment =
+ EmulationVerticalAlignment.from(IntSetting.VERTICAL_ALIGNMENT.getInt())
+ val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) {
+ 0 -> Rational(16, 9)
+ 1 -> Rational(4, 3)
+ 2 -> Rational(21, 9)
+ 3 -> Rational(16, 10)
+ else -> null // Best fit
+ }
+ when (verticalAlignment) {
+ EmulationVerticalAlignment.Top -> {
+ binding.surfaceEmulation.setAspectRatio(aspectRatio)
+ val params = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ binding.surfaceEmulation.layoutParams = params
+ }
+
+ EmulationVerticalAlignment.Center -> {
+ binding.surfaceEmulation.setAspectRatio(null)
+ binding.surfaceEmulation.updateLayoutParams {
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ height = ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ }
+
+ EmulationVerticalAlignment.Bottom -> {
+ binding.surfaceEmulation.setAspectRatio(aspectRatio)
+ val params =
+ FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ binding.surfaceEmulation.layoutParams = params
+ }
+ }
+ emulationState.updateSurface()
emulationActivity?.buildPictureInPictureParams()
updateOrientation()
}
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..c370964e1 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(android.R.string.ok)
+ } else {
+ getString(R.string.close)
+ }
+
+ 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.close used if empty. android.R.string.ok 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/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 4a3bc8e53..00baf86a9 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -38,6 +38,13 @@ struct Values {
Settings::Specialization::Default,
true,
true};
+ Settings::Setting<s32> vertical_alignment{linkage,
+ 0,
+ "vertical_alignment",
+ Settings::Category::Android,
+ Settings::Specialization::Default,
+ true,
+ true};
Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
Settings::Category::GpuDriver};
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 4701913eb..1bd6455b4 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -292,4 +292,15 @@
<item>5</item>
</integer-array>
+ <string-array name="verticalAlignmentEntries">
+ <item>@string/top</item>
+ <item>@string/center</item>
+ <item>@string/bottom</item>
+ </string-array>
+ <integer-array name="verticalAlignmentValues">
+ <item>1</item>
+ <item>0</item>
+ <item>2</item>
+ </integer-array>
+
</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 489e00107..78a4c958a 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -226,6 +226,8 @@
<string name="renderer_screen_layout">Orientation</string>
<string name="renderer_aspect_ratio">Aspect ratio</string>
<string name="renderer_scaling_filter">Window adapting filter</string>
+ <string name="fsr_sharpness">FSR sharpness</string>
+ <string name="fsr_sharpness_description">Determines how sharpened the image will look while using FSR\'s dynamic contrast</string>
<string name="renderer_anti_aliasing">Anti-aliasing method</string>
<string name="renderer_force_max_clock">Force maximum clocks (Adreno only)</string>
<string name="renderer_force_max_clock_description">Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).</string>
@@ -558,6 +560,12 @@
<string name="mute">Mute</string>
<string name="unmute">Unmute</string>
+ <!-- Emulation vertical alignment -->
+ <string name="vertical_alignment">Vertical alignment</string>
+ <string name="top">Top</string>
+ <string name="center">Center</string>
+ <string name="bottom">Bottom</string>
+
<!-- Licenses screen strings -->
<string name="licenses">Licenses</string>
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 5e2f4869e..dc2c611eb 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -666,6 +666,18 @@ add_library(core STATIC
hle/service/ldn/ldn.h
hle/service/ldn/ldn_results.h
hle/service/ldn/ldn_types.h
+ hle/service/ldn/monitor_service.cpp
+ hle/service/ldn/monitor_service.h
+ hle/service/ldn/sf_monitor_service.cpp
+ hle/service/ldn/sf_monitor_service.h
+ hle/service/ldn/sf_service.cpp
+ hle/service/ldn/sf_service.h
+ hle/service/ldn/sf_service_monitor.cpp
+ hle/service/ldn/sf_service_monitor.h
+ hle/service/ldn/system_local_communication_service.cpp
+ hle/service/ldn/system_local_communication_service.h
+ hle/service/ldn/user_local_communication_service.cpp
+ hle/service/ldn/user_local_communication_service.h
hle/service/ldr/ldr.cpp
hle/service/ldr/ldr.h
hle/service/lm/lm.cpp
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
index 8f3c04550..b9db19618 100644
--- a/src/core/hle/service/ldn/lan_discovery.cpp
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -85,15 +85,14 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
}
Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
- std::vector<NodeLatestUpdate>& out_updates,
- std::size_t buffer_count) {
- if (buffer_count > NodeCountMax) {
+ std::span<NodeLatestUpdate> out_updates) {
+ if (out_updates.size() > NodeCountMax) {
return ResultInvalidBufferCount;
}
if (state == State::AccessPointCreated || state == State::StationConnected) {
std::memcpy(&out_network, &network_info, sizeof(network_info));
- for (std::size_t i = 0; i < buffer_count; i++) {
+ for (std::size_t i = 0; i < out_updates.size(); i++) {
out_updates[i].state_change = node_changes[i].state_change;
node_changes[i].state_change = NodeStateChange::None;
}
@@ -107,15 +106,8 @@ DisconnectReason LANDiscovery::GetDisconnectReason() const {
return disconnect_reason;
}
-Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
+Result LANDiscovery::Scan(std::span<NetworkInfo> out_networks, s16& out_count,
const ScanFilter& filter) {
- if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
- filter.network_type <= NetworkType::All) {
- if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
- return ResultBadInput;
- }
- }
-
{
std::scoped_lock lock{packet_mutex};
scan_results.clear();
@@ -128,7 +120,7 @@ Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
std::scoped_lock lock{packet_mutex};
for (const auto& [key, info] : scan_results) {
- if (count >= networks.size()) {
+ if (out_count >= static_cast<s16>(out_networks.size())) {
break;
}
@@ -159,7 +151,7 @@ Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
}
}
- networks[count++] = info;
+ out_networks[out_count++] = info;
}
return ResultSuccess;
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
index 3833cd764..8f7a8dfc4 100644
--- a/src/core/hle/service/ldn/lan_discovery.h
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -54,11 +54,10 @@ public:
void SetState(State new_state);
Result GetNetworkInfo(NetworkInfo& out_network) const;
- Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
- std::size_t buffer_count);
+ Result GetNetworkInfo(NetworkInfo& out_network, std::span<NodeLatestUpdate> out_updates);
DisconnectReason GetDisconnectReason() const;
- Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
+ Result Scan(std::span<NetworkInfo> out_networks, s16& out_count, const ScanFilter& filter);
Result SetAdvertiseData(std::span<const u8> data);
Result OpenAccessPoint();
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 961f89a14..f2d638c30 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -1,36 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <memory>
-
#include "core/core.h"
-#include "core/hle/service/ldn/lan_discovery.h"
+#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ldn/ldn.h"
-#include "core/hle/service/ldn/ldn_results.h"
-#include "core/hle/service/ldn/ldn_types.h"
-#include "core/hle/service/server_manager.h"
-#include "core/internal_network/network.h"
-#include "core/internal_network/network_interface.h"
-#include "network/network.h"
-
-// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
-#undef CreateEvent
+#include "core/hle/service/ldn/monitor_service.h"
+#include "core/hle/service/ldn/sf_monitor_service.h"
+#include "core/hle/service/ldn/sf_service.h"
+#include "core/hle/service/ldn/sf_service_monitor.h"
+#include "core/hle/service/ldn/system_local_communication_service.h"
+#include "core/hle/service/ldn/user_local_communication_service.h"
namespace Service::LDN {
-class IMonitorService final : public ServiceFramework<IMonitorService> {
+class IMonitorServiceCreator final : public ServiceFramework<IMonitorServiceCreator> {
public:
- explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
+ explicit IMonitorServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:m"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"},
- {1, nullptr, "GetNetworkInfoForMonitor"},
- {2, nullptr, "GetIpv4AddressForMonitor"},
- {3, nullptr, "GetDisconnectReasonForMonitor"},
- {4, nullptr, "GetSecurityParameterForMonitor"},
- {5, nullptr, "GetNetworkConfigForMonitor"},
- {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"},
- {101, nullptr, "FinalizeMonitor"},
+ {0, C<&IMonitorServiceCreator::CreateMonitorService>, "CreateMonitorService"}
};
// clang-format on
@@ -38,84 +26,20 @@ public:
}
private:
- void GetStateForMonitor(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
- }
-
- void InitializeMonitor(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- state = State::Initialized;
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- State state{State::None};
-};
-
-class LDNM final : public ServiceFramework<LDNM> {
-public:
- explicit LDNM(Core::System& system_) : ServiceFramework{system_, "ldn:m"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &LDNM::CreateMonitorService, "CreateMonitorService"}
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
- void CreateMonitorService(HLERequestContext& ctx) {
+ Result CreateMonitorService(OutInterface<IMonitorService> out_interface) {
LOG_DEBUG(Service_LDN, "called");
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IMonitorService>(system);
+ *out_interface = std::make_shared<IMonitorService>(system);
+ R_SUCCEED();
}
};
-class ISystemLocalCommunicationService final
- : public ServiceFramework<ISystemLocalCommunicationService> {
+class ISystemServiceCreator final : public ServiceFramework<ISystemServiceCreator> {
public:
- explicit ISystemLocalCommunicationService(Core::System& system_)
- : ServiceFramework{system_, "ISystemLocalCommunicationService"} {
+ explicit ISystemServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:s"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetState"},
- {1, nullptr, "GetNetworkInfo"},
- {2, nullptr, "GetIpv4Address"},
- {3, nullptr, "GetDisconnectReason"},
- {4, nullptr, "GetSecurityParameter"},
- {5, nullptr, "GetNetworkConfig"},
- {100, nullptr, "AttachStateChangeEvent"},
- {101, nullptr, "GetNetworkInfoLatestUpdate"},
- {102, nullptr, "Scan"},
- {103, nullptr, "ScanPrivate"},
- {104, nullptr, "SetWirelessControllerRestriction"},
- {200, nullptr, "OpenAccessPoint"},
- {201, nullptr, "CloseAccessPoint"},
- {202, nullptr, "CreateNetwork"},
- {203, nullptr, "CreateNetworkPrivate"},
- {204, nullptr, "DestroyNetwork"},
- {205, nullptr, "Reject"},
- {206, nullptr, "SetAdvertiseData"},
- {207, nullptr, "SetStationAcceptPolicy"},
- {208, nullptr, "AddAcceptFilterEntry"},
- {209, nullptr, "ClearAcceptFilter"},
- {300, nullptr, "OpenStation"},
- {301, nullptr, "CloseStation"},
- {302, nullptr, "Connect"},
- {303, nullptr, "ConnectPrivate"},
- {304, nullptr, "Disconnect"},
- {400, nullptr, "InitializeSystem"},
- {401, nullptr, "FinalizeSystem"},
- {402, nullptr, "SetOperationMode"},
- {403, &ISystemLocalCommunicationService::InitializeSystem2, "InitializeSystem2"},
+ {0, C<&ISystemServiceCreator::CreateSystemLocalCommunicationService>, "CreateSystemLocalCommunicationService"},
};
// clang-format on
@@ -123,687 +47,78 @@ public:
}
private:
- void InitializeSystem2(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-};
-
-class IUserLocalCommunicationService final
- : public ServiceFramework<IUserLocalCommunicationService> {
-public:
- explicit IUserLocalCommunicationService(Core::System& system_)
- : ServiceFramework{system_, "IUserLocalCommunicationService"},
- service_context{system, "IUserLocalCommunicationService"},
- room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IUserLocalCommunicationService::GetState, "GetState"},
- {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
- {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
- {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
- {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
- {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
- {100, &IUserLocalCommunicationService::AttachStateChangeEvent, "AttachStateChangeEvent"},
- {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
- {102, &IUserLocalCommunicationService::Scan, "Scan"},
- {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
- {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
- {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
- {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
- {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
- {203, &IUserLocalCommunicationService::CreateNetworkPrivate, "CreateNetworkPrivate"},
- {204, &IUserLocalCommunicationService::DestroyNetwork, "DestroyNetwork"},
- {205, nullptr, "Reject"},
- {206, &IUserLocalCommunicationService::SetAdvertiseData, "SetAdvertiseData"},
- {207, &IUserLocalCommunicationService::SetStationAcceptPolicy, "SetStationAcceptPolicy"},
- {208, &IUserLocalCommunicationService::AddAcceptFilterEntry, "AddAcceptFilterEntry"},
- {209, nullptr, "ClearAcceptFilter"},
- {300, &IUserLocalCommunicationService::OpenStation, "OpenStation"},
- {301, &IUserLocalCommunicationService::CloseStation, "CloseStation"},
- {302, &IUserLocalCommunicationService::Connect, "Connect"},
- {303, nullptr, "ConnectPrivate"},
- {304, &IUserLocalCommunicationService::Disconnect, "Disconnect"},
- {400, &IUserLocalCommunicationService::Initialize, "Initialize"},
- {401, &IUserLocalCommunicationService::Finalize, "Finalize"},
- {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-
- state_change_event =
- service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent");
- }
-
- ~IUserLocalCommunicationService() {
- if (is_initialized) {
- if (auto room_member = room_network.GetRoomMember().lock()) {
- room_member->Unbind(ldn_packet_received);
- }
- }
-
- service_context.CloseEvent(state_change_event);
- }
-
- /// Callback to parse and handle a received LDN packet.
- void OnLDNPacketReceived(const Network::LDNPacket& packet) {
- lan_discovery.ReceivePacket(packet);
- }
-
- void OnEventFired() {
- state_change_event->Signal();
- }
-
- void GetState(HLERequestContext& ctx) {
- State state = State::Error;
-
- if (is_initialized) {
- state = lan_discovery.GetState();
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
- }
-
- void GetNetworkInfo(HLERequestContext& ctx) {
- const auto write_buffer_size = ctx.GetWriteBufferSize();
-
- if (write_buffer_size != sizeof(NetworkInfo)) {
- LOG_ERROR(Service_LDN, "Invalid buffer size {}", write_buffer_size);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultBadInput);
- return;
- }
-
- NetworkInfo network_info{};
- const auto rc = lan_discovery.GetNetworkInfo(network_info);
- if (rc.IsError()) {
- LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
- return;
- }
-
- ctx.WriteBuffer<NetworkInfo>(network_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void GetIpv4Address(HLERequestContext& ctx) {
- const auto network_interface = Network::GetSelectedNetworkInterface();
-
- if (!network_interface) {
- LOG_ERROR(Service_LDN, "No network interface available");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultNoIpAddress);
- return;
- }
-
- Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
- Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
-
- // When we're connected to a room, spoof the hosts IP address
- if (auto room_member = room_network.GetRoomMember().lock()) {
- if (room_member->IsConnected()) {
- current_address = room_member->GetFakeIpAddress();
- }
- }
-
- std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
- std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushRaw(current_address);
- rb.PushRaw(subnet_mask);
- }
-
- void GetDisconnectReason(HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(lan_discovery.GetDisconnectReason());
- }
-
- void GetSecurityParameter(HLERequestContext& ctx) {
- SecurityParameter security_parameter{};
- NetworkInfo info{};
- const Result rc = lan_discovery.GetNetworkInfo(info);
-
- if (rc.IsError()) {
- LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
- return;
- }
-
- security_parameter.session_id = info.network_id.session_id;
- std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
- sizeof(SecurityParameter::data));
-
- IPC::ResponseBuilder rb{ctx, 10};
- rb.Push(rc);
- rb.PushRaw<SecurityParameter>(security_parameter);
- }
-
- void GetNetworkConfig(HLERequestContext& ctx) {
- NetworkConfig config{};
- NetworkInfo info{};
- const Result rc = lan_discovery.GetNetworkInfo(info);
-
- if (rc.IsError()) {
- LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
- return;
- }
-
- config.intent_id = info.network_id.intent_id;
- config.channel = info.common.channel;
- config.node_count_max = info.ldn.node_count_max;
- config.local_communication_version = info.ldn.nodes[0].local_communication_version;
-
- IPC::ResponseBuilder rb{ctx, 10};
- rb.Push(rc);
- rb.PushRaw<NetworkConfig>(config);
- }
-
- void AttachStateChangeEvent(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(state_change_event->GetReadableEvent());
- }
-
- void GetNetworkInfoLatestUpdate(HLERequestContext& ctx) {
- const std::size_t network_buffer_size = ctx.GetWriteBufferSize(0);
- const std::size_t node_buffer_count = ctx.GetWriteBufferNumElements<NodeLatestUpdate>(1);
-
- if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
- LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
- node_buffer_count);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultBadInput);
- return;
- }
-
- NetworkInfo info{};
- std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
-
- const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
- if (rc.IsError()) {
- LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
- return;
- }
-
- ctx.WriteBuffer(info, 0);
- ctx.WriteBuffer(latest_update, 1);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void Scan(HLERequestContext& ctx) {
- ScanImpl(ctx);
- }
-
- void ScanPrivate(HLERequestContext& ctx) {
- ScanImpl(ctx, true);
- }
-
- void ScanImpl(HLERequestContext& ctx, bool is_private = false) {
- IPC::RequestParser rp{ctx};
- const auto channel{rp.PopEnum<WifiChannel>()};
- const auto scan_filter{rp.PopRaw<ScanFilter>()};
-
- const std::size_t network_info_size = ctx.GetWriteBufferNumElements<NetworkInfo>();
-
- if (network_info_size == 0) {
- LOG_ERROR(Service_LDN, "Invalid buffer size {}", network_info_size);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultBadInput);
- return;
- }
-
- u16 count = 0;
- std::vector<NetworkInfo> network_infos(network_info_size);
- Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
-
- LOG_INFO(Service_LDN,
- "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
- channel, scan_filter.flag, scan_filter.network_type, is_private);
-
- ctx.WriteBuffer(network_infos);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(rc);
- rb.Push<u32>(count);
- }
-
- void SetWirelessControllerRestriction(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void OpenAccessPoint(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.OpenAccessPoint());
- }
-
- void CloseAccessPoint(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.CloseAccessPoint());
- }
-
- void CreateNetwork(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- CreateNetworkImpl(ctx);
- }
-
- void CreateNetworkPrivate(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- CreateNetworkImpl(ctx, true);
- }
-
- void CreateNetworkImpl(HLERequestContext& ctx, bool is_private = false) {
- IPC::RequestParser rp{ctx};
-
- const auto security_config{rp.PopRaw<SecurityConfig>()};
- [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
- : SecurityParameter{}};
- const auto user_config{rp.PopRaw<UserConfig>()};
- rp.Pop<u32>(); // Padding
- const auto network_Config{rp.PopRaw<NetworkConfig>()};
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
- }
-
- void DestroyNetwork(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.DestroyNetwork());
- }
-
- void SetAdvertiseData(HLERequestContext& ctx) {
- const auto read_buffer = ctx.ReadBuffer();
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
- }
-
- void SetStationAcceptPolicy(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void AddAcceptFilterEntry(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void OpenStation(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.OpenStation());
- }
-
- void CloseStation(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.CloseStation());
- }
-
- void Connect(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- SecurityConfig security_config;
- UserConfig user_config;
- u32 local_communication_version;
- u32 option;
- };
- static_assert(sizeof(Parameters) == 0x7C, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_INFO(Service_LDN,
- "called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode, parameters.local_communication_version);
-
- const auto read_buffer = ctx.ReadBuffer();
- if (read_buffer.size() != sizeof(NetworkInfo)) {
- LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultBadInput);
- return;
- }
-
- NetworkInfo network_info{};
- std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
- static_cast<u16>(parameters.local_communication_version)));
- }
-
- void Disconnect(HLERequestContext& ctx) {
- LOG_INFO(Service_LDN, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.Disconnect());
- }
-
- void Initialize(HLERequestContext& ctx) {
- const auto rc = InitializeImpl(ctx);
- if (rc.IsError()) {
- LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
- }
-
- void Finalize(HLERequestContext& ctx) {
- if (auto room_member = room_network.GetRoomMember().lock()) {
- room_member->Unbind(ldn_packet_received);
- }
-
- is_initialized = false;
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(lan_discovery.Finalize());
- }
-
- void Initialize2(HLERequestContext& ctx) {
- const auto rc = InitializeImpl(ctx);
- if (rc.IsError()) {
- LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
- }
-
- Result InitializeImpl(HLERequestContext& ctx) {
- const auto network_interface = Network::GetSelectedNetworkInterface();
- if (!network_interface) {
- LOG_ERROR(Service_LDN, "No network interface is set");
- return ResultAirplaneModeEnabled;
- }
-
- if (auto room_member = room_network.GetRoomMember().lock()) {
- ldn_packet_received = room_member->BindOnLdnPacketReceived(
- [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
- } else {
- LOG_ERROR(Service_LDN, "Couldn't bind callback!");
- return ResultAirplaneModeEnabled;
- }
-
- lan_discovery.Initialize([&]() { OnEventFired(); });
- is_initialized = true;
- return ResultSuccess;
- }
-
- KernelHelpers::ServiceContext service_context;
- Kernel::KEvent* state_change_event;
- Network::RoomNetwork& room_network;
- LANDiscovery lan_discovery;
-
- // Callback identifier for the OnLDNPacketReceived event.
- Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
-
- bool is_initialized{};
-};
-
-class LDNS final : public ServiceFramework<LDNS> {
-public:
- explicit LDNS(Core::System& system_) : ServiceFramework{system_, "ldn:s"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &LDNS::CreateSystemLocalCommunicationService, "CreateSystemLocalCommunicationService"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
- void CreateSystemLocalCommunicationService(HLERequestContext& ctx) {
+ Result CreateSystemLocalCommunicationService(
+ OutInterface<ISystemLocalCommunicationService> out_interface) {
LOG_DEBUG(Service_LDN, "called");
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISystemLocalCommunicationService>(system);
+ *out_interface = std::make_shared<ISystemLocalCommunicationService>(system);
+ R_SUCCEED();
}
};
-class LDNU final : public ServiceFramework<LDNU> {
+class IUserServiceCreator final : public ServiceFramework<IUserServiceCreator> {
public:
- explicit LDNU(Core::System& system_) : ServiceFramework{system_, "ldn:u"} {
+ explicit IUserServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &LDNU::CreateUserLocalCommunicationService, "CreateUserLocalCommunicationService"},
+ {0, C<&IUserServiceCreator::CreateUserLocalCommunicationService>, "CreateUserLocalCommunicationService"},
};
// clang-format on
RegisterHandlers(functions);
}
- void CreateUserLocalCommunicationService(HLERequestContext& ctx) {
+private:
+ Result CreateUserLocalCommunicationService(
+ OutInterface<IUserLocalCommunicationService> out_interface) {
LOG_DEBUG(Service_LDN, "called");
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IUserLocalCommunicationService>(system);
+ *out_interface = std::make_shared<IUserLocalCommunicationService>(system);
+ R_SUCCEED();
}
};
-class INetworkService final : public ServiceFramework<INetworkService> {
+class ISfServiceCreator final : public ServiceFramework<ISfServiceCreator> {
public:
- explicit INetworkService(Core::System& system_) : ServiceFramework{system_, "INetworkService"} {
+ explicit ISfServiceCreator(Core::System& system_, bool is_system_, const char* name_)
+ : ServiceFramework{system_, name_}, is_system{is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Initialize"},
- {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
- {264, nullptr, "GetNetworkInterfaceLastError"},
- {272, nullptr, "GetRole"},
- {280, nullptr, "GetAdvertiseData"},
- {288, nullptr, "GetGroupInfo"},
- {296, nullptr, "GetGroupInfo2"},
- {304, nullptr, "GetGroupOwner"},
- {312, nullptr, "GetIpConfig"},
- {320, nullptr, "GetLinkLevel"},
- {512, nullptr, "Scan"},
- {768, nullptr, "CreateGroup"},
- {776, nullptr, "DestroyGroup"},
- {784, nullptr, "SetAdvertiseData"},
- {1536, nullptr, "SendToOtherGroup"},
- {1544, nullptr, "RecvFromOtherGroup"},
- {1552, nullptr, "AddAcceptableGroupId"},
- {1560, nullptr, "ClearAcceptableGroupId"},
+ {0, C<&ISfServiceCreator::CreateNetworkService>, "CreateNetworkService"},
+ {8, C<&ISfServiceCreator::CreateNetworkServiceMonitor>, "CreateNetworkServiceMonitor"},
};
// clang-format on
RegisterHandlers(functions);
}
-};
-
-class INetworkServiceMonitor final : public ServiceFramework<INetworkServiceMonitor> {
-public:
- explicit INetworkServiceMonitor(Core::System& system_)
- : ServiceFramework{system_, "INetworkServiceMonitor"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &INetworkServiceMonitor::Initialize, "Initialize"},
- {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
- {264, nullptr, "GetNetworkInterfaceLastError"},
- {272, nullptr, "GetRole"},
- {280, nullptr, "GetAdvertiseData"},
- {281, nullptr, "GetAdvertiseData2"},
- {288, nullptr, "GetGroupInfo"},
- {296, nullptr, "GetGroupInfo2"},
- {304, nullptr, "GetGroupOwner"},
- {312, nullptr, "GetIpConfig"},
- {320, nullptr, "GetLinkLevel"},
- {328, nullptr, "AttachJoinEvent"},
- {336, nullptr, "GetMembers"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
- void Initialize(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultDisabled);
- }
-};
-
-class LP2PAPP final : public ServiceFramework<LP2PAPP> {
-public:
- explicit LP2PAPP(Core::System& system_) : ServiceFramework{system_, "lp2p:app"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &LP2PAPP::CreateMonitorService, "CreateNetworkService"},
- {8, &LP2PAPP::CreateMonitorService, "CreateNetworkServiceMonitor"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
- void CreateNetworkervice(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 reserved_input = rp.Pop<u64>();
- const u32 input = rp.Pop<u32>();
-
- LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={} input={}", reserved_input,
- input);
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<INetworkService>(system);
- }
-
- void CreateMonitorService(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 reserved_input = rp.Pop<u64>();
-
- LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={}", reserved_input);
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<INetworkServiceMonitor>(system);
- }
-};
-
-class LP2PSYS final : public ServiceFramework<LP2PSYS> {
-public:
- explicit LP2PSYS(Core::System& system_) : ServiceFramework{system_, "lp2p:sys"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &LP2PSYS::CreateMonitorService, "CreateNetworkService"},
- {8, &LP2PSYS::CreateMonitorService, "CreateNetworkServiceMonitor"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
- void CreateNetworkervice(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 reserved_input = rp.Pop<u64>();
- const u32 input = rp.Pop<u32>();
+private:
+ Result CreateNetworkService(OutInterface<ISfService> out_interface, u32 input,
+ u64 reserved_input) {
LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={} input={}", reserved_input,
input);
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<INetworkService>(system);
+ *out_interface = std::make_shared<ISfService>(system);
+ R_SUCCEED();
}
- void CreateMonitorService(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 reserved_input = rp.Pop<u64>();
-
+ Result CreateNetworkServiceMonitor(OutInterface<ISfServiceMonitor> out_interface,
+ u64 reserved_input) {
LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={}", reserved_input);
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<INetworkServiceMonitor>(system);
+ *out_interface = std::make_shared<ISfServiceMonitor>(system);
+ R_SUCCEED();
}
-};
-class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
-public:
- explicit ISfMonitorService(Core::System& system_)
- : ServiceFramework{system_, "ISfMonitorService"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &ISfMonitorService::Initialize, "Initialize"},
- {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"},
- {320, nullptr, "GetLinkLevel"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void Initialize(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(0);
- }
-
- void GetGroupInfo(HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
- struct GroupInfo {
- std::array<u8, 0x200> info;
- };
-
- GroupInfo group_info{};
-
- ctx.WriteBuffer(group_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
+ bool is_system{};
};
-class LP2PM final : public ServiceFramework<LP2PM> {
+class ISfMonitorServiceCreator final : public ServiceFramework<ISfMonitorServiceCreator> {
public:
- explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
+ explicit ISfMonitorServiceCreator(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &LP2PM::CreateMonitorService, "CreateMonitorService"},
+ {0, C<&ISfMonitorServiceCreator::CreateMonitorService>, "CreateMonitorService"},
};
// clang-format on
@@ -811,28 +126,27 @@ public:
}
private:
- void CreateMonitorService(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 reserved_input = rp.Pop<u64>();
-
+ Result CreateMonitorService(OutInterface<ISfMonitorService> out_interface, u64 reserved_input) {
LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input);
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISfMonitorService>(system);
+ *out_interface = std::make_shared<ISfMonitorService>(system);
+ R_SUCCEED();
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system));
- server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system));
- server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system));
+ server_manager->RegisterNamedService("ldn:m", std::make_shared<IMonitorServiceCreator>(system));
+ server_manager->RegisterNamedService("ldn:s", std::make_shared<ISystemServiceCreator>(system));
+ server_manager->RegisterNamedService("ldn:u", std::make_shared<IUserServiceCreator>(system));
- server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system));
- server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system));
- server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system));
+ server_manager->RegisterNamedService(
+ "lp2p:app", std::make_shared<ISfServiceCreator>(system, false, "lp2p:app"));
+ server_manager->RegisterNamedService(
+ "lp2p:sys", std::make_shared<ISfServiceCreator>(system, true, "lp2p:sys"));
+ server_manager->RegisterNamedService("lp2p:m",
+ std::make_shared<ISfMonitorServiceCreator>(system));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h
index f4a319168..dae037fa8 100644
--- a/src/core/hle/service/ldn/ldn.h
+++ b/src/core/hle/service/ldn/ldn.h
@@ -3,12 +3,6 @@
#pragma once
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/result.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/sm/sm.h"
-
namespace Core {
class System;
}
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
index 44c2c773b..6198aa07b 100644
--- a/src/core/hle/service/ldn/ldn_types.h
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -123,6 +123,18 @@ enum class NodeStatus : u8 {
Connected,
};
+enum class WirelessControllerRestriction : u32 {
+ None,
+ Default,
+};
+
+struct ConnectOption {
+ union {
+ u32 raw;
+ };
+};
+static_assert(sizeof(ConnectOption) == 0x4, "ConnectOption is an invalid size");
+
struct NodeLatestUpdate {
NodeStateChange state_change;
INSERT_PADDING_BYTES(0x7); // Unknown
@@ -139,9 +151,9 @@ static_assert(sizeof(SessionId) == 0x10, "SessionId is an invalid size");
struct IntentId {
u64 local_communication_id;
- INSERT_PADDING_BYTES(0x2); // Reserved
+ INSERT_PADDING_BYTES_NOINIT(0x2); // Reserved
u16 scene_id;
- INSERT_PADDING_BYTES(0x4); // Reserved
+ INSERT_PADDING_BYTES_NOINIT(0x4); // Reserved
};
static_assert(sizeof(IntentId) == 0x10, "IntentId is an invalid size");
@@ -152,13 +164,14 @@ struct NetworkId {
static_assert(sizeof(NetworkId) == 0x20, "NetworkId is an invalid size");
struct Ssid {
- u8 length{};
- std::array<char, SsidLengthMax + 1> raw{};
+ u8 length;
+ std::array<char, SsidLengthMax + 1> raw;
Ssid() = default;
constexpr explicit Ssid(std::string_view data) {
length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
+ raw = {};
data.copy(raw.data(), length);
raw[length] = 0;
}
@@ -181,7 +194,7 @@ using Ipv4Address = std::array<u8, 4>;
static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
struct MacAddress {
- std::array<u8, 6> raw{};
+ std::array<u8, 6> raw;
friend bool operator==(const MacAddress& lhs, const MacAddress& rhs) = default;
};
@@ -211,7 +224,7 @@ struct CommonNetworkInfo {
WifiChannel channel;
LinkLevel link_level;
PackedNetworkType network_type;
- INSERT_PADDING_BYTES(0x4);
+ INSERT_PADDING_BYTES_NOINIT(0x4);
};
static_assert(sizeof(CommonNetworkInfo) == 0x30, "CommonNetworkInfo is an invalid size");
@@ -221,9 +234,9 @@ struct NodeInfo {
s8 node_id;
u8 is_connected;
std::array<u8, UserNameBytesMax + 1> user_name;
- INSERT_PADDING_BYTES(0x1); // Reserved
+ INSERT_PADDING_BYTES_NOINIT(0x1); // Reserved
s16 local_communication_version;
- INSERT_PADDING_BYTES(0x10); // Reserved
+ INSERT_PADDING_BYTES_NOINIT(0x10); // Reserved
};
static_assert(sizeof(NodeInfo) == 0x40, "NodeInfo is an invalid size");
@@ -232,14 +245,14 @@ struct LdnNetworkInfo {
SecurityMode security_mode;
AcceptPolicy station_accept_policy;
u8 has_action_frame;
- INSERT_PADDING_BYTES(0x2); // Padding
+ INSERT_PADDING_BYTES_NOINIT(0x2); // Padding
u8 node_count_max;
u8 node_count;
std::array<NodeInfo, NodeCountMax> nodes;
- INSERT_PADDING_BYTES(0x2); // Reserved
+ INSERT_PADDING_BYTES_NOINIT(0x2); // Reserved
u16 advertise_data_size;
std::array<u8, AdvertiseDataSizeMax> advertise_data;
- INSERT_PADDING_BYTES(0x8C); // Reserved
+ INSERT_PADDING_BYTES_NOINIT(0x8C); // Reserved
u64 random_authentication_id;
};
static_assert(sizeof(LdnNetworkInfo) == 0x430, "LdnNetworkInfo is an invalid size");
@@ -250,6 +263,7 @@ struct NetworkInfo {
LdnNetworkInfo ldn;
};
static_assert(sizeof(NetworkInfo) == 0x480, "NetworkInfo is an invalid size");
+static_assert(std::is_trivial_v<NetworkInfo>, "NetworkInfo type must be trivially copyable.");
struct SecurityConfig {
SecurityMode security_mode;
@@ -303,4 +317,36 @@ struct AddressList {
};
static_assert(sizeof(AddressList) == 0x60, "AddressList is an invalid size");
+struct GroupInfo {
+ std::array<u8, 0x200> info;
+};
+
+struct CreateNetworkConfig {
+ SecurityConfig security_config;
+ UserConfig user_config;
+ INSERT_PADDING_BYTES(0x4);
+ NetworkConfig network_config;
+};
+static_assert(sizeof(CreateNetworkConfig) == 0x98, "CreateNetworkConfig is an invalid size");
+
+#pragma pack(push, 4)
+struct CreateNetworkConfigPrivate {
+ SecurityConfig security_config;
+ SecurityParameter security_parameter;
+ UserConfig user_config;
+ INSERT_PADDING_BYTES(0x4);
+ NetworkConfig network_config;
+};
+#pragma pack(pop)
+static_assert(sizeof(CreateNetworkConfigPrivate) == 0xB8,
+ "CreateNetworkConfigPrivate is an invalid size");
+
+struct ConnectNetworkData {
+ SecurityConfig security_config;
+ UserConfig user_config;
+ s32 local_communication_version;
+ ConnectOption option;
+};
+static_assert(sizeof(ConnectNetworkData) == 0x7c, "ConnectNetworkData is an invalid size");
+
} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/monitor_service.cpp b/src/core/hle/service/ldn/monitor_service.cpp
new file mode 100644
index 000000000..3471f69da
--- /dev/null
+++ b/src/core/hle/service/ldn/monitor_service.cpp
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/monitor_service.h"
+
+namespace Service::LDN {
+
+IMonitorService::IMonitorService(Core::System& system_)
+ : ServiceFramework{system_, "IMonitorService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, C<&IMonitorService::GetStateForMonitor>, "GetStateForMonitor"},
+ {1, nullptr, "GetNetworkInfoForMonitor"},
+ {2, nullptr, "GetIpv4AddressForMonitor"},
+ {3, nullptr, "GetDisconnectReasonForMonitor"},
+ {4, nullptr, "GetSecurityParameterForMonitor"},
+ {5, nullptr, "GetNetworkConfigForMonitor"},
+ {100, C<&IMonitorService::InitializeMonitor>, "InitializeMonitor"},
+ {101, nullptr, "FinalizeMonitor"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IMonitorService::~IMonitorService() = default;
+
+Result IMonitorService::GetStateForMonitor(Out<State> out_state) {
+ LOG_INFO(Service_LDN, "called");
+
+ *out_state = state;
+ R_SUCCEED();
+}
+
+Result IMonitorService::InitializeMonitor() {
+ LOG_INFO(Service_LDN, "called");
+
+ state = State::Initialized;
+ R_SUCCEED();
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/monitor_service.h b/src/core/hle/service/ldn/monitor_service.h
new file mode 100644
index 000000000..61aacef30
--- /dev/null
+++ b/src/core/hle/service/ldn/monitor_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/ldn/ldn_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::LDN {
+
+class IMonitorService final : public ServiceFramework<IMonitorService> {
+public:
+ explicit IMonitorService(Core::System& system_);
+ ~IMonitorService() override;
+
+private:
+ Result GetStateForMonitor(Out<State> out_state);
+ Result InitializeMonitor();
+
+ State state{State::None};
+};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/sf_monitor_service.cpp b/src/core/hle/service/ldn/sf_monitor_service.cpp
new file mode 100644
index 000000000..9e6736ff2
--- /dev/null
+++ b/src/core/hle/service/ldn/sf_monitor_service.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/ldn/sf_monitor_service.h"
+
+namespace Service::LDN {
+
+ISfMonitorService::ISfMonitorService(Core::System& system_)
+ : ServiceFramework{system_, "ISfMonitorService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, C<&ISfMonitorService::Initialize>, "Initialize"},
+ {288, C<&ISfMonitorService::GetGroupInfo>, "GetGroupInfo"},
+ {320, nullptr, "GetLinkLevel"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ISfMonitorService::~ISfMonitorService() = default;
+
+Result ISfMonitorService::Initialize(Out<u32> out_value) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ *out_value = 0;
+ R_SUCCEED();
+}
+
+Result ISfMonitorService::GetGroupInfo(
+ OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ *out_group_info = GroupInfo{};
+ R_SUCCEED();
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/sf_monitor_service.h b/src/core/hle/service/ldn/sf_monitor_service.h
new file mode 100644
index 000000000..d02115201
--- /dev/null
+++ b/src/core/hle/service/ldn/sf_monitor_service.h
@@ -0,0 +1,26 @@
+// 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::LDN {
+struct GroupInfo;
+
+class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
+public:
+ explicit ISfMonitorService(Core::System& system_);
+ ~ISfMonitorService() override;
+
+private:
+ Result Initialize(Out<u32> out_value);
+ Result GetGroupInfo(OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
+};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/sf_service.cpp b/src/core/hle/service/ldn/sf_service.cpp
new file mode 100644
index 000000000..61cabe219
--- /dev/null
+++ b/src/core/hle/service/ldn/sf_service.cpp
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/ldn/sf_service.h"
+
+namespace Service::LDN {
+
+ISfService::ISfService(Core::System& system_) : ServiceFramework{system_, "ISfService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Initialize"},
+ {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
+ {264, nullptr, "GetNetworkInterfaceLastError"},
+ {272, nullptr, "GetRole"},
+ {280, nullptr, "GetAdvertiseData"},
+ {288, nullptr, "GetGroupInfo"},
+ {296, nullptr, "GetGroupInfo2"},
+ {304, nullptr, "GetGroupOwner"},
+ {312, nullptr, "GetIpConfig"},
+ {320, nullptr, "GetLinkLevel"},
+ {512, nullptr, "Scan"},
+ {768, nullptr, "CreateGroup"},
+ {776, nullptr, "DestroyGroup"},
+ {784, nullptr, "SetAdvertiseData"},
+ {1536, nullptr, "SendToOtherGroup"},
+ {1544, nullptr, "RecvFromOtherGroup"},
+ {1552, nullptr, "AddAcceptableGroupId"},
+ {1560, nullptr, "ClearAcceptableGroupId"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ISfService::~ISfService() = default;
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/sf_service.h b/src/core/hle/service/ldn/sf_service.h
new file mode 100644
index 000000000..05534b567
--- /dev/null
+++ b/src/core/hle/service/ldn/sf_service.h
@@ -0,0 +1,21 @@
+// 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::LDN {
+
+class ISfService final : public ServiceFramework<ISfService> {
+public:
+ explicit ISfService(Core::System& system_);
+ ~ISfService() override;
+};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/sf_service_monitor.cpp b/src/core/hle/service/ldn/sf_service_monitor.cpp
new file mode 100644
index 000000000..33e3c1d69
--- /dev/null
+++ b/src/core/hle/service/ldn/sf_service_monitor.cpp
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/ldn/sf_service_monitor.h"
+
+namespace Service::LDN {
+
+ISfServiceMonitor::ISfServiceMonitor(Core::System& system_)
+ : ServiceFramework{system_, "ISfServiceMonitor"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, C<&ISfServiceMonitor::Initialize>, "Initialize"},
+ {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
+ {264, nullptr, "GetNetworkInterfaceLastError"},
+ {272, nullptr, "GetRole"},
+ {280, nullptr, "GetAdvertiseData"},
+ {281, nullptr, "GetAdvertiseData2"},
+ {288, C<&ISfServiceMonitor::GetGroupInfo>, "GetGroupInfo"},
+ {296, nullptr, "GetGroupInfo2"},
+ {304, nullptr, "GetGroupOwner"},
+ {312, nullptr, "GetIpConfig"},
+ {320, nullptr, "GetLinkLevel"},
+ {328, nullptr, "AttachJoinEvent"},
+ {336, nullptr, "GetMembers"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ISfServiceMonitor::~ISfServiceMonitor() = default;
+
+Result ISfServiceMonitor::Initialize(Out<u32> out_value) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ *out_value = 0;
+ R_SUCCEED();
+}
+
+Result ISfServiceMonitor::GetGroupInfo(
+ OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ *out_group_info = GroupInfo{};
+ R_SUCCEED();
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/sf_service_monitor.h b/src/core/hle/service/ldn/sf_service_monitor.h
new file mode 100644
index 000000000..3cfc5005e
--- /dev/null
+++ b/src/core/hle/service/ldn/sf_service_monitor.h
@@ -0,0 +1,26 @@
+// 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::LDN {
+struct GroupInfo;
+
+class ISfServiceMonitor final : public ServiceFramework<ISfServiceMonitor> {
+public:
+ explicit ISfServiceMonitor(Core::System& system_);
+ ~ISfServiceMonitor() override;
+
+private:
+ Result Initialize(Out<u32> out_value);
+ Result GetGroupInfo(OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
+};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/system_local_communication_service.cpp b/src/core/hle/service/ldn/system_local_communication_service.cpp
new file mode 100644
index 000000000..7b52223cd
--- /dev/null
+++ b/src/core/hle/service/ldn/system_local_communication_service.cpp
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/system_local_communication_service.h"
+
+namespace Service::LDN {
+
+ISystemLocalCommunicationService::ISystemLocalCommunicationService(Core::System& system_)
+ : ServiceFramework{system_, "ISystemLocalCommunicationService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetState"},
+ {1, nullptr, "GetNetworkInfo"},
+ {2, nullptr, "GetIpv4Address"},
+ {3, nullptr, "GetDisconnectReason"},
+ {4, nullptr, "GetSecurityParameter"},
+ {5, nullptr, "GetNetworkConfig"},
+ {100, nullptr, "AttachStateChangeEvent"},
+ {101, nullptr, "GetNetworkInfoLatestUpdate"},
+ {102, nullptr, "Scan"},
+ {103, nullptr, "ScanPrivate"},
+ {104, nullptr, "SetWirelessControllerRestriction"},
+ {200, nullptr, "OpenAccessPoint"},
+ {201, nullptr, "CloseAccessPoint"},
+ {202, nullptr, "CreateNetwork"},
+ {203, nullptr, "CreateNetworkPrivate"},
+ {204, nullptr, "DestroyNetwork"},
+ {205, nullptr, "Reject"},
+ {206, nullptr, "SetAdvertiseData"},
+ {207, nullptr, "SetStationAcceptPolicy"},
+ {208, nullptr, "AddAcceptFilterEntry"},
+ {209, nullptr, "ClearAcceptFilter"},
+ {300, nullptr, "OpenStation"},
+ {301, nullptr, "CloseStation"},
+ {302, nullptr, "Connect"},
+ {303, nullptr, "ConnectPrivate"},
+ {304, nullptr, "Disconnect"},
+ {400, nullptr, "InitializeSystem"},
+ {401, nullptr, "FinalizeSystem"},
+ {402, nullptr, "SetOperationMode"},
+ {403, C<&ISystemLocalCommunicationService::InitializeSystem2>, "InitializeSystem2"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ISystemLocalCommunicationService::~ISystemLocalCommunicationService() = default;
+
+Result ISystemLocalCommunicationService::InitializeSystem2() {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/system_local_communication_service.h b/src/core/hle/service/ldn/system_local_communication_service.h
new file mode 100644
index 000000000..a02b097ea
--- /dev/null
+++ b/src/core/hle/service/ldn/system_local_communication_service.h
@@ -0,0 +1,25 @@
+// 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::LDN {
+
+class ISystemLocalCommunicationService final
+ : public ServiceFramework<ISystemLocalCommunicationService> {
+public:
+ explicit ISystemLocalCommunicationService(Core::System& system_);
+ ~ISystemLocalCommunicationService() override;
+
+private:
+ Result InitializeSystem2();
+};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/user_local_communication_service.cpp b/src/core/hle/service/ldn/user_local_communication_service.cpp
new file mode 100644
index 000000000..f28368962
--- /dev/null
+++ b/src/core/hle/service/ldn/user_local_communication_service.cpp
@@ -0,0 +1,320 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+
+#include "core/core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/ldn_results.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/ldn/user_local_communication_service.h"
+#include "core/hle/service/server_manager.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+#include "network/network.h"
+
+// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
+#undef CreateEvent
+
+namespace Service::LDN {
+
+IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_)
+ : ServiceFramework{system_, "IUserLocalCommunicationService"},
+ service_context{system, "IUserLocalCommunicationService"},
+ room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, C<&IUserLocalCommunicationService::GetState>, "GetState"},
+ {1, C<&IUserLocalCommunicationService::GetNetworkInfo>, "GetNetworkInfo"},
+ {2, C<&IUserLocalCommunicationService::GetIpv4Address>, "GetIpv4Address"},
+ {3, C<&IUserLocalCommunicationService::GetDisconnectReason>, "GetDisconnectReason"},
+ {4, C<&IUserLocalCommunicationService::GetSecurityParameter>, "GetSecurityParameter"},
+ {5, C<&IUserLocalCommunicationService::GetNetworkConfig>, "GetNetworkConfig"},
+ {100, C<&IUserLocalCommunicationService::AttachStateChangeEvent>, "AttachStateChangeEvent"},
+ {101, C<&IUserLocalCommunicationService::GetNetworkInfoLatestUpdate>, "GetNetworkInfoLatestUpdate"},
+ {102, C<&IUserLocalCommunicationService::Scan>, "Scan"},
+ {103, C<&IUserLocalCommunicationService::ScanPrivate>, "ScanPrivate"},
+ {104, C<&IUserLocalCommunicationService::SetWirelessControllerRestriction>, "SetWirelessControllerRestriction"},
+ {200, C<&IUserLocalCommunicationService::OpenAccessPoint>, "OpenAccessPoint"},
+ {201, C<&IUserLocalCommunicationService::CloseAccessPoint>, "CloseAccessPoint"},
+ {202, C<&IUserLocalCommunicationService::CreateNetwork>, "CreateNetwork"},
+ {203, C<&IUserLocalCommunicationService::CreateNetworkPrivate>, "CreateNetworkPrivate"},
+ {204, C<&IUserLocalCommunicationService::DestroyNetwork>, "DestroyNetwork"},
+ {205, nullptr, "Reject"},
+ {206, C<&IUserLocalCommunicationService::SetAdvertiseData>, "SetAdvertiseData"},
+ {207, C<&IUserLocalCommunicationService::SetStationAcceptPolicy>, "SetStationAcceptPolicy"},
+ {208, C<&IUserLocalCommunicationService::AddAcceptFilterEntry>, "AddAcceptFilterEntry"},
+ {209, nullptr, "ClearAcceptFilter"},
+ {300, C<&IUserLocalCommunicationService::OpenStation>, "OpenStation"},
+ {301, C<&IUserLocalCommunicationService::CloseStation>, "CloseStation"},
+ {302, C<&IUserLocalCommunicationService::Connect>, "Connect"},
+ {303, nullptr, "ConnectPrivate"},
+ {304, C<&IUserLocalCommunicationService::Disconnect>, "Disconnect"},
+ {400, C<&IUserLocalCommunicationService::Initialize>, "Initialize"},
+ {401, C<&IUserLocalCommunicationService::Finalize>, "Finalize"},
+ {402, C<&IUserLocalCommunicationService::Initialize2>, "Initialize2"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ state_change_event =
+ service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent");
+}
+
+IUserLocalCommunicationService::~IUserLocalCommunicationService() {
+ if (is_initialized) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
+ }
+
+ service_context.CloseEvent(state_change_event);
+}
+
+Result IUserLocalCommunicationService::GetState(Out<State> out_state) {
+ *out_state = State::Error;
+
+ if (is_initialized) {
+ *out_state = lan_discovery.GetState();
+ }
+
+ LOG_INFO(Service_LDN, "called, state={}", *out_state);
+
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetNetworkInfo(
+ OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info) {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.GetNetworkInfo(*out_network_info));
+}
+
+Result IUserLocalCommunicationService::GetIpv4Address(Out<Ipv4Address> out_current_address,
+ Out<Ipv4Address> out_subnet_mask) {
+ LOG_INFO(Service_LDN, "called");
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ R_UNLESS(network_interface.has_value(), ResultNoIpAddress);
+
+ *out_current_address = {Network::TranslateIPv4(network_interface->ip_address)};
+ *out_subnet_mask = {Network::TranslateIPv4(network_interface->subnet_mask)};
+
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ *out_current_address = room_member->GetFakeIpAddress();
+ }
+ }
+
+ std::reverse(std::begin(*out_current_address), std::end(*out_current_address)); // ntohl
+ std::reverse(std::begin(*out_subnet_mask), std::end(*out_subnet_mask)); // ntohl
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetDisconnectReason(
+ Out<DisconnectReason> out_disconnect_reason) {
+ LOG_INFO(Service_LDN, "called");
+
+ *out_disconnect_reason = lan_discovery.GetDisconnectReason();
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetSecurityParameter(
+ Out<SecurityParameter> out_security_parameter) {
+ LOG_INFO(Service_LDN, "called");
+
+ NetworkInfo info{};
+ R_TRY(lan_discovery.GetNetworkInfo(info));
+
+ out_security_parameter->session_id = info.network_id.session_id;
+ std::memcpy(out_security_parameter->data.data(), info.ldn.security_parameter.data(),
+ sizeof(SecurityParameter::data));
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetNetworkConfig(Out<NetworkConfig> out_network_config) {
+ LOG_INFO(Service_LDN, "called");
+
+ NetworkInfo info{};
+ R_TRY(lan_discovery.GetNetworkInfo(info));
+
+ out_network_config->intent_id = info.network_id.intent_id;
+ out_network_config->channel = info.common.channel;
+ out_network_config->node_count_max = info.ldn.node_count_max;
+ out_network_config->local_communication_version = info.ldn.nodes[0].local_communication_version;
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::AttachStateChangeEvent(
+ OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_INFO(Service_LDN, "called");
+
+ *out_event = &state_change_event->GetReadableEvent();
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetNetworkInfoLatestUpdate(
+ OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info,
+ OutArray<NodeLatestUpdate, BufferAttr_HipcPointer> out_node_latest_update) {
+ LOG_INFO(Service_LDN, "called");
+
+ R_UNLESS(!out_node_latest_update.empty(), ResultBadInput);
+
+ R_RETURN(lan_discovery.GetNetworkInfo(*out_network_info, out_node_latest_update));
+}
+
+Result IUserLocalCommunicationService::Scan(
+ Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+ OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info) {
+ LOG_INFO(Service_LDN, "called, channel={}, filter_scan_flag={}, filter_network_type={}",
+ channel, scan_filter.flag, scan_filter.network_type);
+
+ R_UNLESS(!out_network_info.empty(), ResultBadInput);
+ R_RETURN(lan_discovery.Scan(out_network_info, *network_count, scan_filter));
+}
+
+Result IUserLocalCommunicationService::ScanPrivate(
+ Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+ OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info) {
+ LOG_INFO(Service_LDN, "called, channel={}, filter_scan_flag={}, filter_network_type={}",
+ channel, scan_filter.flag, scan_filter.network_type);
+
+ R_UNLESS(out_network_info.empty(), ResultBadInput);
+ R_RETURN(lan_discovery.Scan(out_network_info, *network_count, scan_filter));
+}
+
+Result IUserLocalCommunicationService::SetWirelessControllerRestriction(
+ WirelessControllerRestriction wireless_restriction) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::OpenAccessPoint() {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.OpenAccessPoint());
+}
+
+Result IUserLocalCommunicationService::CloseAccessPoint() {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.CloseAccessPoint());
+}
+
+Result IUserLocalCommunicationService::CreateNetwork(const CreateNetworkConfig& create_config) {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.CreateNetwork(create_config.security_config, create_config.user_config,
+ create_config.network_config));
+}
+
+Result IUserLocalCommunicationService::CreateNetworkPrivate(
+ const CreateNetworkConfigPrivate& create_config,
+ InArray<AddressEntry, BufferAttr_HipcPointer> address_list) {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.CreateNetwork(create_config.security_config, create_config.user_config,
+ create_config.network_config));
+}
+
+Result IUserLocalCommunicationService::DestroyNetwork() {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.DestroyNetwork());
+}
+
+Result IUserLocalCommunicationService::SetAdvertiseData(
+ InBuffer<BufferAttr_HipcAutoSelect> buffer_data) {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.SetAdvertiseData(buffer_data));
+}
+
+Result IUserLocalCommunicationService::SetStationAcceptPolicy(AcceptPolicy accept_policy) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::AddAcceptFilterEntry(MacAddress mac_address) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::OpenStation() {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.OpenStation());
+}
+
+Result IUserLocalCommunicationService::CloseStation() {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.CloseStation());
+}
+
+Result IUserLocalCommunicationService::Connect(
+ const ConnectNetworkData& connect_data,
+ InLargeData<NetworkInfo, BufferAttr_HipcPointer> network_info) {
+ LOG_INFO(Service_LDN,
+ "called, passphrase_size={}, security_mode={}, "
+ "local_communication_version={}",
+ connect_data.security_config.passphrase_size,
+ connect_data.security_config.security_mode, connect_data.local_communication_version);
+
+ R_RETURN(lan_discovery.Connect(*network_info, connect_data.user_config,
+ static_cast<u16>(connect_data.local_communication_version)));
+}
+
+Result IUserLocalCommunicationService::Disconnect() {
+ LOG_INFO(Service_LDN, "called");
+
+ R_RETURN(lan_discovery.Disconnect());
+}
+
+Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) {
+ LOG_INFO(Service_LDN, "called, process_id={}", aruid.pid);
+
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+ R_UNLESS(network_interface, ResultAirplaneModeEnabled);
+
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ ldn_packet_received = room_member->BindOnLdnPacketReceived(
+ [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
+ } else {
+ LOG_ERROR(Service_LDN, "Couldn't bind callback!");
+ R_RETURN(ResultAirplaneModeEnabled);
+ }
+
+ lan_discovery.Initialize([&]() { OnEventFired(); });
+ is_initialized = true;
+ R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::Finalize() {
+ LOG_INFO(Service_LDN, "called");
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
+
+ is_initialized = false;
+
+ R_RETURN(lan_discovery.Finalize());
+}
+
+Result IUserLocalCommunicationService::Initialize2(u32 version, ClientProcessId process_id) {
+ LOG_INFO(Service_LDN, "called, version={}, process_id={}", version, process_id.pid);
+ R_RETURN(Initialize(process_id));
+}
+
+void IUserLocalCommunicationService::OnLDNPacketReceived(const Network::LDNPacket& packet) {
+ lan_discovery.ReceivePacket(packet);
+}
+
+void IUserLocalCommunicationService::OnEventFired() {
+ state_change_event->Signal();
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/user_local_communication_service.h b/src/core/hle/service/ldn/user_local_communication_service.h
new file mode 100644
index 000000000..6698d10d2
--- /dev/null
+++ b/src/core/hle/service/ldn/user_local_communication_service.h
@@ -0,0 +1,103 @@
+// 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/ldn/lan_discovery.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Network {
+class RoomNetwork;
+}
+
+namespace Service::LDN {
+
+class IUserLocalCommunicationService final
+ : public ServiceFramework<IUserLocalCommunicationService> {
+public:
+ explicit IUserLocalCommunicationService(Core::System& system_);
+ ~IUserLocalCommunicationService() override;
+
+private:
+ Result GetState(Out<State> out_state);
+
+ Result GetNetworkInfo(OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info);
+
+ Result GetIpv4Address(Out<Ipv4Address> out_current_address, Out<Ipv4Address> out_subnet_mask);
+
+ Result GetDisconnectReason(Out<DisconnectReason> out_disconnect_reason);
+
+ Result GetSecurityParameter(Out<SecurityParameter> out_security_parameter);
+
+ Result GetNetworkConfig(Out<NetworkConfig> out_network_config);
+
+ Result AttachStateChangeEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+
+ Result GetNetworkInfoLatestUpdate(
+ OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info,
+ OutArray<NodeLatestUpdate, BufferAttr_HipcPointer> out_node_latest_update);
+
+ Result Scan(Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+ OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info);
+
+ Result ScanPrivate(Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+ OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info);
+
+ Result SetWirelessControllerRestriction(WirelessControllerRestriction wireless_restriction);
+
+ Result OpenAccessPoint();
+
+ Result CloseAccessPoint();
+
+ Result CreateNetwork(const CreateNetworkConfig& create_network_Config);
+
+ Result CreateNetworkPrivate(const CreateNetworkConfigPrivate& create_network_Config,
+ InArray<AddressEntry, BufferAttr_HipcPointer> address_list);
+
+ Result DestroyNetwork();
+
+ Result SetAdvertiseData(InBuffer<BufferAttr_HipcAutoSelect> buffer_data);
+
+ Result SetStationAcceptPolicy(AcceptPolicy accept_policy);
+
+ Result AddAcceptFilterEntry(MacAddress mac_address);
+
+ Result OpenStation();
+
+ Result CloseStation();
+
+ Result Connect(const ConnectNetworkData& connect_data,
+ InLargeData<NetworkInfo, BufferAttr_HipcPointer> network_info);
+
+ Result Disconnect();
+
+ Result Initialize(ClientProcessId aruid);
+
+ Result Finalize();
+
+ Result Initialize2(u32 version, ClientProcessId aruid);
+
+private:
+ /// Callback to parse and handle a received LDN packet.
+ void OnLDNPacketReceived(const Network::LDNPacket& packet);
+ void OnEventFired();
+
+ KernelHelpers::ServiceContext service_context;
+ Kernel::KEvent* state_change_event;
+ Network::RoomNetwork& room_network;
+ LANDiscovery lan_discovery;
+
+ // Callback identifier for the OnLDNPacketReceived event.
+ Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
+
+ bool is_initialized{};
+};
+
+} // namespace Service::LDN
diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp
index b72788c6d..9444becce 100644
--- a/src/video_core/texture_cache/image_info.cpp
+++ b/src/video_core/texture_cache/image_info.cpp
@@ -42,6 +42,7 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept {
};
}
rescaleable = false;
+ is_sparse = config.is_sparse != 0;
tile_width_spacing = config.tile_width_spacing;
if (config.texture_type != TextureType::Texture2D &&
config.texture_type != TextureType::Texture2DNoMipmap) {
diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h
index 8a4cb0cbd..eb490a642 100644
--- a/src/video_core/texture_cache/image_info.h
+++ b/src/video_core/texture_cache/image_info.h
@@ -41,6 +41,7 @@ struct ImageInfo {
bool downscaleable = false;
bool forced_flushed = false;
bool dma_downloaded = false;
+ bool is_sparse = false;
};
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 3a1cc060e..01c3561c9 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -600,17 +600,17 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz
[&](ImageId id, Image&) { deleted_images.push_back(id); });
for (const ImageId id : deleted_images) {
Image& image = slot_images[id];
- if (True(image.flags & ImageFlagBits::CpuModified)) {
- continue;
+ if (False(image.flags & ImageFlagBits::CpuModified)) {
+ image.flags |= ImageFlagBits::CpuModified;
+ if (True(image.flags & ImageFlagBits::Tracked)) {
+ UntrackImage(image, id);
+ }
}
- image.flags |= ImageFlagBits::CpuModified;
+
if (True(image.flags & ImageFlagBits::Remapped)) {
continue;
}
image.flags |= ImageFlagBits::Remapped;
- if (True(image.flags & ImageFlagBits::Tracked)) {
- UntrackImage(image, id);
- }
}
}
@@ -1469,7 +1469,8 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
Image& new_image = slot_images[new_image_id];
- if (!gpu_memory->IsContinuousRange(new_image.gpu_addr, new_image.guest_size_bytes)) {
+ if (!gpu_memory->IsContinuousRange(new_image.gpu_addr, new_image.guest_size_bytes) &&
+ new_info.is_sparse) {
new_image.flags |= ImageFlagBits::Sparse;
}
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index ce65b2bf1..d138b53c8 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -54,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
@@ -80,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 "
@@ -121,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(
@@ -157,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
@@ -187,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());
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;
}